2014-07-16 9:55 GMT+08:00 Huayi Li <huayi.li@xxxxxxx>: > PWM controller of CSR SiRFSoC can generate 7 independent outputs. Each output > duty cycle can be adjusted by setting the corresponding wait & hold registers. > There are 6 external channels (0 to 5) and 1 internal channel (6). > Supports a wide frequency range: the source clock divider can be from 2 > up to 65536*2. > > Signed-off-by: Huayi Li <huayi.li@xxxxxxx> > --- > drivers/pwm/Kconfig | 10 ++ > drivers/pwm/Makefile | 1 + > drivers/pwm/pwm-sirf.c | 450 +++++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 461 insertions(+) > create mode 100644 drivers/pwm/pwm-sirf.c > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig > index 4ad7b89..77d65a6 100644 > --- a/drivers/pwm/Kconfig > +++ b/drivers/pwm/Kconfig > @@ -215,6 +215,16 @@ config PWM_SAMSUNG > To compile this driver as a module, choose M here: the module > will be called pwm-samsung. > > +config PWM_SIRF > + tristate "SiRF PWM support" > + depends on ARCH_SIRF > + help > + Generic PWM framework driver for the PWM controller on SiRF > + SoCs. > + > + To compile this driver as a module, choose M here: the module > + will be called pwm-sirf. > + > config PWM_SPEAR > tristate "STMicroelectronics SPEAr PWM support" > depends on PLAT_SPEAR > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile > index 5c86a19..7fa4f14 100644 > --- a/drivers/pwm/Makefile > +++ b/drivers/pwm/Makefile > @@ -19,6 +19,7 @@ obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o > obj-$(CONFIG_PWM_PXA) += pwm-pxa.o > obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o > obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o > +obj-$(CONFIG_PWM_SIRF) += pwm-sirf.o > obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o > obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o > obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o > diff --git a/drivers/pwm/pwm-sirf.c b/drivers/pwm/pwm-sirf.c > new file mode 100644 > index 0000000..a5a9e2f > --- /dev/null > +++ b/drivers/pwm/pwm-sirf.c > @@ -0,0 +1,450 @@ > +/* > + * SIRF serial SoC PWM device core driver > + * > + * Copyright (c) 2014 Cambridge Silicon Radio Limited, a CSR plc group company. > + * > + * Licensed under GPLv2. > + */ > + > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/device.h> > +#include <linux/init.h> > +#include <linux/io.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/pwm.h> > + > +#define SIRF_PWM_SELECT_PRECLK 0x0 > +#define SIRF_PWM_OE 0x4 > +#define SIRF_PWM_ENABLE_PRECLOCK 0x8 > +#define SIRF_PWM_ENABLE_POSTCLOCK 0xC > +#define SIRF_PWM_GET_WAIT_OFFSET(n) (0x10 + 0x8*n) > +#define SIRF_PWM_GET_HOLD_OFFSET(n) (0x14 + 0x8*n) > + > +#define SIRF_PWM_TR_STEP(n) (0x48 + 0x8*n) > +#define SIRF_PWM_STEP_HOLD(n) (0x4c + 0x8*n) > + > +#define SRC_FIELD_SIZE 3 > +#define BYPASS_MODE_BIT 21 > +#define TRANS_MODE_SELECT_BIT 7 > + > +#define SIRF_MAX_SRC_CLK 5 > + > +struct sirf_pwm_chip { > + struct pwm_chip chip; > + struct mutex mutex; > + void __iomem *base; > + struct clk *pwmc_clk; > +}; > + > +struct sirf_pwm { > + u32 sigsrc_clk_idx; > + struct clk *sigsrc_clk; > + u32 bypass_mode; > + u32 duty_ns; > +}; > + > +static inline struct sirf_pwm_chip *to_sirf_pwm_chip(struct pwm_chip *chip) > +{ > + return container_of(chip, struct sirf_pwm_chip, chip); > +} > + > +static int sirf_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) > +{ > + struct sirf_pwm *spwm; > + > + spwm = devm_kzalloc(chip->dev, sizeof(*spwm), GFP_KERNEL); > + if (!spwm) > + return -ENOMEM; > + > + pwm_set_chip_data(pwm, spwm); > + > + return 0; > +} > + > +static void sirf_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) > +{ > + struct sirf_pwm *spwm = pwm_get_chip_data(pwm); > + > + if (!spwm) > + devm_kfree(chip->dev, spwm); > +} > + > +static u32 sirf_pwm_ns_to_cycles(struct pwm_device *pwm, u32 time_ns) > +{ > + struct sirf_pwm *spwm = pwm_get_chip_data(pwm); > + u32 src_clk_rate = clk_get_rate(spwm->sigsrc_clk); > + u64 cycle; > + > + cycle = div_u64((u64)src_clk_rate * time_ns, NSEC_PER_SEC); > + > + return (u32)(cycle > 1 ? cycle : 1); > +} > + > +static void _sirf_pwm_hwconfig(struct pwm_chip *chip, struct pwm_device *pwm) > +{ > + u32 period_cycles, high_cycles, low_cycles; > + struct sirf_pwm_chip *spwmc = to_sirf_pwm_chip(chip); > + struct sirf_pwm *spwm = pwm_get_chip_data(pwm); > + > + period_cycles = sirf_pwm_ns_to_cycles(pwm, pwm_get_period(pwm)); > + > + /* > + * enter bypass mode, high_cycles and low_cycle > + * do not need to config if period_cycles == 1 > + */ > + if (period_cycles == 1) { > + spwm->bypass_mode = 1; the bypass mode need some comments here. or we need to add a "TODO: move to clk subsystem" here, it is actually not PWM, but a clock output now. > + } else { > + spwm->bypass_mode = 0; > + > + high_cycles = sirf_pwm_ns_to_cycles(pwm, spwm->duty_ns); > + low_cycles = period_cycles - high_cycles; > + > + /* > + * high_cycles will equal to period_cycles when duty_ns > + * is big enough, so low_cycles will be 0, > + * a wrong value will be written to register after > + * low_cycles minus 1 later. > + */ > + if (high_cycles == period_cycles) { > + high_cycles--; > + low_cycles = 1; > + } > + > + mutex_lock(&spwmc->mutex); > + > + writel(high_cycles - 1, > + spwmc->base + SIRF_PWM_GET_WAIT_OFFSET(pwm->hwpwm)); > + writel(low_cycles - 1, > + spwmc->base + SIRF_PWM_GET_HOLD_OFFSET(pwm->hwpwm)); > + > + mutex_unlock(&spwmc->mutex); > + } > +} > + > +static int sirf_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, > + int duty_ns, int period_ns) > +{ > + struct sirf_pwm_chip *spwmc = to_sirf_pwm_chip(chip); > + struct sirf_pwm *spwm = pwm_get_chip_data(pwm); > + > + if (!test_bit(PWMF_ENABLED, &pwm->flags)) { > + u32 src_clk_rate, src_clk_rate_min = ~0; > + u32 i; > + u64 cycle; > + u32 cycle_diff; > + u32 ns_diff, ns_diff_min = ~0; > + int ret; > + char src_clk_name[10]; > + struct clk *sigsrc_clk; > + > + /* > + * select a best source clock for the specific PWM clock > + * 1. select the clock with minimal error > + * 2. select the slower clock if some of them have > + * the same error > + */ > + for (i = 0; i < SIRF_MAX_SRC_CLK; i++) { > + sprintf(src_clk_name, "sigsrc%d", i); > + sigsrc_clk = devm_clk_get(chip->dev, src_clk_name); > + if (IS_ERR(sigsrc_clk)) > + continue; > + > + src_clk_rate = clk_get_rate(sigsrc_clk); > + > + cycle = (u64)src_clk_rate * period_ns; > + div_u64_rem(cycle, NSEC_PER_SEC, &cycle_diff); > + > + ns_diff = (u32)cycle_diff / src_clk_rate; > + > + if (ns_diff <= ns_diff_min && > + src_clk_rate < src_clk_rate_min) { > + ns_diff_min = ns_diff; > + src_clk_rate_min = src_clk_rate; > + spwm->sigsrc_clk_idx = i; > + spwm->sigsrc_clk = sigsrc_clk; > + } else { > + devm_clk_put(chip->dev, sigsrc_clk); > + } > + } > + > + /* > + * enable PWM before writing the register > + */ > + ret = clk_prepare_enable(spwmc->pwmc_clk); > + if (ret) > + return ret; > + } > + > + spwm->duty_ns = duty_ns; > + > + _sirf_pwm_hwconfig(chip, pwm); it looks weird if we config pwm if it has been enabled. the whole produre to select suitable clock source is not executed? and only duty_ns can be configed? > + > + /* > + * if the PWM is not enabled, turn off the clock again > + */ > + if (!test_bit(PWMF_ENABLED, &pwm->flags)) > + clk_disable_unprepare(spwmc->pwmc_clk); > + > + return 0; > +} > + > +static void _sirf_pwm_hwenable(struct pwm_chip *chip, struct pwm_device *pwm) > +{ > + struct sirf_pwm_chip *spwmc = to_sirf_pwm_chip(chip); > + struct sirf_pwm *spwm = pwm_get_chip_data(pwm); > + u32 val; > + > + mutex_lock(&spwmc->mutex); > + > + /* disable preclock */ > + val = readl(spwmc->base + SIRF_PWM_ENABLE_PRECLOCK); > + val &= ~(1 << pwm->hwpwm); > + writel(val, spwmc->base + SIRF_PWM_ENABLE_PRECLOCK); > + > + /* select preclock source must after disable preclk */ > + val = readl(spwmc->base + SIRF_PWM_SELECT_PRECLK); > + val &= ~(0x7 << (SRC_FIELD_SIZE * pwm->hwpwm)); > + val |= (spwm->sigsrc_clk_idx << (SRC_FIELD_SIZE * pwm->hwpwm)); > + > + if (spwm->bypass_mode == 1) > + val |= (0x1 << (BYPASS_MODE_BIT + pwm->hwpwm)); > + else > + val &= ~(0x1 << (BYPASS_MODE_BIT + pwm->hwpwm)); > + > + writel(val, spwmc->base + SIRF_PWM_SELECT_PRECLK); > + > + /* wait for some time */ > + usleep_range(100, 200); > + > + /* enable preclock */ > + val = readl(spwmc->base + SIRF_PWM_ENABLE_PRECLOCK); > + val |= (1 << pwm->hwpwm); > + writel(val, spwmc->base + SIRF_PWM_ENABLE_PRECLOCK); > + > + /* enable post clock*/ > + val = readl(spwmc->base + SIRF_PWM_ENABLE_POSTCLOCK); > + val |= (1 << pwm->hwpwm); > + writel(val, spwmc->base + SIRF_PWM_ENABLE_POSTCLOCK); > + > + /* enable output */ > + val = readl(spwmc->base + SIRF_PWM_OE); > + val |= 1 << pwm->hwpwm; > + val |= 1 << (pwm->hwpwm + TRANS_MODE_SELECT_BIT); > + > + writel(val, spwmc->base + SIRF_PWM_OE); > + > + mutex_unlock(&spwmc->mutex); > +} > + > +static int sirf_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) > +{ > + struct sirf_pwm_chip *spwmc = to_sirf_pwm_chip(chip); > + struct sirf_pwm *spwm = pwm_get_chip_data(pwm); > + u32 ret; > + > + ret = clk_prepare_enable(spwm->sigsrc_clk); > + if (ret) > + return ret; > + > + ret = clk_prepare_enable(spwmc->pwmc_clk); > + if (ret) > + return ret; > + > + _sirf_pwm_hwenable(chip, pwm); > + > + return 0; > +} > + > +static void sirf_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) > +{ > + u32 val; > + struct sirf_pwm_chip *spwmc = to_sirf_pwm_chip(chip); > + struct sirf_pwm *spwm = pwm_get_chip_data(pwm); > + > + mutex_lock(&spwmc->mutex); > + > + /* disable output */ > + val = readl(spwmc->base + SIRF_PWM_OE); > + val &= ~(1 << pwm->hwpwm); > + writel(val, spwmc->base + SIRF_PWM_OE); > + > + /* disable postclock */ > + val = readl(spwmc->base + SIRF_PWM_ENABLE_POSTCLOCK); > + val &= ~(1 << pwm->hwpwm); > + writel(val, spwmc->base + SIRF_PWM_ENABLE_POSTCLOCK); > + > + /* disable preclock */ > + val = readl(spwmc->base + SIRF_PWM_ENABLE_PRECLOCK); > + val &= ~(1 << pwm->hwpwm); > + writel(val, spwmc->base + SIRF_PWM_ENABLE_PRECLOCK); > + > + mutex_unlock(&spwmc->mutex); > + > + clk_disable_unprepare(spwm->sigsrc_clk); > + > + clk_disable_unprepare(spwmc->pwmc_clk); > +} > + > +static const struct pwm_ops sirf_pwm_ops = { > + .request = sirf_pwm_request, > + .free = sirf_pwm_free, > + .enable = sirf_pwm_enable, > + .disable = sirf_pwm_disable, > + .config = sirf_pwm_config, > + .owner = THIS_MODULE, > +}; > + > +static int sirf_pwm_probe(struct platform_device *pdev) > +{ > + struct sirf_pwm_chip *spwmc; > + struct resource *mem_res; > + int ret; > + > + spwmc = devm_kzalloc(&pdev->dev, sizeof(*spwmc), > + GFP_KERNEL); > + if (!spwmc) > + return -ENOMEM; > + > + platform_set_drvdata(pdev, spwmc); > + > + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + spwmc->base = devm_ioremap_resource(&pdev->dev, mem_res); > + if (IS_ERR(spwmc->base)) > + return PTR_ERR(spwmc->base); > + > + /* > + * get clock for PWM controller > + */ > + spwmc->pwmc_clk = devm_clk_get(&pdev->dev, "pwmc"); > + if (IS_ERR(spwmc->pwmc_clk)) { > + dev_err(&pdev->dev, "failed to get PWM controller clock\n"); > + return PTR_ERR(spwmc->pwmc_clk); > + } > + > + spwmc->chip.dev = &pdev->dev; > + spwmc->chip.ops = &sirf_pwm_ops; > + spwmc->chip.base = -1; > + spwmc->chip.npwm = 7; > + > + mutex_init(&spwmc->mutex); > + > + ret = pwmchip_add(&spwmc->chip); > + if (ret < 0) { > + dev_err(&pdev->dev, "failed to register PWM chip\n"); > + return ret; > + } > + > + return 0; > +} > + > +static int sirf_pwm_remove(struct platform_device *pdev) > +{ > + struct sirf_pwm_chip *spwmc = platform_get_drvdata(pdev); > + > + return pwmchip_remove(&spwmc->chip); > +} > + > +#ifdef CONFIG_PM_SLEEP > +static int sirf_pwm_suspend(struct device *dev) > +{ > + struct sirf_pwm_chip *spwmc = dev_get_drvdata(dev); > + struct pwm_device *pwm; > + int i; > + > + for (i = 0; i < spwmc->chip.npwm; i++) { > + pwm = &spwmc->chip.pwms[i]; > + /* > + * disable PWM which is not disabled when user suspend > + */ > + if (test_bit(PWMF_REQUESTED, &pwm->flags) && > + test_bit(PWMF_ENABLED, &pwm->flags)) > + sirf_pwm_disable(pwm->chip, pwm); > + } > + > + return 0; > +} > + > +static int sirf_pwm_resume(struct device *dev) > +{ > + struct sirf_pwm_chip *spwmc = dev_get_drvdata(dev); > + struct pwm_device *pwm; > + struct sirf_pwm *spwm; > + int i; > + > + for (i = 0; i < spwmc->chip.npwm; i++) { > + pwm = &spwmc->chip.pwms[i]; > + spwm = pwm_get_chip_data(pwm); > + > + if (test_bit(PWMF_REQUESTED, &pwm->flags) && > + test_bit(PWMF_ENABLED, &pwm->flags)) { > + sirf_pwm_config(&spwmc->chip, pwm, spwm->duty_ns, > + pwm_get_period(pwm)); > + sirf_pwm_enable(&spwmc->chip, pwm); > + } > + } > + > + return 0; > +} > + > +static int sirf_pwm_restore(struct device *dev) > +{ > + struct sirf_pwm_chip *spwmc = dev_get_drvdata(dev); > + struct pwm_device *pwm; > + struct sirf_pwm *spwm; > + int i; > + > + for (i = 0; i < spwmc->chip.npwm; i++) { > + pwm = &spwmc->chip.pwms[i]; > + spwm = pwm_get_chip_data(pwm); > + /* > + * while restoring from hibernation, state of PWM is enabled, > + * but PWM hardware is not re-enabled, register about config > + * and enable should be restored here > + */ > + if (test_bit(PWMF_REQUESTED, &pwm->flags) && > + test_bit(PWMF_ENABLED, &pwm->flags)) { > + _sirf_pwm_hwconfig(&spwmc->chip, pwm); > + _sirf_pwm_hwenable(&spwmc->chip, pwm); > + } > + } > + > + return 0; > +} > +#else > +#define sirf_pwm_resume NULL > +#define sirf_pwm_suspend NULL > +#define sirf_pwm_restore NULL > +#endif > + > +static const struct dev_pm_ops sirf_pwm_pm_ops = { > + .suspend = sirf_pwm_suspend, > + .resume = sirf_pwm_resume, > + .restore = sirf_pwm_restore, > +}; > + > +static const struct of_device_id sirf_pwm_of_match[] = { > + { .compatible = "sirf,prima2-pwm", }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, sirf_pwm_of_match); > + > +static struct platform_driver sirf_pwm_driver = { > + .driver = { > + .name = "sirf-pwm", > + .pm = &sirf_pwm_pm_ops, > + .of_match_table = sirf_pwm_of_match, > + }, > + .probe = sirf_pwm_probe, > + .remove = sirf_pwm_remove, > +}; > +module_platform_driver(sirf_pwm_driver); > + > +MODULE_DESCRIPTION("SIRF serial SoC PWM device core driver"); > +MODULE_AUTHOR("RongJun Ying <Rongjun.Ying@xxxxxxx>"); > +MODULE_AUTHOR("Huayi Li <huayi.li@xxxxxxx>"); > +MODULE_LICENSE("GPL v2"); > -- > 1.9.3 -barry -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html