diff options
Diffstat (limited to 'drivers/pwm')
-rw-r--r-- | drivers/pwm/Kconfig | 41 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 4 | ||||
-rw-r--r-- | drivers/pwm/core.c | 27 | ||||
-rw-r--r-- | drivers/pwm/pwm-atmel-tcb.c | 2 | ||||
-rw-r--r-- | drivers/pwm/pwm-atmel.c | 24 | ||||
-rw-r--r-- | drivers/pwm/pwm-fsl-ftm.c | 90 | ||||
-rw-r--r-- | drivers/pwm/pwm-imx.c | 72 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpss-pci.c | 64 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpss-platform.c | 68 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpss.c | 151 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpss.h | 32 | ||||
-rw-r--r-- | drivers/pwm/pwm-rockchip.c | 303 | ||||
-rw-r--r-- | drivers/pwm/pwm-sti.c | 418 | ||||
-rw-r--r-- | drivers/pwm/pwm-tipwmss.c | 4 |
14 files changed, 1076 insertions, 224 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 4ad7b89a4cb4..ef2dd2e4754b 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -43,7 +43,7 @@ config PWM_AB8500 config PWM_ATMEL tristate "Atmel PWM support" - depends on ARCH_AT91 + depends on ARCH_AT91 || AVR32 help Generic PWM framework driver for Atmel SoC. @@ -83,6 +83,7 @@ config PWM_BFIN config PWM_CLPS711X tristate "CLPS711X PWM support" depends on ARCH_CLPS711X || COMPILE_TEST + depends on HAS_IOMEM help Generic PWM framework driver for Cirrus Logic CLPS711X. @@ -101,6 +102,7 @@ config PWM_EP93XX config PWM_FSL_FTM tristate "Freescale FlexTimer Module (FTM) PWM support" depends on OF + select REGMAP_MMIO help Generic FTM PWM framework driver for Freescale VF610 and Layerscape LS-1 SoCs. @@ -149,7 +151,7 @@ config PWM_LPC32XX config PWM_LPSS tristate "Intel LPSS PWM support" - depends on ACPI + depends on X86 help Generic PWM framework driver for Intel Low Power Subsystem PWM controller. @@ -157,6 +159,24 @@ config PWM_LPSS To compile this driver as a module, choose M here: the module will be called pwm-lpss. +config PWM_LPSS_PCI + tristate "Intel LPSS PWM PCI driver" + depends on PWM_LPSS && PCI + help + The PCI driver for Intel Low Power Subsystem PWM controller. + + To compile this driver as a module, choose M here: the module + will be called pwm-lpss-pci. + +config PWM_LPSS_PLATFORM + tristate "Intel LPSS PWM platform driver" + depends on PWM_LPSS && ACPI + help + The platform driver for Intel Low Power Subsystem PWM controller. + + To compile this driver as a module, choose M here: the module + will be called pwm-lpss-platform. + config PWM_MXS tristate "Freescale MXS PWM support" depends on ARCH_MXS && OF @@ -206,6 +226,13 @@ config PWM_RENESAS_TPU To compile this driver as a module, choose M here: the module will be called pwm-renesas-tpu. +config PWM_ROCKCHIP + tristate "Rockchip PWM support" + depends on ARCH_ROCKCHIP + help + Generic PWM framework driver for the PWM controller found on + Rockchip SoCs. + config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG @@ -226,6 +253,16 @@ config PWM_SPEAR To compile this driver as a module, choose M here: the module will be called pwm-spear. +config PWM_STI + tristate "STiH4xx PWM support" + depends on ARCH_STI + depends on OF + help + Generic PWM framework driver for STiH4xx SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-sti. + config PWM_TEGRA tristate "NVIDIA Tegra PWM support" depends on ARCH_TEGRA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 5c86a19d5d39..c458606c3755 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -13,13 +13,17 @@ obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o +obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o +obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.o obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o 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_ROCKCHIP) += pwm-rockchip.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o +obj-$(CONFIG_PWM_STI) += pwm-sti.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 4b66bf09ee55..966497d10c6e 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -236,7 +236,7 @@ int pwmchip_add(struct pwm_chip *chip) int ret; if (!chip || !chip->dev || !chip->ops || !chip->ops->config || - !chip->ops->enable || !chip->ops->disable) + !chip->ops->enable || !chip->ops->disable || !chip->npwm) return -EINVAL; mutex_lock(&pwm_lock); @@ -602,9 +602,8 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id) struct pwm_device *pwm = ERR_PTR(-EPROBE_DEFER); const char *dev_id = dev ? dev_name(dev) : NULL; struct pwm_chip *chip = NULL; - unsigned int index = 0; unsigned int best = 0; - struct pwm_lookup *p; + struct pwm_lookup *p, *chosen = NULL; unsigned int match; /* look up via DT first */ @@ -651,8 +650,7 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id) } if (match > best) { - chip = pwmchip_find_by_name(p->provider); - index = p->index; + chosen = p; if (match != 3) best = match; @@ -661,17 +659,22 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id) } } - mutex_unlock(&pwm_lookup_lock); + if (!chosen) + goto out; - if (chip) - pwm = pwm_request_from_chip(chip, index, con_id ?: dev_id); - if (IS_ERR(pwm)) - return pwm; + chip = pwmchip_find_by_name(chosen->provider); + if (!chip) + goto out; - pwm_set_period(pwm, p->period); - pwm_set_polarity(pwm, p->polarity); + pwm = pwm_request_from_chip(chip, chosen->index, con_id ?: dev_id); + if (IS_ERR(pwm)) + goto out; + pwm_set_period(pwm, chosen->period); + pwm_set_polarity(pwm, chosen->polarity); +out: + mutex_unlock(&pwm_lookup_lock); return pwm; } EXPORT_SYMBOL_GPL(pwm_get); diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c index f3dcd02390f1..d56e5b717431 100644 --- a/drivers/pwm/pwm-atmel-tcb.c +++ b/drivers/pwm/pwm-atmel-tcb.c @@ -379,7 +379,7 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev) return err; } - tc = atmel_tc_alloc(tcblock, "tcb-pwm"); + tc = atmel_tc_alloc(tcblock); if (tc == NULL) { dev_err(&pdev->dev, "failed to allocate Timer Counter Block\n"); return -ENOMEM; diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c index 6e700a541ca3..d3c22de9ee47 100644 --- a/drivers/pwm/pwm-atmel.c +++ b/drivers/pwm/pwm-atmel.c @@ -102,7 +102,7 @@ static int atmel_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); - unsigned long clk_rate, prd, dty; + unsigned long prd, dty; unsigned long long div; unsigned int pres = 0; u32 val; @@ -113,20 +113,18 @@ static int atmel_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, return -EBUSY; } - clk_rate = clk_get_rate(atmel_pwm->clk); - div = clk_rate; + /* Calculate the period cycles and prescale value */ + div = (unsigned long long)clk_get_rate(atmel_pwm->clk) * period_ns; + do_div(div, NSEC_PER_SEC); - /* Calculate the period cycles */ while (div > PWM_MAX_PRD) { - div = clk_rate / (1 << pres); - div = div * period_ns; - /* 1/Hz = 100000000 ns */ - do_div(div, 1000000000); - - if (pres++ > PRD_MAX_PRES) { - dev_err(chip->dev, "pres exceeds the maximum value\n"); - return -EINVAL; - } + div >>= 1; + pres++; + } + + if (pres > PRD_MAX_PRES) { + dev_err(chip->dev, "pres exceeds the maximum value\n"); + return -EINVAL; } /* Calculate the duty cycles */ diff --git a/drivers/pwm/pwm-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c index a18bc8fea385..0f2cc7ef7784 100644 --- a/drivers/pwm/pwm-fsl-ftm.c +++ b/drivers/pwm/pwm-fsl-ftm.c @@ -18,14 +18,14 @@ #include <linux/of_address.h> #include <linux/platform_device.h> #include <linux/pwm.h> +#include <linux/regmap.h> #include <linux/slab.h> #define FTM_SC 0x00 -#define FTM_SC_CLK_MASK 0x3 -#define FTM_SC_CLK_SHIFT 3 -#define FTM_SC_CLK(c) (((c) + 1) << FTM_SC_CLK_SHIFT) +#define FTM_SC_CLK_MASK_SHIFT 3 +#define FTM_SC_CLK_MASK (3 << FTM_SC_CLK_MASK_SHIFT) +#define FTM_SC_CLK(c) (((c) + 1) << FTM_SC_CLK_MASK_SHIFT) #define FTM_SC_PS_MASK 0x7 -#define FTM_SC_PS_SHIFT 0 #define FTM_CNT 0x04 #define FTM_MOD 0x08 @@ -83,7 +83,7 @@ struct fsl_pwm_chip { unsigned int cnt_select; unsigned int clk_ps; - void __iomem *base; + struct regmap *regmap; int period_ns; @@ -219,10 +219,11 @@ static unsigned long fsl_pwm_calculate_duty(struct fsl_pwm_chip *fpc, unsigned long period_ns, unsigned long duty_ns) { - unsigned long long val, duty; + unsigned long long duty; + u32 val; - val = readl(fpc->base + FTM_MOD); - duty = duty_ns * (val + 1); + regmap_read(fpc->regmap, FTM_MOD, &val); + duty = (unsigned long long)duty_ns * (val + 1); do_div(duty, period_ns); return (unsigned long)duty; @@ -232,7 +233,7 @@ static int fsl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct fsl_pwm_chip *fpc = to_fsl_chip(chip); - u32 val, period, duty; + u32 period, duty; mutex_lock(&fpc->lock); @@ -257,11 +258,9 @@ static int fsl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, return -EINVAL; } - val = readl(fpc->base + FTM_SC); - val &= ~(FTM_SC_PS_MASK << FTM_SC_PS_SHIFT); - val |= fpc->clk_ps; - writel(val, fpc->base + FTM_SC); - writel(period - 1, fpc->base + FTM_MOD); + regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_PS_MASK, + fpc->clk_ps); + regmap_write(fpc->regmap, FTM_MOD, period - 1); fpc->period_ns = period_ns; } @@ -270,8 +269,9 @@ static int fsl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, duty = fsl_pwm_calculate_duty(fpc, period_ns, duty_ns); - writel(FTM_CSC_MSB | FTM_CSC_ELSB, fpc->base + FTM_CSC(pwm->hwpwm)); - writel(duty, fpc->base + FTM_CV(pwm->hwpwm)); + regmap_write(fpc->regmap, FTM_CSC(pwm->hwpwm), + FTM_CSC_MSB | FTM_CSC_ELSB); + regmap_write(fpc->regmap, FTM_CV(pwm->hwpwm), duty); return 0; } @@ -283,31 +283,28 @@ static int fsl_pwm_set_polarity(struct pwm_chip *chip, struct fsl_pwm_chip *fpc = to_fsl_chip(chip); u32 val; - val = readl(fpc->base + FTM_POL); + regmap_read(fpc->regmap, FTM_POL, &val); if (polarity == PWM_POLARITY_INVERSED) val |= BIT(pwm->hwpwm); else val &= ~BIT(pwm->hwpwm); - writel(val, fpc->base + FTM_POL); + regmap_write(fpc->regmap, FTM_POL, val); return 0; } static int fsl_counter_clock_enable(struct fsl_pwm_chip *fpc) { - u32 val; int ret; if (fpc->use_count != 0) return 0; /* select counter clock source */ - val = readl(fpc->base + FTM_SC); - val &= ~(FTM_SC_CLK_MASK << FTM_SC_CLK_SHIFT); - val |= FTM_SC_CLK(fpc->cnt_select); - writel(val, fpc->base + FTM_SC); + regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK, + FTM_SC_CLK(fpc->cnt_select)); ret = clk_prepare_enable(fpc->clk[fpc->cnt_select]); if (ret) @@ -327,13 +324,10 @@ static int fsl_counter_clock_enable(struct fsl_pwm_chip *fpc) static int fsl_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct fsl_pwm_chip *fpc = to_fsl_chip(chip); - u32 val; int ret; mutex_lock(&fpc->lock); - val = readl(fpc->base + FTM_OUTMASK); - val &= ~BIT(pwm->hwpwm); - writel(val, fpc->base + FTM_OUTMASK); + regmap_update_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm), 0); ret = fsl_counter_clock_enable(fpc); mutex_unlock(&fpc->lock); @@ -343,8 +337,6 @@ static int fsl_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) static void fsl_counter_clock_disable(struct fsl_pwm_chip *fpc) { - u32 val; - /* * already disabled, do nothing */ @@ -356,9 +348,7 @@ static void fsl_counter_clock_disable(struct fsl_pwm_chip *fpc) return; /* no users left, disable PWM counter clock */ - val = readl(fpc->base + FTM_SC); - val &= ~(FTM_SC_CLK_MASK << FTM_SC_CLK_SHIFT); - writel(val, fpc->base + FTM_SC); + regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK, 0); clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]); clk_disable_unprepare(fpc->clk[fpc->cnt_select]); @@ -370,14 +360,12 @@ static void fsl_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) u32 val; mutex_lock(&fpc->lock); - val = readl(fpc->base + FTM_OUTMASK); - val |= BIT(pwm->hwpwm); - writel(val, fpc->base + FTM_OUTMASK); + regmap_update_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm), + BIT(pwm->hwpwm)); fsl_counter_clock_disable(fpc); - val = readl(fpc->base + FTM_OUTMASK); - + regmap_read(fpc->regmap, FTM_OUTMASK, &val); if ((val & 0xFF) == 0xFF) fpc->period_ns = 0; @@ -402,19 +390,28 @@ static int fsl_pwm_init(struct fsl_pwm_chip *fpc) if (ret) return ret; - writel(0x00, fpc->base + FTM_CNTIN); - writel(0x00, fpc->base + FTM_OUTINIT); - writel(0xFF, fpc->base + FTM_OUTMASK); + regmap_write(fpc->regmap, FTM_CNTIN, 0x00); + regmap_write(fpc->regmap, FTM_OUTINIT, 0x00); + regmap_write(fpc->regmap, FTM_OUTMASK, 0xFF); clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_SYS]); return 0; } +static const struct regmap_config fsl_pwm_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = FTM_PWMLOAD, +}; + static int fsl_pwm_probe(struct platform_device *pdev) { struct fsl_pwm_chip *fpc; struct resource *res; + void __iomem *base; int ret; fpc = devm_kzalloc(&pdev->dev, sizeof(*fpc), GFP_KERNEL); @@ -426,9 +423,16 @@ static int fsl_pwm_probe(struct platform_device *pdev) fpc->chip.dev = &pdev->dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - fpc->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(fpc->base)) - return PTR_ERR(fpc->base); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + fpc->regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, base, + &fsl_pwm_regmap_config); + if (IS_ERR(fpc->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(fpc->regmap); + } fpc->clk[FSL_PWM_CLK_SYS] = devm_clk_get(&pdev->dev, "ftm_sys"); if (IS_ERR(fpc->clk[FSL_PWM_CLK_SYS])) { diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index d797c7b84c3f..f8b5f109c1ab 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -14,6 +14,7 @@ #include <linux/slab.h> #include <linux/err.h> #include <linux/clk.h> +#include <linux/delay.h> #include <linux/io.h> #include <linux/pwm.h> #include <linux/of.h> @@ -21,24 +22,30 @@ /* i.MX1 and i.MX21 share the same PWM function block: */ -#define MX1_PWMC 0x00 /* PWM Control Register */ -#define MX1_PWMS 0x04 /* PWM Sample Register */ -#define MX1_PWMP 0x08 /* PWM Period Register */ +#define MX1_PWMC 0x00 /* PWM Control Register */ +#define MX1_PWMS 0x04 /* PWM Sample Register */ +#define MX1_PWMP 0x08 /* PWM Period Register */ -#define MX1_PWMC_EN (1 << 4) +#define MX1_PWMC_EN (1 << 4) /* i.MX27, i.MX31, i.MX35 share the same PWM function block: */ -#define MX3_PWMCR 0x00 /* PWM Control Register */ -#define MX3_PWMSAR 0x0C /* PWM Sample Register */ -#define MX3_PWMPR 0x10 /* PWM Period Register */ -#define MX3_PWMCR_PRESCALER(x) (((x - 1) & 0xFFF) << 4) -#define MX3_PWMCR_DOZEEN (1 << 24) -#define MX3_PWMCR_WAITEN (1 << 23) +#define MX3_PWMCR 0x00 /* PWM Control Register */ +#define MX3_PWMSR 0x04 /* PWM Status Register */ +#define MX3_PWMSAR 0x0C /* PWM Sample Register */ +#define MX3_PWMPR 0x10 /* PWM Period Register */ +#define MX3_PWMCR_PRESCALER(x) ((((x) - 1) & 0xFFF) << 4) +#define MX3_PWMCR_DOZEEN (1 << 24) +#define MX3_PWMCR_WAITEN (1 << 23) #define MX3_PWMCR_DBGEN (1 << 22) -#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16) -#define MX3_PWMCR_CLKSRC_IPG (1 << 16) -#define MX3_PWMCR_EN (1 << 0) +#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16) +#define MX3_PWMCR_CLKSRC_IPG (1 << 16) +#define MX3_PWMCR_SWR (1 << 3) +#define MX3_PWMCR_EN (1 << 0) +#define MX3_PWMSR_FIFOAV_4WORDS 0x4 +#define MX3_PWMSR_FIFOAV_MASK 0x7 + +#define MX3_PWM_SWR_LOOP 5 struct imx_chip { struct clk *clk_per; @@ -103,9 +110,43 @@ static int imx_pwm_config_v2(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct imx_chip *imx = to_imx_chip(chip); + struct device *dev = chip->dev; unsigned long long c; unsigned long period_cycles, duty_cycles, prescale; - u32 cr; + unsigned int period_ms; + bool enable = test_bit(PWMF_ENABLED, &pwm->flags); + int wait_count = 0, fifoav; + u32 cr, sr; + + /* + * i.MX PWMv2 has a 4-word sample FIFO. + * In order to avoid FIFO overflow issue, we do software reset + * to clear all sample FIFO if the controller is disabled or + * wait for a full PWM cycle to get a relinquished FIFO slot + * when the controller is enabled and the FIFO is fully loaded. + */ + if (enable) { + sr = readl(imx->mmio_base + MX3_PWMSR); + fifoav = sr & MX3_PWMSR_FIFOAV_MASK; + if (fifoav == MX3_PWMSR_FIFOAV_4WORDS) { + period_ms = DIV_ROUND_UP(pwm->period, NSEC_PER_MSEC); + msleep(period_ms); + + sr = readl(imx->mmio_base + MX3_PWMSR); + if (fifoav == (sr & MX3_PWMSR_FIFOAV_MASK)) + dev_warn(dev, "there is no free FIFO slot\n"); + } + } else { + writel(MX3_PWMCR_SWR, imx->mmio_base + MX3_PWMCR); + do { + usleep_range(200, 1000); + cr = readl(imx->mmio_base + MX3_PWMCR); + } while ((cr & MX3_PWMCR_SWR) && + (wait_count++ < MX3_PWM_SWR_LOOP)); + + if (cr & MX3_PWMCR_SWR) + dev_warn(dev, "software reset timeout\n"); + } c = clk_get_rate(imx->clk_per); c = c * period_ns; @@ -135,7 +176,7 @@ static int imx_pwm_config_v2(struct pwm_chip *chip, MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH; - if (test_bit(PWMF_ENABLED, &pwm->flags)) + if (enable) cr |= MX3_PWMCR_EN; writel(cr, imx->mmio_base + MX3_PWMCR); @@ -262,6 +303,7 @@ static int imx_pwm_probe(struct platform_device *pdev) imx->chip.dev = &pdev->dev; imx->chip.base = -1; imx->chip.npwm = 1; + imx->chip.can_sleep = true; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); imx->mmio_base = devm_ioremap_resource(&pdev->dev, r); diff --git a/drivers/pwm/pwm-lpss-pci.c b/drivers/pwm/pwm-lpss-pci.c new file mode 100644 index 000000000000..cf20d2beacdd --- /dev/null +++ b/drivers/pwm/pwm-lpss-pci.c @@ -0,0 +1,64 @@ +/* + * Intel Low Power Subsystem PWM controller PCI driver + * + * Copyright (C) 2014, Intel Corporation + * + * Derived from the original pwm-lpss.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> + +#include "pwm-lpss.h" + +static int pwm_lpss_probe_pci(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + const struct pwm_lpss_boardinfo *info; + struct pwm_lpss_chip *lpwm; + int err; + + err = pcim_enable_device(pdev); + if (err < 0) + return err; + + info = (struct pwm_lpss_boardinfo *)id->driver_data; + lpwm = pwm_lpss_probe(&pdev->dev, &pdev->resource[0], info); + if (IS_ERR(lpwm)) + return PTR_ERR(lpwm); + + pci_set_drvdata(pdev, lpwm); + return 0; +} + +static void pwm_lpss_remove_pci(struct pci_dev *pdev) +{ + struct pwm_lpss_chip *lpwm = pci_get_drvdata(pdev); + + pwm_lpss_remove(lpwm); +} + +static const struct pci_device_id pwm_lpss_pci_ids[] = { + { PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&pwm_lpss_byt_info}, + { PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&pwm_lpss_byt_info}, + { PCI_VDEVICE(INTEL, 0x2288), (unsigned long)&pwm_lpss_bsw_info}, + { PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info}, + { }, +}; +MODULE_DEVICE_TABLE(pci, pwm_lpss_pci_ids); + +static struct pci_driver pwm_lpss_driver_pci = { + .name = "pwm-lpss", + .id_table = pwm_lpss_pci_ids, + .probe = pwm_lpss_probe_pci, + .remove = pwm_lpss_remove_pci, +}; +module_pci_driver(pwm_lpss_driver_pci); + +MODULE_DESCRIPTION("PWM PCI driver for Intel LPSS"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-lpss-platform.c b/drivers/pwm/pwm-lpss-platform.c new file mode 100644 index 000000000000..18a9c880a76d --- /dev/null +++ b/drivers/pwm/pwm-lpss-platform.c @@ -0,0 +1,68 @@ +/* + * Intel Low Power Subsystem PWM controller driver + * + * Copyright (C) 2014, Intel Corporation + * + * Derived from the original pwm-lpss.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/acpi.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "pwm-lpss.h" + +static int pwm_lpss_probe_platform(struct platform_device *pdev) +{ + const struct pwm_lpss_boardinfo *info; + const struct acpi_device_id *id; + struct pwm_lpss_chip *lpwm; + struct resource *r; + + id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev); + if (!id) + return -ENODEV; + + info = (const struct pwm_lpss_boardinfo *)id->driver_data; + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + lpwm = pwm_lpss_probe(&pdev->dev, r, info); + if (IS_ERR(lpwm)) + return PTR_ERR(lpwm); + + platform_set_drvdata(pdev, lpwm); + return 0; +} + +static int pwm_lpss_remove_platform(struct platform_device *pdev) +{ + struct pwm_lpss_chip *lpwm = platform_get_drvdata(pdev); + + return pwm_lpss_remove(lpwm); +} + +static const struct acpi_device_id pwm_lpss_acpi_match[] = { + { "80860F09", (unsigned long)&pwm_lpss_byt_info }, + { "80862288", (unsigned long)&pwm_lpss_bsw_info }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, pwm_lpss_acpi_match); + +static struct platform_driver pwm_lpss_driver_platform = { + .driver = { + .name = "pwm-lpss", + .acpi_match_table = pwm_lpss_acpi_match, + }, + .probe = pwm_lpss_probe_platform, + .remove = pwm_lpss_remove_platform, +}; +module_platform_driver(pwm_lpss_driver_platform); + +MODULE_DESCRIPTION("PWM platform driver for Intel LPSS"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:pwm-lpss"); diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 44ce6c6103ae..e9798253a16f 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -13,16 +13,11 @@ * published by the Free Software Foundation. */ -#include <linux/acpi.h> -#include <linux/clk.h> -#include <linux/device.h> +#include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/pwm.h> -#include <linux/platform_device.h> -#include <linux/pci.h> -static int pci_drv, plat_drv; /* So we know which drivers registered */ +#include "pwm-lpss.h" #define PWM 0x00000000 #define PWM_ENABLE BIT(31) @@ -37,18 +32,20 @@ static int pci_drv, plat_drv; /* So we know which drivers registered */ struct pwm_lpss_chip { struct pwm_chip chip; void __iomem *regs; - struct clk *clk; unsigned long clk_rate; }; -struct pwm_lpss_boardinfo { - unsigned long clk_rate; +/* BayTrail */ +const struct pwm_lpss_boardinfo pwm_lpss_byt_info = { + .clk_rate = 25000000 }; +EXPORT_SYMBOL_GPL(pwm_lpss_byt_info); -/* BayTrail */ -static const struct pwm_lpss_boardinfo byt_info = { - 25000000 +/* Braswell */ +const struct pwm_lpss_boardinfo pwm_lpss_bsw_info = { + .clk_rate = 19200000 }; +EXPORT_SYMBOL_GPL(pwm_lpss_bsw_info); static inline struct pwm_lpss_chip *to_lpwm(struct pwm_chip *chip) { @@ -97,11 +94,6 @@ static int pwm_lpss_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct pwm_lpss_chip *lpwm = to_lpwm(chip); u32 ctrl; - int ret; - - ret = clk_prepare_enable(lpwm->clk); - if (ret) - return ret; ctrl = readl(lpwm->regs + PWM); writel(ctrl | PWM_ENABLE, lpwm->regs + PWM); @@ -116,8 +108,6 @@ static void pwm_lpss_disable(struct pwm_chip *chip, struct pwm_device *pwm) ctrl = readl(lpwm->regs + PWM); writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM); - - clk_disable_unprepare(lpwm->clk); } static const struct pwm_ops pwm_lpss_ops = { @@ -127,9 +117,8 @@ static const struct pwm_ops pwm_lpss_ops = { .owner = THIS_MODULE, }; -static struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, - struct resource *r, - const struct pwm_lpss_boardinfo *info) +struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, + const struct pwm_lpss_boardinfo *info) { struct pwm_lpss_chip *lpwm; int ret; @@ -142,17 +131,7 @@ static struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, if (IS_ERR(lpwm->regs)) return ERR_CAST(lpwm->regs); - if (info) { - lpwm->clk_rate = info->clk_rate; - } else { - lpwm->clk = devm_clk_get(dev, NULL); - if (IS_ERR(lpwm->clk)) { - dev_err(dev, "failed to get PWM clock\n"); - return ERR_CAST(lpwm->clk); - } - lpwm->clk_rate = clk_get_rate(lpwm->clk); - } - + lpwm->clk_rate = info->clk_rate; lpwm->chip.dev = dev; lpwm->chip.ops = &pwm_lpss_ops; lpwm->chip.base = -1; @@ -166,8 +145,9 @@ static struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, return lpwm; } +EXPORT_SYMBOL_GPL(pwm_lpss_probe); -static int pwm_lpss_remove(struct pwm_lpss_chip *lpwm) +int pwm_lpss_remove(struct pwm_lpss_chip *lpwm) { u32 ctrl; @@ -176,107 +156,8 @@ static int pwm_lpss_remove(struct pwm_lpss_chip *lpwm) return pwmchip_remove(&lpwm->chip); } - -static int pwm_lpss_probe_pci(struct pci_dev *pdev, - const struct pci_device_id *id) -{ - const struct pwm_lpss_boardinfo *info; - struct pwm_lpss_chip *lpwm; - int err; - - err = pci_enable_device(pdev); - if (err < 0) - return err; - - info = (struct pwm_lpss_boardinfo *)id->driver_data; - lpwm = pwm_lpss_probe(&pdev->dev, &pdev->resource[0], info); - if (IS_ERR(lpwm)) - return PTR_ERR(lpwm); - - pci_set_drvdata(pdev, lpwm); - return 0; -} - -static void pwm_lpss_remove_pci(struct pci_dev *pdev) -{ - struct pwm_lpss_chip *lpwm = pci_get_drvdata(pdev); - - pwm_lpss_remove(lpwm); - pci_disable_device(pdev); -} - -static struct pci_device_id pwm_lpss_pci_ids[] = { - { PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&byt_info}, - { PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&byt_info}, - { }, -}; -MODULE_DEVICE_TABLE(pci, pwm_lpss_pci_ids); - -static struct pci_driver pwm_lpss_driver_pci = { - .name = "pwm-lpss", - .id_table = pwm_lpss_pci_ids, - .probe = pwm_lpss_probe_pci, - .remove = pwm_lpss_remove_pci, -}; - -static int pwm_lpss_probe_platform(struct platform_device *pdev) -{ - struct pwm_lpss_chip *lpwm; - struct resource *r; - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - lpwm = pwm_lpss_probe(&pdev->dev, r, NULL); - if (IS_ERR(lpwm)) - return PTR_ERR(lpwm); - - platform_set_drvdata(pdev, lpwm); - return 0; -} - -static int pwm_lpss_remove_platform(struct platform_device *pdev) -{ - struct pwm_lpss_chip *lpwm = platform_get_drvdata(pdev); - - return pwm_lpss_remove(lpwm); -} - -static const struct acpi_device_id pwm_lpss_acpi_match[] = { - { "80860F09", 0 }, - { }, -}; -MODULE_DEVICE_TABLE(acpi, pwm_lpss_acpi_match); - -static struct platform_driver pwm_lpss_driver_platform = { - .driver = { - .name = "pwm-lpss", - .acpi_match_table = pwm_lpss_acpi_match, - }, - .probe = pwm_lpss_probe_platform, - .remove = pwm_lpss_remove_platform, -}; - -static int __init pwm_init(void) -{ - pci_drv = pci_register_driver(&pwm_lpss_driver_pci); - plat_drv = platform_driver_register(&pwm_lpss_driver_platform); - if (pci_drv && plat_drv) - return pci_drv; - - return 0; -} -module_init(pwm_init); - -static void __exit pwm_exit(void) -{ - if (!pci_drv) - pci_unregister_driver(&pwm_lpss_driver_pci); - if (!plat_drv) - platform_driver_unregister(&pwm_lpss_driver_platform); -} -module_exit(pwm_exit); +EXPORT_SYMBOL_GPL(pwm_lpss_remove); MODULE_DESCRIPTION("PWM driver for Intel LPSS"); MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:pwm-lpss"); diff --git a/drivers/pwm/pwm-lpss.h b/drivers/pwm/pwm-lpss.h new file mode 100644 index 000000000000..aa041bb1b67d --- /dev/null +++ b/drivers/pwm/pwm-lpss.h @@ -0,0 +1,32 @@ +/* + * Intel Low Power Subsystem PWM controller driver + * + * Copyright (C) 2014, Intel Corporation + * + * Derived from the original pwm-lpss.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __PWM_LPSS_H +#define __PWM_LPSS_H + +#include <linux/device.h> +#include <linux/pwm.h> + +struct pwm_lpss_chip; + +struct pwm_lpss_boardinfo { + unsigned long clk_rate; +}; + +extern const struct pwm_lpss_boardinfo pwm_lpss_byt_info; +extern const struct pwm_lpss_boardinfo pwm_lpss_bsw_info; + +struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, + const struct pwm_lpss_boardinfo *info); +int pwm_lpss_remove(struct pwm_lpss_chip *lpwm); + +#endif /* __PWM_LPSS_H */ diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c new file mode 100644 index 000000000000..9442df244101 --- /dev/null +++ b/drivers/pwm/pwm-rockchip.c @@ -0,0 +1,303 @@ +/* + * PWM driver for Rockchip SoCs + * + * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com> + * Copyright (C) 2014 ROCKCHIP, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/time.h> + +#define PWM_CTRL_TIMER_EN (1 << 0) +#define PWM_CTRL_OUTPUT_EN (1 << 3) + +#define PWM_ENABLE (1 << 0) +#define PWM_CONTINUOUS (1 << 1) +#define PWM_DUTY_POSITIVE (1 << 3) +#define PWM_DUTY_NEGATIVE (0 << 3) +#define PWM_INACTIVE_NEGATIVE (0 << 4) +#define PWM_INACTIVE_POSITIVE (1 << 4) +#define PWM_OUTPUT_LEFT (0 << 5) +#define PWM_LP_DISABLE (0 << 8) + +struct rockchip_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + const struct rockchip_pwm_data *data; + void __iomem *base; +}; + +struct rockchip_pwm_regs { + unsigned long duty; + unsigned long period; + unsigned long cntr; + unsigned long ctrl; +}; + +struct rockchip_pwm_data { + struct rockchip_pwm_regs regs; + unsigned int prescaler; + const struct pwm_ops *ops; + + void (*set_enable)(struct pwm_chip *chip, + struct pwm_device *pwm, bool enable); +}; + +static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c) +{ + return container_of(c, struct rockchip_pwm_chip, chip); +} + +static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip, + struct pwm_device *pwm, bool enable) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN; + u32 val; + + val = readl_relaxed(pc->base + pc->data->regs.ctrl); + + if (enable) + val |= enable_conf; + else + val &= ~enable_conf; + + writel_relaxed(val, pc->base + pc->data->regs.ctrl); +} + +static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip, + struct pwm_device *pwm, bool enable) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | + PWM_CONTINUOUS; + u32 val; + + if (pwm->polarity == PWM_POLARITY_INVERSED) + enable_conf |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE; + else + enable_conf |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE; + + val = readl_relaxed(pc->base + pc->data->regs.ctrl); + + if (enable) + val |= enable_conf; + else + val &= ~enable_conf; + + writel_relaxed(val, pc->base + pc->data->regs.ctrl); +} + +static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + unsigned long period, duty; + u64 clk_rate, div; + int ret; + + clk_rate = clk_get_rate(pc->clk); + + /* + * Since period and duty cycle registers have a width of 32 + * bits, every possible input period can be obtained using the + * default prescaler value for all practical clock rate values. + */ + div = clk_rate * period_ns; + do_div(div, pc->data->prescaler * NSEC_PER_SEC); + period = div; + + div = clk_rate * duty_ns; + do_div(div, pc->data->prescaler * NSEC_PER_SEC); + duty = div; + + ret = clk_enable(pc->clk); + if (ret) + return ret; + + writel(period, pc->base + pc->data->regs.period); + writel(duty, pc->base + pc->data->regs.duty); + writel(0, pc->base + pc->data->regs.cntr); + + clk_disable(pc->clk); + + return 0; +} + +static int rockchip_pwm_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + /* + * No action needed here because pwm->polarity will be set by the core + * and the core will only change polarity when the PWM is not enabled. + * We'll handle things in set_enable(). + */ + + return 0; +} + +static int rockchip_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + int ret; + + ret = clk_enable(pc->clk); + if (ret) + return ret; + + pc->data->set_enable(chip, pwm, true); + + return 0; +} + +static void rockchip_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + + pc->data->set_enable(chip, pwm, false); + + clk_disable(pc->clk); +} + +static const struct pwm_ops rockchip_pwm_ops_v1 = { + .config = rockchip_pwm_config, + .enable = rockchip_pwm_enable, + .disable = rockchip_pwm_disable, + .owner = THIS_MODULE, +}; + +static const struct pwm_ops rockchip_pwm_ops_v2 = { + .config = rockchip_pwm_config, + .set_polarity = rockchip_pwm_set_polarity, + .enable = rockchip_pwm_enable, + .disable = rockchip_pwm_disable, + .owner = THIS_MODULE, +}; + +static const struct rockchip_pwm_data pwm_data_v1 = { + .regs = { + .duty = 0x04, + .period = 0x08, + .cntr = 0x00, + .ctrl = 0x0c, + }, + .prescaler = 2, + .ops = &rockchip_pwm_ops_v1, + .set_enable = rockchip_pwm_set_enable_v1, +}; + +static const struct rockchip_pwm_data pwm_data_v2 = { + .regs = { + .duty = 0x08, + .period = 0x04, + .cntr = 0x00, + .ctrl = 0x0c, + }, + .prescaler = 1, + .ops = &rockchip_pwm_ops_v2, + .set_enable = rockchip_pwm_set_enable_v2, +}; + +static const struct rockchip_pwm_data pwm_data_vop = { + .regs = { + .duty = 0x08, + .period = 0x04, + .cntr = 0x0c, + .ctrl = 0x00, + }, + .prescaler = 1, + .ops = &rockchip_pwm_ops_v2, + .set_enable = rockchip_pwm_set_enable_v2, +}; + +static const struct of_device_id rockchip_pwm_dt_ids[] = { + { .compatible = "rockchip,rk2928-pwm", .data = &pwm_data_v1}, + { .compatible = "rockchip,rk3288-pwm", .data = &pwm_data_v2}, + { .compatible = "rockchip,vop-pwm", .data = &pwm_data_vop}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rockchip_pwm_dt_ids); + +static int rockchip_pwm_probe(struct platform_device *pdev) +{ + const struct of_device_id *id; + struct rockchip_pwm_chip *pc; + struct resource *r; + int ret; + + id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev); + if (!id) + return -EINVAL; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pc->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(pc->base)) + return PTR_ERR(pc->base); + + pc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pc->clk)) + return PTR_ERR(pc->clk); + + ret = clk_prepare(pc->clk); + if (ret) + return ret; + + platform_set_drvdata(pdev, pc); + + pc->data = id->data; + pc->chip.dev = &pdev->dev; + pc->chip.ops = pc->data->ops; + pc->chip.base = -1; + pc->chip.npwm = 1; + + if (pc->data->ops->set_polarity) { + pc->chip.of_xlate = of_pwm_xlate_with_flags; + pc->chip.of_pwm_n_cells = 3; + } + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + clk_unprepare(pc->clk); + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + } + + return ret; +} + +static int rockchip_pwm_remove(struct platform_device *pdev) +{ + struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev); + + clk_unprepare(pc->clk); + + return pwmchip_remove(&pc->chip); +} + +static struct platform_driver rockchip_pwm_driver = { + .driver = { + .name = "rockchip-pwm", + .of_match_table = rockchip_pwm_dt_ids, + }, + .probe = rockchip_pwm_probe, + .remove = rockchip_pwm_remove, +}; +module_platform_driver(rockchip_pwm_driver); + +MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>"); +MODULE_DESCRIPTION("Rockchip SoC PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-sti.c b/drivers/pwm/pwm-sti.c new file mode 100644 index 000000000000..b95115cdaea7 --- /dev/null +++ b/drivers/pwm/pwm-sti.c @@ -0,0 +1,418 @@ +/* + * PWM device driver for ST SoCs. + * Author: Ajit Pal Singh <ajitpal.singh@st.com> + * + * Copyright (C) 2013-2014 STMicroelectronics (R&D) Limited + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/math64.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/time.h> + +#define STI_DS_REG(ch) (4 * (ch)) /* Channel's Duty Cycle register */ +#define STI_PWMCR 0x50 /* Control/Config register */ +#define STI_INTEN 0x54 /* Interrupt Enable/Disable register */ +#define PWM_PRESCALE_LOW_MASK 0x0f +#define PWM_PRESCALE_HIGH_MASK 0xf0 + +/* Regfield IDs */ +enum { + PWMCLK_PRESCALE_LOW, + PWMCLK_PRESCALE_HIGH, + PWM_EN, + PWM_INT_EN, + + /* Keep last */ + MAX_REGFIELDS +}; + +struct sti_pwm_compat_data { + const struct reg_field *reg_fields; + unsigned int num_chan; + unsigned int max_pwm_cnt; + unsigned int max_prescale; +}; + +struct sti_pwm_chip { + struct device *dev; + struct clk *clk; + unsigned long clk_rate; + struct regmap *regmap; + struct sti_pwm_compat_data *cdata; + struct regmap_field *prescale_low; + struct regmap_field *prescale_high; + struct regmap_field *pwm_en; + struct regmap_field *pwm_int_en; + struct pwm_chip chip; + struct pwm_device *cur; + unsigned int en_count; + struct mutex sti_pwm_lock; /* To sync between enable/disable calls */ + void __iomem *mmio; +}; + +static const struct reg_field sti_pwm_regfields[MAX_REGFIELDS] = { + [PWMCLK_PRESCALE_LOW] = REG_FIELD(STI_PWMCR, 0, 3), + [PWMCLK_PRESCALE_HIGH] = REG_FIELD(STI_PWMCR, 11, 14), + [PWM_EN] = REG_FIELD(STI_PWMCR, 9, 9), + [PWM_INT_EN] = REG_FIELD(STI_INTEN, 0, 0), +}; + +static inline struct sti_pwm_chip *to_sti_pwmchip(struct pwm_chip *chip) +{ + return container_of(chip, struct sti_pwm_chip, chip); +} + +/* + * Calculate the prescaler value corresponding to the period. + */ +static int sti_pwm_get_prescale(struct sti_pwm_chip *pc, unsigned long period, + unsigned int *prescale) +{ + struct sti_pwm_compat_data *cdata = pc->cdata; + unsigned long val; + unsigned int ps; + + /* + * prescale = ((period_ns * clk_rate) / (10^9 * (max_pwm_count + 1)) - 1 + */ + val = NSEC_PER_SEC / pc->clk_rate; + val *= cdata->max_pwm_cnt + 1; + + if (period % val) { + return -EINVAL; + } else { + ps = period / val - 1; + if (ps > cdata->max_prescale) + return -EINVAL; + } + *prescale = ps; + + return 0; +} + +/* Calculate the number of PWM devices configured with a period. */ +static unsigned int sti_pwm_count_configured(struct pwm_chip *chip) +{ + struct pwm_device *pwm; + unsigned int ncfg = 0; + unsigned int i; + + for (i = 0; i < chip->npwm; i++) { + pwm = &chip->pwms[i]; + if (test_bit(PWMF_REQUESTED, &pwm->flags)) { + if (pwm_get_period(pwm)) + ncfg++; + } + } + + return ncfg; +} + +/* + * For STiH4xx PWM IP, the PWM period is fixed to 256 local clock cycles. + * The only way to change the period (apart from changing the PWM input clock) + * is to change the PWM clock prescaler. + * The prescaler is of 8 bits, so 256 prescaler values and hence + * 256 possible period values are supported (for a particular clock rate). + * The requested period will be applied only if it matches one of these + * 256 values. + */ +static int sti_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); + struct sti_pwm_compat_data *cdata = pc->cdata; + struct pwm_device *cur = pc->cur; + struct device *dev = pc->dev; + unsigned int prescale = 0, pwmvalx; + int ret; + unsigned int ncfg; + bool period_same = false; + + ncfg = sti_pwm_count_configured(chip); + if (ncfg) + period_same = (period_ns == pwm_get_period(cur)); + + /* Allow configuration changes if one of the + * following conditions satisfy. + * 1. No channels have been configured. + * 2. Only one channel has been configured and the new request + * is for the same channel. + * 3. Only one channel has been configured and the new request is + * for a new channel and period of the new channel is same as + * the current configured period. + * 4. More than one channels are configured and period of the new + * requestis the same as the current period. + */ + if (!ncfg || + ((ncfg == 1) && (pwm->hwpwm == cur->hwpwm)) || + ((ncfg == 1) && (pwm->hwpwm != cur->hwpwm) && period_same) || + ((ncfg > 1) && period_same)) { + /* Enable clock before writing to PWM registers. */ + ret = clk_enable(pc->clk); + if (ret) + return ret; + + if (!period_same) { + ret = sti_pwm_get_prescale(pc, period_ns, &prescale); + if (ret) + goto clk_dis; + + ret = + regmap_field_write(pc->prescale_low, + prescale & PWM_PRESCALE_LOW_MASK); + if (ret) + goto clk_dis; + + ret = + regmap_field_write(pc->prescale_high, + (prescale & PWM_PRESCALE_HIGH_MASK) >> 4); + if (ret) + goto clk_dis; + } + + /* + * When PWMVal == 0, PWM pulse = 1 local clock cycle. + * When PWMVal == max_pwm_count, + * PWM pulse = (max_pwm_count + 1) local cycles, + * that is continuous pulse: signal never goes low. + */ + pwmvalx = cdata->max_pwm_cnt * duty_ns / period_ns; + + ret = regmap_write(pc->regmap, STI_DS_REG(pwm->hwpwm), pwmvalx); + if (ret) + goto clk_dis; + + ret = regmap_field_write(pc->pwm_int_en, 0); + + pc->cur = pwm; + + dev_dbg(dev, "prescale:%u, period:%i, duty:%i, pwmvalx:%u\n", + prescale, period_ns, duty_ns, pwmvalx); + } else { + return -EINVAL; + } + +clk_dis: + clk_disable(pc->clk); + return ret; +} + +static int sti_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); + struct device *dev = pc->dev; + int ret = 0; + + /* + * Since we have a common enable for all PWM channels, + * do not enable if already enabled. + */ + mutex_lock(&pc->sti_pwm_lock); + if (!pc->en_count) { + ret = clk_enable(pc->clk); + if (ret) + goto out; + + ret = regmap_field_write(pc->pwm_en, 1); + if (ret) { + dev_err(dev, "failed to enable PWM device:%d\n", + pwm->hwpwm); + goto out; + } + } + pc->en_count++; +out: + mutex_unlock(&pc->sti_pwm_lock); + return ret; +} + +static void sti_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); + + mutex_lock(&pc->sti_pwm_lock); + if (--pc->en_count) { + mutex_unlock(&pc->sti_pwm_lock); + return; + } + regmap_field_write(pc->pwm_en, 0); + + clk_disable(pc->clk); + mutex_unlock(&pc->sti_pwm_lock); +} + +static const struct pwm_ops sti_pwm_ops = { + .config = sti_pwm_config, + .enable = sti_pwm_enable, + .disable = sti_pwm_disable, + .owner = THIS_MODULE, +}; + +static int sti_pwm_probe_dt(struct sti_pwm_chip *pc) +{ + struct device *dev = pc->dev; + const struct reg_field *reg_fields; + struct device_node *np = dev->of_node; + struct sti_pwm_compat_data *cdata = pc->cdata; + u32 num_chan; + + of_property_read_u32(np, "st,pwm-num-chan", &num_chan); + if (num_chan) + cdata->num_chan = num_chan; + + reg_fields = cdata->reg_fields; + + pc->prescale_low = devm_regmap_field_alloc(dev, pc->regmap, + reg_fields[PWMCLK_PRESCALE_LOW]); + if (IS_ERR(pc->prescale_low)) + return PTR_ERR(pc->prescale_low); + + pc->prescale_high = devm_regmap_field_alloc(dev, pc->regmap, + reg_fields[PWMCLK_PRESCALE_HIGH]); + if (IS_ERR(pc->prescale_high)) + return PTR_ERR(pc->prescale_high); + + pc->pwm_en = devm_regmap_field_alloc(dev, pc->regmap, + reg_fields[PWM_EN]); + if (IS_ERR(pc->pwm_en)) + return PTR_ERR(pc->pwm_en); + + pc->pwm_int_en = devm_regmap_field_alloc(dev, pc->regmap, + reg_fields[PWM_INT_EN]); + if (IS_ERR(pc->pwm_int_en)) + return PTR_ERR(pc->pwm_int_en); + + return 0; +} + +static const struct regmap_config sti_pwm_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int sti_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sti_pwm_compat_data *cdata; + struct sti_pwm_chip *pc; + struct resource *res; + int ret; + + pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + cdata = devm_kzalloc(dev, sizeof(*cdata), GFP_KERNEL); + if (!cdata) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + pc->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(pc->mmio)) + return PTR_ERR(pc->mmio); + + pc->regmap = devm_regmap_init_mmio(dev, pc->mmio, + &sti_pwm_regmap_config); + if (IS_ERR(pc->regmap)) + return PTR_ERR(pc->regmap); + + /* + * Setup PWM data with default values: some values could be replaced + * with specific ones provided from Device Tree. + */ + cdata->reg_fields = &sti_pwm_regfields[0]; + cdata->max_prescale = 0xff; + cdata->max_pwm_cnt = 255; + cdata->num_chan = 1; + + pc->cdata = cdata; + pc->dev = dev; + pc->en_count = 0; + mutex_init(&pc->sti_pwm_lock); + + ret = sti_pwm_probe_dt(pc); + if (ret) + return ret; + + pc->clk = of_clk_get_by_name(dev->of_node, "pwm"); + if (IS_ERR(pc->clk)) { + dev_err(dev, "failed to get PWM clock\n"); + return PTR_ERR(pc->clk); + } + + pc->clk_rate = clk_get_rate(pc->clk); + if (!pc->clk_rate) { + dev_err(dev, "failed to get clock rate\n"); + return -EINVAL; + } + + ret = clk_prepare(pc->clk); + if (ret) { + dev_err(dev, "failed to prepare clock\n"); + return ret; + } + + pc->chip.dev = dev; + pc->chip.ops = &sti_pwm_ops; + pc->chip.base = -1; + pc->chip.npwm = pc->cdata->num_chan; + pc->chip.can_sleep = true; + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + clk_unprepare(pc->clk); + return ret; + } + + platform_set_drvdata(pdev, pc); + + return 0; +} + +static int sti_pwm_remove(struct platform_device *pdev) +{ + struct sti_pwm_chip *pc = platform_get_drvdata(pdev); + unsigned int i; + + for (i = 0; i < pc->cdata->num_chan; i++) + pwm_disable(&pc->chip.pwms[i]); + + clk_unprepare(pc->clk); + + return pwmchip_remove(&pc->chip); +} + +static const struct of_device_id sti_pwm_of_match[] = { + { .compatible = "st,sti-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sti_pwm_of_match); + +static struct platform_driver sti_pwm_driver = { + .driver = { + .name = "sti-pwm", + .of_match_table = sti_pwm_of_match, + }, + .probe = sti_pwm_probe, + .remove = sti_pwm_remove, +}; +module_platform_driver(sti_pwm_driver); + +MODULE_AUTHOR("Ajit Pal Singh <ajitpal.singh@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST PWM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-tipwmss.c b/drivers/pwm/pwm-tipwmss.c index 3b119bc2c3c6..67481dc6da3f 100644 --- a/drivers/pwm/pwm-tipwmss.c +++ b/drivers/pwm/pwm-tipwmss.c @@ -62,10 +62,8 @@ static int pwmss_probe(struct platform_device *pdev) struct device_node *node = pdev->dev.of_node; info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); - if (!info) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (!info) return -ENOMEM; - } mutex_init(&info->pwmss_lock); |