1*4882a593Smuzhiyun /*
2*4882a593Smuzhiyun * ST Microelectronics SPEAr Pulse Width Modulator driver
3*4882a593Smuzhiyun *
4*4882a593Smuzhiyun * Copyright (C) 2012 ST Microelectronics
5*4882a593Smuzhiyun * Shiraz Hashim <shiraz.linux.kernel@gmail.com>
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * This file is licensed under the terms of the GNU General Public
8*4882a593Smuzhiyun * License version 2. This program is licensed "as is" without any
9*4882a593Smuzhiyun * warranty of any kind, whether express or implied.
10*4882a593Smuzhiyun */
11*4882a593Smuzhiyun
12*4882a593Smuzhiyun #include <linux/clk.h>
13*4882a593Smuzhiyun #include <linux/err.h>
14*4882a593Smuzhiyun #include <linux/io.h>
15*4882a593Smuzhiyun #include <linux/ioport.h>
16*4882a593Smuzhiyun #include <linux/kernel.h>
17*4882a593Smuzhiyun #include <linux/math64.h>
18*4882a593Smuzhiyun #include <linux/module.h>
19*4882a593Smuzhiyun #include <linux/of.h>
20*4882a593Smuzhiyun #include <linux/platform_device.h>
21*4882a593Smuzhiyun #include <linux/pwm.h>
22*4882a593Smuzhiyun #include <linux/slab.h>
23*4882a593Smuzhiyun #include <linux/types.h>
24*4882a593Smuzhiyun
25*4882a593Smuzhiyun #define NUM_PWM 4
26*4882a593Smuzhiyun
27*4882a593Smuzhiyun /* PWM registers and bits definitions */
28*4882a593Smuzhiyun #define PWMCR 0x00 /* Control Register */
29*4882a593Smuzhiyun #define PWMCR_PWM_ENABLE 0x1
30*4882a593Smuzhiyun #define PWMCR_PRESCALE_SHIFT 2
31*4882a593Smuzhiyun #define PWMCR_MIN_PRESCALE 0x00
32*4882a593Smuzhiyun #define PWMCR_MAX_PRESCALE 0x3FFF
33*4882a593Smuzhiyun
34*4882a593Smuzhiyun #define PWMDCR 0x04 /* Duty Cycle Register */
35*4882a593Smuzhiyun #define PWMDCR_MIN_DUTY 0x0001
36*4882a593Smuzhiyun #define PWMDCR_MAX_DUTY 0xFFFF
37*4882a593Smuzhiyun
38*4882a593Smuzhiyun #define PWMPCR 0x08 /* Period Register */
39*4882a593Smuzhiyun #define PWMPCR_MIN_PERIOD 0x0001
40*4882a593Smuzhiyun #define PWMPCR_MAX_PERIOD 0xFFFF
41*4882a593Smuzhiyun
42*4882a593Smuzhiyun /* Following only available on 13xx SoCs */
43*4882a593Smuzhiyun #define PWMMCR 0x3C /* Master Control Register */
44*4882a593Smuzhiyun #define PWMMCR_PWM_ENABLE 0x1
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun /**
47*4882a593Smuzhiyun * struct spear_pwm_chip - struct representing pwm chip
48*4882a593Smuzhiyun *
49*4882a593Smuzhiyun * @mmio_base: base address of pwm chip
50*4882a593Smuzhiyun * @clk: pointer to clk structure of pwm chip
51*4882a593Smuzhiyun * @chip: linux pwm chip representation
52*4882a593Smuzhiyun */
53*4882a593Smuzhiyun struct spear_pwm_chip {
54*4882a593Smuzhiyun void __iomem *mmio_base;
55*4882a593Smuzhiyun struct clk *clk;
56*4882a593Smuzhiyun struct pwm_chip chip;
57*4882a593Smuzhiyun };
58*4882a593Smuzhiyun
to_spear_pwm_chip(struct pwm_chip * chip)59*4882a593Smuzhiyun static inline struct spear_pwm_chip *to_spear_pwm_chip(struct pwm_chip *chip)
60*4882a593Smuzhiyun {
61*4882a593Smuzhiyun return container_of(chip, struct spear_pwm_chip, chip);
62*4882a593Smuzhiyun }
63*4882a593Smuzhiyun
spear_pwm_readl(struct spear_pwm_chip * chip,unsigned int num,unsigned long offset)64*4882a593Smuzhiyun static inline u32 spear_pwm_readl(struct spear_pwm_chip *chip, unsigned int num,
65*4882a593Smuzhiyun unsigned long offset)
66*4882a593Smuzhiyun {
67*4882a593Smuzhiyun return readl_relaxed(chip->mmio_base + (num << 4) + offset);
68*4882a593Smuzhiyun }
69*4882a593Smuzhiyun
spear_pwm_writel(struct spear_pwm_chip * chip,unsigned int num,unsigned long offset,unsigned long val)70*4882a593Smuzhiyun static inline void spear_pwm_writel(struct spear_pwm_chip *chip,
71*4882a593Smuzhiyun unsigned int num, unsigned long offset,
72*4882a593Smuzhiyun unsigned long val)
73*4882a593Smuzhiyun {
74*4882a593Smuzhiyun writel_relaxed(val, chip->mmio_base + (num << 4) + offset);
75*4882a593Smuzhiyun }
76*4882a593Smuzhiyun
spear_pwm_config(struct pwm_chip * chip,struct pwm_device * pwm,int duty_ns,int period_ns)77*4882a593Smuzhiyun static int spear_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
78*4882a593Smuzhiyun int duty_ns, int period_ns)
79*4882a593Smuzhiyun {
80*4882a593Smuzhiyun struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
81*4882a593Smuzhiyun u64 val, div, clk_rate;
82*4882a593Smuzhiyun unsigned long prescale = PWMCR_MIN_PRESCALE, pv, dc;
83*4882a593Smuzhiyun int ret;
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun /*
86*4882a593Smuzhiyun * Find pv, dc and prescale to suit duty_ns and period_ns. This is done
87*4882a593Smuzhiyun * according to formulas described below:
88*4882a593Smuzhiyun *
89*4882a593Smuzhiyun * period_ns = 10^9 * (PRESCALE + 1) * PV / PWM_CLK_RATE
90*4882a593Smuzhiyun * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
91*4882a593Smuzhiyun *
92*4882a593Smuzhiyun * PV = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1))
93*4882a593Smuzhiyun * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1))
94*4882a593Smuzhiyun */
95*4882a593Smuzhiyun clk_rate = clk_get_rate(pc->clk);
96*4882a593Smuzhiyun while (1) {
97*4882a593Smuzhiyun div = 1000000000;
98*4882a593Smuzhiyun div *= 1 + prescale;
99*4882a593Smuzhiyun val = clk_rate * period_ns;
100*4882a593Smuzhiyun pv = div64_u64(val, div);
101*4882a593Smuzhiyun val = clk_rate * duty_ns;
102*4882a593Smuzhiyun dc = div64_u64(val, div);
103*4882a593Smuzhiyun
104*4882a593Smuzhiyun /* if duty_ns and period_ns are not achievable then return */
105*4882a593Smuzhiyun if (pv < PWMPCR_MIN_PERIOD || dc < PWMDCR_MIN_DUTY)
106*4882a593Smuzhiyun return -EINVAL;
107*4882a593Smuzhiyun
108*4882a593Smuzhiyun /*
109*4882a593Smuzhiyun * if pv and dc have crossed their upper limit, then increase
110*4882a593Smuzhiyun * prescale and recalculate pv and dc.
111*4882a593Smuzhiyun */
112*4882a593Smuzhiyun if (pv > PWMPCR_MAX_PERIOD || dc > PWMDCR_MAX_DUTY) {
113*4882a593Smuzhiyun if (++prescale > PWMCR_MAX_PRESCALE)
114*4882a593Smuzhiyun return -EINVAL;
115*4882a593Smuzhiyun continue;
116*4882a593Smuzhiyun }
117*4882a593Smuzhiyun break;
118*4882a593Smuzhiyun }
119*4882a593Smuzhiyun
120*4882a593Smuzhiyun /*
121*4882a593Smuzhiyun * NOTE: the clock to PWM has to be enabled first before writing to the
122*4882a593Smuzhiyun * registers.
123*4882a593Smuzhiyun */
124*4882a593Smuzhiyun ret = clk_enable(pc->clk);
125*4882a593Smuzhiyun if (ret)
126*4882a593Smuzhiyun return ret;
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun spear_pwm_writel(pc, pwm->hwpwm, PWMCR,
129*4882a593Smuzhiyun prescale << PWMCR_PRESCALE_SHIFT);
130*4882a593Smuzhiyun spear_pwm_writel(pc, pwm->hwpwm, PWMDCR, dc);
131*4882a593Smuzhiyun spear_pwm_writel(pc, pwm->hwpwm, PWMPCR, pv);
132*4882a593Smuzhiyun clk_disable(pc->clk);
133*4882a593Smuzhiyun
134*4882a593Smuzhiyun return 0;
135*4882a593Smuzhiyun }
136*4882a593Smuzhiyun
spear_pwm_enable(struct pwm_chip * chip,struct pwm_device * pwm)137*4882a593Smuzhiyun static int spear_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
138*4882a593Smuzhiyun {
139*4882a593Smuzhiyun struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
140*4882a593Smuzhiyun int rc = 0;
141*4882a593Smuzhiyun u32 val;
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun rc = clk_enable(pc->clk);
144*4882a593Smuzhiyun if (rc)
145*4882a593Smuzhiyun return rc;
146*4882a593Smuzhiyun
147*4882a593Smuzhiyun val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR);
148*4882a593Smuzhiyun val |= PWMCR_PWM_ENABLE;
149*4882a593Smuzhiyun spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val);
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun return 0;
152*4882a593Smuzhiyun }
153*4882a593Smuzhiyun
spear_pwm_disable(struct pwm_chip * chip,struct pwm_device * pwm)154*4882a593Smuzhiyun static void spear_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
155*4882a593Smuzhiyun {
156*4882a593Smuzhiyun struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
157*4882a593Smuzhiyun u32 val;
158*4882a593Smuzhiyun
159*4882a593Smuzhiyun val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR);
160*4882a593Smuzhiyun val &= ~PWMCR_PWM_ENABLE;
161*4882a593Smuzhiyun spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val);
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun clk_disable(pc->clk);
164*4882a593Smuzhiyun }
165*4882a593Smuzhiyun
166*4882a593Smuzhiyun static const struct pwm_ops spear_pwm_ops = {
167*4882a593Smuzhiyun .config = spear_pwm_config,
168*4882a593Smuzhiyun .enable = spear_pwm_enable,
169*4882a593Smuzhiyun .disable = spear_pwm_disable,
170*4882a593Smuzhiyun .owner = THIS_MODULE,
171*4882a593Smuzhiyun };
172*4882a593Smuzhiyun
spear_pwm_probe(struct platform_device * pdev)173*4882a593Smuzhiyun static int spear_pwm_probe(struct platform_device *pdev)
174*4882a593Smuzhiyun {
175*4882a593Smuzhiyun struct device_node *np = pdev->dev.of_node;
176*4882a593Smuzhiyun struct spear_pwm_chip *pc;
177*4882a593Smuzhiyun struct resource *r;
178*4882a593Smuzhiyun int ret;
179*4882a593Smuzhiyun u32 val;
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
182*4882a593Smuzhiyun if (!pc)
183*4882a593Smuzhiyun return -ENOMEM;
184*4882a593Smuzhiyun
185*4882a593Smuzhiyun r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
186*4882a593Smuzhiyun pc->mmio_base = devm_ioremap_resource(&pdev->dev, r);
187*4882a593Smuzhiyun if (IS_ERR(pc->mmio_base))
188*4882a593Smuzhiyun return PTR_ERR(pc->mmio_base);
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun pc->clk = devm_clk_get(&pdev->dev, NULL);
191*4882a593Smuzhiyun if (IS_ERR(pc->clk))
192*4882a593Smuzhiyun return PTR_ERR(pc->clk);
193*4882a593Smuzhiyun
194*4882a593Smuzhiyun platform_set_drvdata(pdev, pc);
195*4882a593Smuzhiyun
196*4882a593Smuzhiyun pc->chip.dev = &pdev->dev;
197*4882a593Smuzhiyun pc->chip.ops = &spear_pwm_ops;
198*4882a593Smuzhiyun pc->chip.base = -1;
199*4882a593Smuzhiyun pc->chip.npwm = NUM_PWM;
200*4882a593Smuzhiyun
201*4882a593Smuzhiyun ret = clk_prepare(pc->clk);
202*4882a593Smuzhiyun if (ret)
203*4882a593Smuzhiyun return ret;
204*4882a593Smuzhiyun
205*4882a593Smuzhiyun if (of_device_is_compatible(np, "st,spear1340-pwm")) {
206*4882a593Smuzhiyun ret = clk_enable(pc->clk);
207*4882a593Smuzhiyun if (ret) {
208*4882a593Smuzhiyun clk_unprepare(pc->clk);
209*4882a593Smuzhiyun return ret;
210*4882a593Smuzhiyun }
211*4882a593Smuzhiyun /*
212*4882a593Smuzhiyun * Following enables PWM chip, channels would still be
213*4882a593Smuzhiyun * enabled individually through their control register
214*4882a593Smuzhiyun */
215*4882a593Smuzhiyun val = readl_relaxed(pc->mmio_base + PWMMCR);
216*4882a593Smuzhiyun val |= PWMMCR_PWM_ENABLE;
217*4882a593Smuzhiyun writel_relaxed(val, pc->mmio_base + PWMMCR);
218*4882a593Smuzhiyun
219*4882a593Smuzhiyun clk_disable(pc->clk);
220*4882a593Smuzhiyun }
221*4882a593Smuzhiyun
222*4882a593Smuzhiyun ret = pwmchip_add(&pc->chip);
223*4882a593Smuzhiyun if (ret < 0) {
224*4882a593Smuzhiyun clk_unprepare(pc->clk);
225*4882a593Smuzhiyun dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
226*4882a593Smuzhiyun }
227*4882a593Smuzhiyun
228*4882a593Smuzhiyun return ret;
229*4882a593Smuzhiyun }
230*4882a593Smuzhiyun
spear_pwm_remove(struct platform_device * pdev)231*4882a593Smuzhiyun static int spear_pwm_remove(struct platform_device *pdev)
232*4882a593Smuzhiyun {
233*4882a593Smuzhiyun struct spear_pwm_chip *pc = platform_get_drvdata(pdev);
234*4882a593Smuzhiyun
235*4882a593Smuzhiyun /* clk was prepared in probe, hence unprepare it here */
236*4882a593Smuzhiyun clk_unprepare(pc->clk);
237*4882a593Smuzhiyun return pwmchip_remove(&pc->chip);
238*4882a593Smuzhiyun }
239*4882a593Smuzhiyun
240*4882a593Smuzhiyun static const struct of_device_id spear_pwm_of_match[] = {
241*4882a593Smuzhiyun { .compatible = "st,spear320-pwm" },
242*4882a593Smuzhiyun { .compatible = "st,spear1340-pwm" },
243*4882a593Smuzhiyun { }
244*4882a593Smuzhiyun };
245*4882a593Smuzhiyun
246*4882a593Smuzhiyun MODULE_DEVICE_TABLE(of, spear_pwm_of_match);
247*4882a593Smuzhiyun
248*4882a593Smuzhiyun static struct platform_driver spear_pwm_driver = {
249*4882a593Smuzhiyun .driver = {
250*4882a593Smuzhiyun .name = "spear-pwm",
251*4882a593Smuzhiyun .of_match_table = spear_pwm_of_match,
252*4882a593Smuzhiyun },
253*4882a593Smuzhiyun .probe = spear_pwm_probe,
254*4882a593Smuzhiyun .remove = spear_pwm_remove,
255*4882a593Smuzhiyun };
256*4882a593Smuzhiyun
257*4882a593Smuzhiyun module_platform_driver(spear_pwm_driver);
258*4882a593Smuzhiyun
259*4882a593Smuzhiyun MODULE_LICENSE("GPL");
260*4882a593Smuzhiyun MODULE_AUTHOR("Shiraz Hashim <shiraz.linux.kernel@gmail.com>");
261*4882a593Smuzhiyun MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.com>");
262*4882a593Smuzhiyun MODULE_ALIAS("platform:spear-pwm");
263