Re: [PATCH v5 1/4] pwm: add CSR SiRFSoc PWM driver

From: Barry Song
Date: Thu Jul 24 2014 - 06:04:19 EST


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 linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/