diff options
author | Alexander Shiyan <shc_work@mail.ru> | 2014-03-12 11:53:05 -0400 |
---|---|---|
committer | Thierry Reding <thierry.reding@gmail.com> | 2014-03-18 16:15:16 -0400 |
commit | 7eb3f6ffb5c3635e8cc5df7b19741b4bfc5894f5 (patch) | |
tree | 1fc37173d371c507c996ad8fb984c7ac81585171 | |
parent | 916030db4399f9237beef480fee6b11dd83cacd5 (diff) |
pwm: Add CLPS711X PWM support
Add a new driver for the ARM CLPS711X Pulse Width Modulator (PWM) interface.
This CPU contain two 4-bit PWM outputs with constant period, based on CPU
PLL frequency. PWM polarity is determined by hardware by power on reset.
Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
-rw-r--r-- | Documentation/devicetree/bindings/pwm/cirrus,clps711x-pwm.txt | 16 | ||||
-rw-r--r-- | drivers/pwm/Kconfig | 9 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-clps711x.c | 176 |
4 files changed, 202 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/pwm/cirrus,clps711x-pwm.txt b/Documentation/devicetree/bindings/pwm/cirrus,clps711x-pwm.txt new file mode 100644 index 000000000000..a183db48f910 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/cirrus,clps711x-pwm.txt | |||
@@ -0,0 +1,16 @@ | |||
1 | * Cirris Logic CLPS711X PWM controller | ||
2 | |||
3 | Required properties: | ||
4 | - compatible: Shall contain "cirrus,clps711x-pwm". | ||
5 | - reg: Physical base address and length of the controller's registers. | ||
6 | - clocks: phandle + clock specifier pair of the PWM reference clock. | ||
7 | - #pwm-cells: Should be 1. The cell specifies the index of the channel. | ||
8 | |||
9 | Example: | ||
10 | pwm: pwm@80000400 { | ||
11 | compatible = "cirrus,ep7312-pwm", | ||
12 | "cirrus,clps711x-pwm"; | ||
13 | reg = <0x80000400 0x4>; | ||
14 | clocks = <&clks 8>; | ||
15 | #pwm-cells = <1>; | ||
16 | }; | ||
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 17935628383a..1445ba176574 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig | |||
@@ -71,6 +71,15 @@ config PWM_BFIN | |||
71 | To compile this driver as a module, choose M here: the module | 71 | To compile this driver as a module, choose M here: the module |
72 | will be called pwm-bfin. | 72 | will be called pwm-bfin. |
73 | 73 | ||
74 | config PWM_CLPS711X | ||
75 | tristate "CLPS711X PWM support" | ||
76 | depends on ARCH_CLPS711X || COMPILE_TEST | ||
77 | help | ||
78 | Generic PWM framework driver for Cirrus Logic CLPS711X. | ||
79 | |||
80 | To compile this driver as a module, choose M here: the module | ||
81 | will be called pwm-clps711x. | ||
82 | |||
74 | config PWM_EP93XX | 83 | config PWM_EP93XX |
75 | tristate "Cirrus Logic EP93xx PWM support" | 84 | tristate "Cirrus Logic EP93xx PWM support" |
76 | depends on ARCH_EP93XX | 85 | depends on ARCH_EP93XX |
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 7a10e05acb48..82f294e67079 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile | |||
@@ -4,6 +4,7 @@ obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o | |||
4 | obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o | 4 | obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o |
5 | obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o | 5 | obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o |
6 | obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o | 6 | obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o |
7 | obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o | ||
7 | obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o | 8 | obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o |
8 | obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o | 9 | obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o |
9 | obj-$(CONFIG_PWM_IMX) += pwm-imx.o | 10 | obj-$(CONFIG_PWM_IMX) += pwm-imx.o |
diff --git a/drivers/pwm/pwm-clps711x.c b/drivers/pwm/pwm-clps711x.c new file mode 100644 index 000000000000..fafb6a0111b0 --- /dev/null +++ b/drivers/pwm/pwm-clps711x.c | |||
@@ -0,0 +1,176 @@ | |||
1 | /* | ||
2 | * Cirrus Logic CLPS711X PWM driver | ||
3 | * | ||
4 | * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <linux/clk.h> | ||
13 | #include <linux/io.h> | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/of.h> | ||
16 | #include <linux/platform_device.h> | ||
17 | #include <linux/pwm.h> | ||
18 | |||
19 | struct clps711x_chip { | ||
20 | struct pwm_chip chip; | ||
21 | void __iomem *pmpcon; | ||
22 | struct clk *clk; | ||
23 | spinlock_t lock; | ||
24 | }; | ||
25 | |||
26 | static inline struct clps711x_chip *to_clps711x_chip(struct pwm_chip *chip) | ||
27 | { | ||
28 | return container_of(chip, struct clps711x_chip, chip); | ||
29 | } | ||
30 | |||
31 | static void clps711x_pwm_update_val(struct clps711x_chip *priv, u32 n, u32 v) | ||
32 | { | ||
33 | /* PWM0 - bits 4..7, PWM1 - bits 8..11 */ | ||
34 | u32 shift = (n + 1) * 4; | ||
35 | unsigned long flags; | ||
36 | u32 tmp; | ||
37 | |||
38 | spin_lock_irqsave(&priv->lock, flags); | ||
39 | |||
40 | tmp = readl(priv->pmpcon); | ||
41 | tmp &= ~(0xf << shift); | ||
42 | tmp |= v << shift; | ||
43 | writel(tmp, priv->pmpcon); | ||
44 | |||
45 | spin_unlock_irqrestore(&priv->lock, flags); | ||
46 | } | ||
47 | |||
48 | static unsigned int clps711x_get_duty(struct pwm_device *pwm, unsigned int v) | ||
49 | { | ||
50 | /* Duty cycle 0..15 max */ | ||
51 | return DIV_ROUND_CLOSEST(v * 0xf, pwm_get_period(pwm)); | ||
52 | } | ||
53 | |||
54 | static int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) | ||
55 | { | ||
56 | struct clps711x_chip *priv = to_clps711x_chip(chip); | ||
57 | unsigned int freq = clk_get_rate(priv->clk); | ||
58 | |||
59 | if (!freq) | ||
60 | return -EINVAL; | ||
61 | |||
62 | /* Store constant period value */ | ||
63 | pwm_set_period(pwm, DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq)); | ||
64 | |||
65 | return 0; | ||
66 | } | ||
67 | |||
68 | static int clps711x_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, | ||
69 | int duty_ns, int period_ns) | ||
70 | { | ||
71 | struct clps711x_chip *priv = to_clps711x_chip(chip); | ||
72 | unsigned int duty; | ||
73 | |||
74 | if (period_ns != pwm_get_period(pwm)) | ||
75 | return -EINVAL; | ||
76 | |||
77 | duty = clps711x_get_duty(pwm, duty_ns); | ||
78 | clps711x_pwm_update_val(priv, pwm->hwpwm, duty); | ||
79 | |||
80 | return 0; | ||
81 | } | ||
82 | |||
83 | static int clps711x_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
84 | { | ||
85 | struct clps711x_chip *priv = to_clps711x_chip(chip); | ||
86 | unsigned int duty; | ||
87 | |||
88 | duty = clps711x_get_duty(pwm, pwm_get_duty_cycle(pwm)); | ||
89 | clps711x_pwm_update_val(priv, pwm->hwpwm, duty); | ||
90 | |||
91 | return 0; | ||
92 | } | ||
93 | |||
94 | static void clps711x_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | ||
95 | { | ||
96 | struct clps711x_chip *priv = to_clps711x_chip(chip); | ||
97 | |||
98 | clps711x_pwm_update_val(priv, pwm->hwpwm, 0); | ||
99 | } | ||
100 | |||
101 | static const struct pwm_ops clps711x_pwm_ops = { | ||
102 | .request = clps711x_pwm_request, | ||
103 | .config = clps711x_pwm_config, | ||
104 | .enable = clps711x_pwm_enable, | ||
105 | .disable = clps711x_pwm_disable, | ||
106 | .owner = THIS_MODULE, | ||
107 | }; | ||
108 | |||
109 | static struct pwm_device *clps711x_pwm_xlate(struct pwm_chip *chip, | ||
110 | const struct of_phandle_args *args) | ||
111 | { | ||
112 | if (args->args[0] >= chip->npwm) | ||
113 | return ERR_PTR(-EINVAL); | ||
114 | |||
115 | return pwm_request_from_chip(chip, args->args[0], NULL); | ||
116 | } | ||
117 | |||
118 | static int clps711x_pwm_probe(struct platform_device *pdev) | ||
119 | { | ||
120 | struct clps711x_chip *priv; | ||
121 | struct resource *res; | ||
122 | |||
123 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | ||
124 | if (!priv) | ||
125 | return -ENOMEM; | ||
126 | |||
127 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
128 | priv->pmpcon = devm_ioremap_resource(&pdev->dev, res); | ||
129 | if (IS_ERR(priv->pmpcon)) | ||
130 | return PTR_ERR(priv->pmpcon); | ||
131 | |||
132 | priv->clk = devm_clk_get(&pdev->dev, NULL); | ||
133 | if (IS_ERR(priv->clk)) | ||
134 | return PTR_ERR(priv->clk); | ||
135 | |||
136 | priv->chip.ops = &clps711x_pwm_ops; | ||
137 | priv->chip.dev = &pdev->dev; | ||
138 | priv->chip.base = -1; | ||
139 | priv->chip.npwm = 2; | ||
140 | priv->chip.of_xlate = clps711x_pwm_xlate; | ||
141 | priv->chip.of_pwm_n_cells = 1; | ||
142 | |||
143 | spin_lock_init(&priv->lock); | ||
144 | |||
145 | platform_set_drvdata(pdev, priv); | ||
146 | |||
147 | return pwmchip_add(&priv->chip); | ||
148 | } | ||
149 | |||
150 | static int clps711x_pwm_remove(struct platform_device *pdev) | ||
151 | { | ||
152 | struct clps711x_chip *priv = platform_get_drvdata(pdev); | ||
153 | |||
154 | return pwmchip_remove(&priv->chip); | ||
155 | } | ||
156 | |||
157 | static const struct of_device_id __maybe_unused clps711x_pwm_dt_ids[] = { | ||
158 | { .compatible = "cirrus,clps711x-pwm", }, | ||
159 | { } | ||
160 | }; | ||
161 | MODULE_DEVICE_TABLE(of, clps711x_pwm_dt_ids); | ||
162 | |||
163 | static struct platform_driver clps711x_pwm_driver = { | ||
164 | .driver = { | ||
165 | .name = "clps711x-pwm", | ||
166 | .owner = THIS_MODULE, | ||
167 | .of_match_table = of_match_ptr(clps711x_pwm_dt_ids), | ||
168 | }, | ||
169 | .probe = clps711x_pwm_probe, | ||
170 | .remove = clps711x_pwm_remove, | ||
171 | }; | ||
172 | module_platform_driver(clps711x_pwm_driver); | ||
173 | |||
174 | MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); | ||
175 | MODULE_DESCRIPTION("Cirrus Logic CLPS711X PWM driver"); | ||
176 | MODULE_LICENSE("GPL"); | ||