/* * pwm-intelce2700.c * * GPL LICENSE SUMMARY * * Copyright (c) 2020-2022 MaxLinear, Inc. * Copyright (c) 2008-2020 Intel Corporation. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include #include #include #include /**Default PWM clock frequency is 25M HZ, period in nano secs */ #define PWM_CLK_PERIOD_NS 40 #define PWM_CHAN1_IDX 0 #define PWM_CHAN2_IDX 1 #define PWM_CHAN_MAX 2 #define PWM_CHAN1_REG_OFFSET 0x4 #define PWM_CHAN2_REG_OFFSET 0x8 #define PWMEN_REG_OFFSET 0xC #define PWM_CHAN1_EN (1 << 1) #define PWM_CHAN2_EN (1 << 2) #define PWM_CHAN1_OUT_EN (1 << 9) #define PWM_CHAN2_OUT_EN (1 << 10) #define PWM_CHAN1_EN_WD (PWM_CHAN1_EN | PWM_CHAN1_OUT_EN) #define PWM_CHAN2_EN_WD (PWM_CHAN2_EN | PWM_CHAN2_OUT_EN) #define BOOT_CFG_UNLOCK0_OFFSET 0x007C #define BOOT_CFG_UNLOCK1_OFFSET 0x0080 #define BBU_CRU_OFFSET 0x00A0 #define BOOT_CFG_BBU_ACCESS_CTRL_OFFSET 0x0158 #define BOOT_CFG_UNLOCK0_DATA 0x20406080 #define BOOT_CFG_UNLOCK1_DATA 0x10305070 #define BBU_CRU_ENABLE 0x3 #define BBU_ACCESS_CTRL 0x7 struct intelce2700_pwm_chip { struct pwm_chip chip; void __iomem *reg_base; uint16_t range[PWM_CHAN_MAX]; uint16_t duty_cycle[PWM_CHAN_MAX]; struct device *dev; spinlock_t lock; atomic_t access_ok; }; static inline struct intelce2700_pwm_chip *to_intelce2700_pwm_chip (struct pwm_chip *chip) { return container_of(chip, struct intelce2700_pwm_chip, chip); } static int intelce2700_pwm_config(struct pwm_chip *chip, struct pwm_device *pwmdev, int duty_ns, int period_ns) { int idx; struct intelce2700_pwm_chip *pwm = to_intelce2700_pwm_chip(chip); void __iomem *ptr = pwm->reg_base; int chan_idx = pwmdev->pwm - pwmdev->chip->base; uint16_t range_plus_one; uint16_t duty_cycle_val; uint32_t val; if (pwmdev->hwpwm >= PWM_CHAN_MAX) return -EINVAL; if (chan_idx != PWM_CHAN1_IDX && chan_idx != PWM_CHAN2_IDX) return -EINVAL; if (period_ns < PWM_CLK_PERIOD_NS) return -EINVAL; /** Called to set period, return and calculate when duty_ns is set*/ if (duty_ns == 0) return 0; spin_lock(&pwm->lock); if (!atomic_read(&pwm->access_ok)) { spin_unlock(&pwm->lock); return -ENODEV; } idx = pwmdev->hwpwm; if (idx == 0) ptr += PWM_CHAN1_REG_OFFSET; else if (idx == 1) ptr += PWM_CHAN2_REG_OFFSET; dev_dbg(pwm->dev, "Intelce2700 pwm config pwm %d hwpwm %d\n", pwmdev->pwm, pwmdev->hwpwm); /** range_plus_one = period_ns/pwm_clock_period */ if (period_ns > 0) { range_plus_one = period_ns/PWM_CLK_PERIOD_NS; pwm->range[idx] = range_plus_one - 1; } /** duty_cycle_val = (duty_ns * range_plus_one)/period_ns */ if (duty_ns > 0) { duty_cycle_val = (duty_ns * range_plus_one)/(period_ns); pwm->duty_cycle[idx] = duty_cycle_val; } val = ((pwm->duty_cycle[idx] << 16) | pwm->range[idx]); iowrite32be(val, ptr); spin_unlock(&pwm->lock); dev_dbg(pwm->dev, "Range %08X duty %08X\n", pwm->range[idx], pwm->duty_cycle[idx]); return 0; } static uint32_t calculate_pwm_ctrl_word(int idx, uint32_t val, bool en) { if (en) { if (idx == PWM_CHAN1_IDX) val |= PWM_CHAN1_EN_WD; else if (idx == PWM_CHAN2_IDX) val |= PWM_CHAN2_EN_WD; } else { if (idx == PWM_CHAN1_IDX) val &= ~PWM_CHAN1_EN_WD; else if (idx == PWM_CHAN2_IDX) val &= ~PWM_CHAN2_EN_WD; } return val; } static int pwm_control(int idx, void __iomem *ptr, bool en) { uint32_t val; uint32_t rdbk_val; val = ioread32be(ptr); val = calculate_pwm_ctrl_word(idx, val, en); iowrite32be(val, ptr); /**Check PWM channel0 control bits are modified */ rdbk_val = ioread32be(ptr); if (val != rdbk_val) { val = calculate_pwm_ctrl_word(idx, rdbk_val, en); iowrite32be(val, ptr); } pr_debug("Intelce2700 pwm %s pwm %d regval= %08X\n", (en ? "enable" : "disable"), idx, val); return 0; } static int intelce2700_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwmdev) { int ret = 0; struct intelce2700_pwm_chip *pwm = to_intelce2700_pwm_chip(chip); int chan_idx = pwmdev->pwm - pwmdev->chip->base; if (pwmdev->hwpwm >= PWM_CHAN_MAX) return -EINVAL; if (chan_idx != PWM_CHAN1_IDX && chan_idx != PWM_CHAN2_IDX) return -EINVAL; spin_lock(&pwm->lock); if (!atomic_read(&pwm->access_ok)) { spin_unlock(&pwm->lock); return -ENODEV; } ret = pwm_control(chan_idx, pwm->reg_base+PWMEN_REG_OFFSET, true); spin_unlock(&pwm->lock); return ret; } static void intelce2700_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwmdev) { struct intelce2700_pwm_chip *pwm = to_intelce2700_pwm_chip(chip); int chan_idx = pwmdev->pwm - pwmdev->chip->base; if (pwmdev->hwpwm >= PWM_CHAN_MAX) return ; if (chan_idx != PWM_CHAN1_IDX && chan_idx != PWM_CHAN2_IDX) return ; spin_lock(&pwm->lock); if (!atomic_read(&pwm->access_ok)) { spin_unlock(&pwm->lock); return ; } pwm_control(chan_idx, pwm->reg_base+PWMEN_REG_OFFSET, false); spin_unlock(&pwm->lock); } static const struct pwm_ops intelce2700_pwm_ops = { .config = intelce2700_pwm_config, .enable = intelce2700_pwm_enable, .disable = intelce2700_pwm_disable, .owner = THIS_MODULE, }; int intelce2700_pwm_handle_pwr_state(void *data, netss_power_state_t state) { struct intelce2700_pwm_chip *pwm = data; spin_lock(&pwm->lock); if (state == NETSS_NETIP_POWER_STATE_ACTIVE) atomic_set(&pwm->access_ok, 1); else atomic_set(&pwm->access_ok, 0); spin_unlock(&pwm->lock); return 0; } int intelce2700_pwm_probe(struct platform_device *pdev) { int ret = 0; netss_dev_info_t boot_cfg_mmio; netss_dev_info_t clk_ctrl_mmio; netss_dev_info_t pwm_mmio; struct intelce2700_pwm_chip *pwm; void __iomem *boot_cfg = NULL; netss_power_state_callback_info_t pwr_cb_info; pwm = kzalloc(sizeof(struct intelce2700_pwm_chip), GFP_KERNEL); if (!pwm) ret = -ENOMEM; pwm->chip.ops = &intelce2700_pwm_ops; pwm->chip.base = PWM_CHAN1_IDX; pwm->chip.npwm = PWM_CHAN_MAX; pwm->chip.dev = &pdev->dev; ret = pwmchip_add(&pwm->chip); if (ret < 0) { dev_err(&pdev->dev, "pwmchip add failed: %d\n", ret); return ret; } /**Enable host access to PWM registers */ /*1. set boot and cfg */ ret = netss_device_get_info(NETSS_DEV_BOOTCFG, &boot_cfg_mmio); if (ret < 0) { dev_err(&pdev->dev, "Failed to get netss subdev %d info, ret %d\n", NETSS_DEV_BOOTCFG, ret); goto free_mem; } else { boot_cfg = ioremap(boot_cfg_mmio.base, boot_cfg_mmio.size); if (NULL == boot_cfg) { ret = -EFAULT; goto free_mem; } iowrite32be(BOOT_CFG_UNLOCK0_DATA, boot_cfg + BOOT_CFG_UNLOCK0_OFFSET); iowrite32be(BOOT_CFG_UNLOCK1_DATA, boot_cfg + BOOT_CFG_UNLOCK1_OFFSET); } /*2.Enable BBU CRU */ ret = netss_device_get_info(NETSS_DEV_CLK_CTRL, &clk_ctrl_mmio); if (ret < 0) { dev_err(&pdev->dev, "Failed to get netss subdev %d info, ret %d\n", NETSS_DEV_CLK_CTRL, ret); goto unmap_mem; } else { void __iomem *bbu_cru; uint32_t val; bbu_cru = ioremap(clk_ctrl_mmio.base+BBU_CRU_OFFSET, 4); if (NULL == bbu_cru) { ret = -EFAULT; goto unmap_mem; } val = ioread32be(bbu_cru); val |= BBU_CRU_ENABLE; iowrite32be(val, bbu_cru); iounmap(bbu_cru); } /*3. Enable BBU access */ if (boot_cfg) { uint32_t val; void __iomem *ptr = boot_cfg + BOOT_CFG_BBU_ACCESS_CTRL_OFFSET; val = ioread32be(ptr); val |= BBU_ACCESS_CTRL; iowrite32be(val, ptr); iounmap(boot_cfg); } ret = netss_device_get_info(NETSS_DEV_PWM, &pwm_mmio); if (ret) { dev_err(&pdev->dev, "Failed to get pwm mmio\n"); goto free_mem; } pwm->reg_base = ioremap(pwm_mmio.base, pwm_mmio.size); if (!pwm->reg_base) { dev_err(&pdev->dev, "Failed to ioremap pwm mmio\n"); ret = -EFAULT; goto free_mem; } atomic_set(&pwm->access_ok, 1); pwm->dev = &pdev->dev; platform_set_drvdata(pdev, pwm); pwr_cb_info.func = intelce2700_pwm_handle_pwr_state; pwr_cb_info.args = pwm; ret = netss_power_state_change_callback_register(NETSS_DEV_PWM, &pwr_cb_info); if (ret) { dev_err(&pdev->dev, "Failed to register netss pwr state callback\n"); iounmap(pwm->reg_base); goto free_mem; } spin_lock_init(&pwm->lock); return ret; unmap_mem: if (boot_cfg) iounmap(boot_cfg); free_mem: kfree(pwm); return ret; } int intelce2700_pwm_remove(struct platform_device *pdev) { struct intelce2700_pwm_chip *pwm = platform_get_drvdata(pdev); if (!pwm) return -EINVAL; pwmchip_remove(&pwm->chip); iounmap(pwm->reg_base); kfree(pwm); return 0; } static const struct acpi_device_id intelce2700_pwm_acpi_match[] = { { "INT351A", 0 }, { } }; MODULE_DEVICE_TABLE(acpi, intelce2700_pwm_acpi_match); static struct platform_driver intelce2700_pwm_driver = { .probe = intelce2700_pwm_probe, .remove = intelce2700_pwm_remove, .driver = { .name = "intelce2700_pwm_driver", .owner = THIS_MODULE, .acpi_match_table = ACPI_PTR(intelce2700_pwm_acpi_match), } }; static int intelce2700_pwm_drv_init(void) { return platform_driver_register(&intelce2700_pwm_driver); } static void intelce2700_pwm_drv_exit(void) { platform_driver_unregister(&intelce2700_pwm_driver); } device_initcall(intelce2700_pwm_drv_init); module_exit(intelce2700_pwm_drv_exit); MODULE_DESCRIPTION("Intel(R) CE 2700 PWM Driver"); MODULE_AUTHOR("Intel Corporation"); MODULE_LICENSE("GPL");