aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMika Westerberg <mika.westerberg@linux.intel.com>2014-03-20 10:04:23 -0400
committerThierry Reding <thierry.reding@gmail.com>2014-04-01 06:03:40 -0400
commitd16a5aa9e821633a3095d7a88cd1d2cd108bf966 (patch)
tree0e0a627503aea9fe3c3075ae79046777d2778619
parent7eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5 (diff)
pwm: add support for Intel Low Power Subsystem PWM
Add support for Intel Low Power I/O subsystem PWM controllers found on Intel BayTrail SoC. Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com> Signed-off-by: Chew, Kean Ho <kean.ho.chew@intel.com> Signed-off-by: Chang, Rebecca Swee Fun <rebecca.swee.fun.chang@intel.com> Signed-off-by: Chew, Chiau Ee <chiau.ee.chew@intel.com> Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
-rw-r--r--drivers/pwm/Kconfig10
-rw-r--r--drivers/pwm/Makefile1
-rw-r--r--drivers/pwm/pwm-lpss.c183
3 files changed, 194 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 1445ba176574..5b34ff29ea38 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -138,6 +138,16 @@ config PWM_LPC32XX
138 To compile this driver as a module, choose M here: the module 138 To compile this driver as a module, choose M here: the module
139 will be called pwm-lpc32xx. 139 will be called pwm-lpc32xx.
140 140
141config PWM_LPSS
142 tristate "Intel LPSS PWM support"
143 depends on ACPI
144 help
145 Generic PWM framework driver for Intel Low Power Subsystem PWM
146 controller.
147
148 To compile this driver as a module, choose M here: the module
149 will be called pwm-lpss.
150
141config PWM_MXS 151config PWM_MXS
142 tristate "Freescale MXS PWM support" 152 tristate "Freescale MXS PWM support"
143 depends on ARCH_MXS && OF 153 depends on ARCH_MXS && OF
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 82f294e67079..e57d2c38a794 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_PWM_IMX) += pwm-imx.o
11obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o 11obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o
12obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o 12obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o
13obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o 13obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o
14obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o
14obj-$(CONFIG_PWM_MXS) += pwm-mxs.o 15obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
15obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o 16obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o
16obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o 17obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o
diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c
new file mode 100644
index 000000000000..449e372050a0
--- /dev/null
+++ b/drivers/pwm/pwm-lpss.c
@@ -0,0 +1,183 @@
1/*
2 * Intel Low Power Subsystem PWM controller driver
3 *
4 * Copyright (C) 2014, Intel Corporation
5 * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
6 * Author: Chew Kean Ho <kean.ho.chew@intel.com>
7 * Author: Chang Rebecca Swee Fun <rebecca.swee.fun.chang@intel.com>
8 * Author: Chew Chiau Ee <chiau.ee.chew@intel.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
13 */
14
15#include <linux/acpi.h>
16#include <linux/clk.h>
17#include <linux/device.h>
18#include <linux/kernel.h>
19#include <linux/module.h>
20#include <linux/pwm.h>
21#include <linux/platform_device.h>
22
23#define PWM 0x00000000
24#define PWM_ENABLE BIT(31)
25#define PWM_SW_UPDATE BIT(30)
26#define PWM_BASE_UNIT_SHIFT 8
27#define PWM_BASE_UNIT_MASK 0x00ffff00
28#define PWM_ON_TIME_DIV_MASK 0x000000ff
29#define PWM_DIVISION_CORRECTION 0x2
30#define PWM_LIMIT (0x8000 + PWM_DIVISION_CORRECTION)
31#define NSECS_PER_SEC 1000000000UL
32
33struct pwm_lpss_chip {
34 struct pwm_chip chip;
35 void __iomem *regs;
36 struct clk *clk;
37};
38
39static inline struct pwm_lpss_chip *to_lpwm(struct pwm_chip *chip)
40{
41 return container_of(chip, struct pwm_lpss_chip, chip);
42}
43
44static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm,
45 int duty_ns, int period_ns)
46{
47 struct pwm_lpss_chip *lpwm = to_lpwm(chip);
48 u8 on_time_div;
49 unsigned long c;
50 unsigned long long base_unit, freq = NSECS_PER_SEC;
51 u32 ctrl;
52
53 do_div(freq, period_ns);
54
55 /* The equation is: base_unit = ((freq / c) * 65536) + correction */
56 base_unit = freq * 65536;
57
58 c = clk_get_rate(lpwm->clk);
59 if (!c)
60 return -EINVAL;
61
62 do_div(base_unit, c);
63 base_unit += PWM_DIVISION_CORRECTION;
64 if (base_unit > PWM_LIMIT)
65 return -EINVAL;
66
67 if (duty_ns <= 0)
68 duty_ns = 1;
69 on_time_div = 255 - (255 * duty_ns / period_ns);
70
71 ctrl = readl(lpwm->regs + PWM);
72 ctrl &= ~(PWM_BASE_UNIT_MASK | PWM_ON_TIME_DIV_MASK);
73 ctrl |= (u16) base_unit << PWM_BASE_UNIT_SHIFT;
74 ctrl |= on_time_div;
75 /* request PWM to update on next cycle */
76 ctrl |= PWM_SW_UPDATE;
77 writel(ctrl, lpwm->regs + PWM);
78
79 return 0;
80}
81
82static int pwm_lpss_enable(struct pwm_chip *chip, struct pwm_device *pwm)
83{
84 struct pwm_lpss_chip *lpwm = to_lpwm(chip);
85 u32 ctrl;
86 int ret;
87
88 ret = clk_prepare_enable(lpwm->clk);
89 if (ret)
90 return ret;
91
92 ctrl = readl(lpwm->regs + PWM);
93 writel(ctrl | PWM_ENABLE, lpwm->regs + PWM);
94
95 return 0;
96}
97
98static void pwm_lpss_disable(struct pwm_chip *chip, struct pwm_device *pwm)
99{
100 struct pwm_lpss_chip *lpwm = to_lpwm(chip);
101 u32 ctrl;
102
103 ctrl = readl(lpwm->regs + PWM);
104 writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM);
105
106 clk_disable_unprepare(lpwm->clk);
107}
108
109static const struct pwm_ops pwm_lpss_ops = {
110 .config = pwm_lpss_config,
111 .enable = pwm_lpss_enable,
112 .disable = pwm_lpss_disable,
113 .owner = THIS_MODULE,
114};
115
116static const struct acpi_device_id pwm_lpss_acpi_match[] = {
117 { "80860F09", 0 },
118 { },
119};
120MODULE_DEVICE_TABLE(acpi, pwm_lpss_acpi_match);
121
122static int pwm_lpss_probe(struct platform_device *pdev)
123{
124 struct pwm_lpss_chip *lpwm;
125 struct resource *r;
126 int ret;
127
128 lpwm = devm_kzalloc(&pdev->dev, sizeof(*lpwm), GFP_KERNEL);
129 if (!lpwm)
130 return -ENOMEM;
131
132 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
133
134 lpwm->regs = devm_ioremap_resource(&pdev->dev, r);
135 if (IS_ERR(lpwm->regs))
136 return PTR_ERR(lpwm->regs);
137
138 lpwm->clk = devm_clk_get(&pdev->dev, NULL);
139 if (IS_ERR(lpwm->clk)) {
140 dev_err(&pdev->dev, "failed to get PWM clock\n");
141 return PTR_ERR(lpwm->clk);
142 }
143
144 lpwm->chip.dev = &pdev->dev;
145 lpwm->chip.ops = &pwm_lpss_ops;
146 lpwm->chip.base = -1;
147 lpwm->chip.npwm = 1;
148
149 ret = pwmchip_add(&lpwm->chip);
150 if (ret) {
151 dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
152 return ret;
153 }
154
155 platform_set_drvdata(pdev, lpwm);
156 return 0;
157}
158
159static int pwm_lpss_remove(struct platform_device *pdev)
160{
161 struct pwm_lpss_chip *lpwm = platform_get_drvdata(pdev);
162 u32 ctrl;
163
164 ctrl = readl(lpwm->regs + PWM);
165 writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM);
166
167 return pwmchip_remove(&lpwm->chip);
168}
169
170static struct platform_driver pwm_lpss_driver = {
171 .driver = {
172 .name = "pwm-lpss",
173 .acpi_match_table = pwm_lpss_acpi_match,
174 },
175 .probe = pwm_lpss_probe,
176 .remove = pwm_lpss_remove,
177};
178module_platform_driver(pwm_lpss_driver);
179
180MODULE_DESCRIPTION("PWM driver for Intel LPSS");
181MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
182MODULE_LICENSE("GPL v2");
183MODULE_ALIAS("platform:pwm-lpss");