1ce20364bSShiraz Hashim /*
2ce20364bSShiraz Hashim * ST Microelectronics SPEAr Pulse Width Modulator driver
3ce20364bSShiraz Hashim *
4ce20364bSShiraz Hashim * Copyright (C) 2012 ST Microelectronics
59cc23682SViresh Kumar * Shiraz Hashim <shiraz.linux.kernel@gmail.com>
6ce20364bSShiraz Hashim *
7ce20364bSShiraz Hashim * This file is licensed under the terms of the GNU General Public
8ce20364bSShiraz Hashim * License version 2. This program is licensed "as is" without any
9ce20364bSShiraz Hashim * warranty of any kind, whether express or implied.
10ce20364bSShiraz Hashim */
11ce20364bSShiraz Hashim
12ce20364bSShiraz Hashim #include <linux/clk.h>
13ce20364bSShiraz Hashim #include <linux/err.h>
14ce20364bSShiraz Hashim #include <linux/io.h>
15ce20364bSShiraz Hashim #include <linux/ioport.h>
16ce20364bSShiraz Hashim #include <linux/kernel.h>
17ce20364bSShiraz Hashim #include <linux/math64.h>
18ce20364bSShiraz Hashim #include <linux/module.h>
19ce20364bSShiraz Hashim #include <linux/of.h>
20ce20364bSShiraz Hashim #include <linux/platform_device.h>
21ce20364bSShiraz Hashim #include <linux/pwm.h>
22ce20364bSShiraz Hashim #include <linux/slab.h>
23ce20364bSShiraz Hashim #include <linux/types.h>
24ce20364bSShiraz Hashim
25ce20364bSShiraz Hashim #define NUM_PWM 4
26ce20364bSShiraz Hashim
27ce20364bSShiraz Hashim /* PWM registers and bits definitions */
28ce20364bSShiraz Hashim #define PWMCR 0x00 /* Control Register */
29ce20364bSShiraz Hashim #define PWMCR_PWM_ENABLE 0x1
30ce20364bSShiraz Hashim #define PWMCR_PRESCALE_SHIFT 2
31ce20364bSShiraz Hashim #define PWMCR_MIN_PRESCALE 0x00
32ce20364bSShiraz Hashim #define PWMCR_MAX_PRESCALE 0x3FFF
33ce20364bSShiraz Hashim
34ce20364bSShiraz Hashim #define PWMDCR 0x04 /* Duty Cycle Register */
35ce20364bSShiraz Hashim #define PWMDCR_MIN_DUTY 0x0001
36ce20364bSShiraz Hashim #define PWMDCR_MAX_DUTY 0xFFFF
37ce20364bSShiraz Hashim
38ce20364bSShiraz Hashim #define PWMPCR 0x08 /* Period Register */
39ce20364bSShiraz Hashim #define PWMPCR_MIN_PERIOD 0x0001
40ce20364bSShiraz Hashim #define PWMPCR_MAX_PERIOD 0xFFFF
41ce20364bSShiraz Hashim
42ce20364bSShiraz Hashim /* Following only available on 13xx SoCs */
43ce20364bSShiraz Hashim #define PWMMCR 0x3C /* Master Control Register */
44ce20364bSShiraz Hashim #define PWMMCR_PWM_ENABLE 0x1
45ce20364bSShiraz Hashim
46ce20364bSShiraz Hashim /**
47ce20364bSShiraz Hashim * struct spear_pwm_chip - struct representing pwm chip
48ce20364bSShiraz Hashim *
49ce20364bSShiraz Hashim * @mmio_base: base address of pwm chip
50ce20364bSShiraz Hashim * @clk: pointer to clk structure of pwm chip
51ce20364bSShiraz Hashim * @chip: linux pwm chip representation
52ce20364bSShiraz Hashim */
53ce20364bSShiraz Hashim struct spear_pwm_chip {
54ce20364bSShiraz Hashim void __iomem *mmio_base;
55ce20364bSShiraz Hashim struct clk *clk;
56ce20364bSShiraz Hashim struct pwm_chip chip;
57ce20364bSShiraz Hashim };
58ce20364bSShiraz Hashim
to_spear_pwm_chip(struct pwm_chip * chip)59ce20364bSShiraz Hashim static inline struct spear_pwm_chip *to_spear_pwm_chip(struct pwm_chip *chip)
60ce20364bSShiraz Hashim {
61ce20364bSShiraz Hashim return container_of(chip, struct spear_pwm_chip, chip);
62ce20364bSShiraz Hashim }
63ce20364bSShiraz Hashim
spear_pwm_readl(struct spear_pwm_chip * chip,unsigned int num,unsigned long offset)64ce20364bSShiraz Hashim static inline u32 spear_pwm_readl(struct spear_pwm_chip *chip, unsigned int num,
65ce20364bSShiraz Hashim unsigned long offset)
66ce20364bSShiraz Hashim {
67ce20364bSShiraz Hashim return readl_relaxed(chip->mmio_base + (num << 4) + offset);
68ce20364bSShiraz Hashim }
69ce20364bSShiraz Hashim
spear_pwm_writel(struct spear_pwm_chip * chip,unsigned int num,unsigned long offset,unsigned long val)70ce20364bSShiraz Hashim static inline void spear_pwm_writel(struct spear_pwm_chip *chip,
71ce20364bSShiraz Hashim unsigned int num, unsigned long offset,
72ce20364bSShiraz Hashim unsigned long val)
73ce20364bSShiraz Hashim {
74ce20364bSShiraz Hashim writel_relaxed(val, chip->mmio_base + (num << 4) + offset);
75ce20364bSShiraz Hashim }
76ce20364bSShiraz Hashim
spear_pwm_config(struct pwm_chip * chip,struct pwm_device * pwm,u64 duty_ns,u64 period_ns)77c9371360SAxel Lin static int spear_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
7898761ce4SUwe Kleine-König u64 duty_ns, u64 period_ns)
79ce20364bSShiraz Hashim {
80ce20364bSShiraz Hashim struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
81ce20364bSShiraz Hashim u64 val, div, clk_rate;
82ce20364bSShiraz Hashim unsigned long prescale = PWMCR_MIN_PRESCALE, pv, dc;
83ce20364bSShiraz Hashim int ret;
84ce20364bSShiraz Hashim
85ce20364bSShiraz Hashim /*
86ce20364bSShiraz Hashim * Find pv, dc and prescale to suit duty_ns and period_ns. This is done
87ce20364bSShiraz Hashim * according to formulas described below:
88ce20364bSShiraz Hashim *
89ce20364bSShiraz Hashim * period_ns = 10^9 * (PRESCALE + 1) * PV / PWM_CLK_RATE
90ce20364bSShiraz Hashim * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
91ce20364bSShiraz Hashim *
92ce20364bSShiraz Hashim * PV = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1))
93ce20364bSShiraz Hashim * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1))
94ce20364bSShiraz Hashim */
95ce20364bSShiraz Hashim clk_rate = clk_get_rate(pc->clk);
96ce20364bSShiraz Hashim while (1) {
97ce20364bSShiraz Hashim div = 1000000000;
98ce20364bSShiraz Hashim div *= 1 + prescale;
99ce20364bSShiraz Hashim val = clk_rate * period_ns;
100ce20364bSShiraz Hashim pv = div64_u64(val, div);
101ce20364bSShiraz Hashim val = clk_rate * duty_ns;
102ce20364bSShiraz Hashim dc = div64_u64(val, div);
103ce20364bSShiraz Hashim
104ce20364bSShiraz Hashim /* if duty_ns and period_ns are not achievable then return */
105ce20364bSShiraz Hashim if (pv < PWMPCR_MIN_PERIOD || dc < PWMDCR_MIN_DUTY)
106ce20364bSShiraz Hashim return -EINVAL;
107ce20364bSShiraz Hashim
108ce20364bSShiraz Hashim /*
109ce20364bSShiraz Hashim * if pv and dc have crossed their upper limit, then increase
110ce20364bSShiraz Hashim * prescale and recalculate pv and dc.
111ce20364bSShiraz Hashim */
112ce20364bSShiraz Hashim if (pv > PWMPCR_MAX_PERIOD || dc > PWMDCR_MAX_DUTY) {
113ce20364bSShiraz Hashim if (++prescale > PWMCR_MAX_PRESCALE)
114ce20364bSShiraz Hashim return -EINVAL;
115ce20364bSShiraz Hashim continue;
116ce20364bSShiraz Hashim }
117ce20364bSShiraz Hashim break;
118ce20364bSShiraz Hashim }
119ce20364bSShiraz Hashim
120ce20364bSShiraz Hashim /*
121ce20364bSShiraz Hashim * NOTE: the clock to PWM has to be enabled first before writing to the
122ce20364bSShiraz Hashim * registers.
123ce20364bSShiraz Hashim */
124ce20364bSShiraz Hashim ret = clk_enable(pc->clk);
125ce20364bSShiraz Hashim if (ret)
126ce20364bSShiraz Hashim return ret;
127ce20364bSShiraz Hashim
128ce20364bSShiraz Hashim spear_pwm_writel(pc, pwm->hwpwm, PWMCR,
129ce20364bSShiraz Hashim prescale << PWMCR_PRESCALE_SHIFT);
130ce20364bSShiraz Hashim spear_pwm_writel(pc, pwm->hwpwm, PWMDCR, dc);
131ce20364bSShiraz Hashim spear_pwm_writel(pc, pwm->hwpwm, PWMPCR, pv);
132ce20364bSShiraz Hashim clk_disable(pc->clk);
133ce20364bSShiraz Hashim
134ce20364bSShiraz Hashim return 0;
135ce20364bSShiraz Hashim }
136ce20364bSShiraz Hashim
spear_pwm_enable(struct pwm_chip * chip,struct pwm_device * pwm)137ce20364bSShiraz Hashim static int spear_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
138ce20364bSShiraz Hashim {
139ce20364bSShiraz Hashim struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
140ce20364bSShiraz Hashim int rc = 0;
141ce20364bSShiraz Hashim u32 val;
142ce20364bSShiraz Hashim
143ce20364bSShiraz Hashim rc = clk_enable(pc->clk);
144563861cdSAxel Lin if (rc)
145ce20364bSShiraz Hashim return rc;
146ce20364bSShiraz Hashim
147ce20364bSShiraz Hashim val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR);
148ce20364bSShiraz Hashim val |= PWMCR_PWM_ENABLE;
149ce20364bSShiraz Hashim spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val);
150ce20364bSShiraz Hashim
151ce20364bSShiraz Hashim return 0;
152ce20364bSShiraz Hashim }
153ce20364bSShiraz Hashim
spear_pwm_disable(struct pwm_chip * chip,struct pwm_device * pwm)154ce20364bSShiraz Hashim static void spear_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
155ce20364bSShiraz Hashim {
156ce20364bSShiraz Hashim struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
157ce20364bSShiraz Hashim u32 val;
158ce20364bSShiraz Hashim
159ce20364bSShiraz Hashim val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR);
160ce20364bSShiraz Hashim val &= ~PWMCR_PWM_ENABLE;
161ce20364bSShiraz Hashim spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val);
162ce20364bSShiraz Hashim
163ce20364bSShiraz Hashim clk_disable(pc->clk);
164ce20364bSShiraz Hashim }
165ce20364bSShiraz Hashim
spear_pwm_apply(struct pwm_chip * chip,struct pwm_device * pwm,const struct pwm_state * state)16698761ce4SUwe Kleine-König static int spear_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
16798761ce4SUwe Kleine-König const struct pwm_state *state)
16898761ce4SUwe Kleine-König {
16998761ce4SUwe Kleine-König int err;
17098761ce4SUwe Kleine-König
17198761ce4SUwe Kleine-König if (state->polarity != PWM_POLARITY_NORMAL)
17298761ce4SUwe Kleine-König return -EINVAL;
17398761ce4SUwe Kleine-König
17498761ce4SUwe Kleine-König if (!state->enabled) {
17598761ce4SUwe Kleine-König if (pwm->state.enabled)
17698761ce4SUwe Kleine-König spear_pwm_disable(chip, pwm);
17798761ce4SUwe Kleine-König return 0;
17898761ce4SUwe Kleine-König }
17998761ce4SUwe Kleine-König
18098761ce4SUwe Kleine-König err = spear_pwm_config(chip, pwm, state->duty_cycle, state->period);
18198761ce4SUwe Kleine-König if (err)
18298761ce4SUwe Kleine-König return err;
18398761ce4SUwe Kleine-König
18498761ce4SUwe Kleine-König if (!pwm->state.enabled)
18598761ce4SUwe Kleine-König return spear_pwm_enable(chip, pwm);
18698761ce4SUwe Kleine-König
18798761ce4SUwe Kleine-König return 0;
18898761ce4SUwe Kleine-König }
18998761ce4SUwe Kleine-König
190ce20364bSShiraz Hashim static const struct pwm_ops spear_pwm_ops = {
19198761ce4SUwe Kleine-König .apply = spear_pwm_apply,
192ce20364bSShiraz Hashim .owner = THIS_MODULE,
193ce20364bSShiraz Hashim };
194ce20364bSShiraz Hashim
spear_pwm_probe(struct platform_device * pdev)195ce20364bSShiraz Hashim static int spear_pwm_probe(struct platform_device *pdev)
196ce20364bSShiraz Hashim {
197ce20364bSShiraz Hashim struct device_node *np = pdev->dev.of_node;
198ce20364bSShiraz Hashim struct spear_pwm_chip *pc;
199ce20364bSShiraz Hashim int ret;
200ce20364bSShiraz Hashim u32 val;
201ce20364bSShiraz Hashim
202ce20364bSShiraz Hashim pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
2039321fe9dSJingoo Han if (!pc)
204ce20364bSShiraz Hashim return -ENOMEM;
205ce20364bSShiraz Hashim
20621af4356SYangtao Li pc->mmio_base = devm_platform_ioremap_resource(pdev, 0);
2076d4294d1SThierry Reding if (IS_ERR(pc->mmio_base))
2086d4294d1SThierry Reding return PTR_ERR(pc->mmio_base);
209ce20364bSShiraz Hashim
210ce20364bSShiraz Hashim pc->clk = devm_clk_get(&pdev->dev, NULL);
211ce20364bSShiraz Hashim if (IS_ERR(pc->clk))
212ce20364bSShiraz Hashim return PTR_ERR(pc->clk);
213ce20364bSShiraz Hashim
214ce20364bSShiraz Hashim platform_set_drvdata(pdev, pc);
215ce20364bSShiraz Hashim
216ce20364bSShiraz Hashim pc->chip.dev = &pdev->dev;
217ce20364bSShiraz Hashim pc->chip.ops = &spear_pwm_ops;
218ce20364bSShiraz Hashim pc->chip.npwm = NUM_PWM;
219ce20364bSShiraz Hashim
220ce20364bSShiraz Hashim ret = clk_prepare(pc->clk);
221563861cdSAxel Lin if (ret)
222ce20364bSShiraz Hashim return ret;
223ce20364bSShiraz Hashim
224ce20364bSShiraz Hashim if (of_device_is_compatible(np, "st,spear1340-pwm")) {
225ce20364bSShiraz Hashim ret = clk_enable(pc->clk);
226563861cdSAxel Lin if (ret) {
227ce20364bSShiraz Hashim clk_unprepare(pc->clk);
228ce20364bSShiraz Hashim return ret;
229ce20364bSShiraz Hashim }
230ce20364bSShiraz Hashim /*
231ce20364bSShiraz Hashim * Following enables PWM chip, channels would still be
232ce20364bSShiraz Hashim * enabled individually through their control register
233ce20364bSShiraz Hashim */
234ce20364bSShiraz Hashim val = readl_relaxed(pc->mmio_base + PWMMCR);
235ce20364bSShiraz Hashim val |= PWMMCR_PWM_ENABLE;
236ce20364bSShiraz Hashim writel_relaxed(val, pc->mmio_base + PWMMCR);
237ce20364bSShiraz Hashim
238ce20364bSShiraz Hashim clk_disable(pc->clk);
239ce20364bSShiraz Hashim }
240ce20364bSShiraz Hashim
241ce20364bSShiraz Hashim ret = pwmchip_add(&pc->chip);
2425b1e8e06SBeniamino Galvani if (ret < 0) {
243ce20364bSShiraz Hashim clk_unprepare(pc->clk);
244ce20364bSShiraz Hashim dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
245ce20364bSShiraz Hashim }
246ce20364bSShiraz Hashim
247ce20364bSShiraz Hashim return ret;
248ce20364bSShiraz Hashim }
249ce20364bSShiraz Hashim
spear_pwm_remove(struct platform_device * pdev)250*f113c11bSUwe Kleine-König static void spear_pwm_remove(struct platform_device *pdev)
251ce20364bSShiraz Hashim {
252ce20364bSShiraz Hashim struct spear_pwm_chip *pc = platform_get_drvdata(pdev);
253ce20364bSShiraz Hashim
254da0dea89SUwe Kleine-König pwmchip_remove(&pc->chip);
255da0dea89SUwe Kleine-König
256ce20364bSShiraz Hashim /* clk was prepared in probe, hence unprepare it here */
257ce20364bSShiraz Hashim clk_unprepare(pc->clk);
258ce20364bSShiraz Hashim }
259ce20364bSShiraz Hashim
260f1a8870aSThierry Reding static const struct of_device_id spear_pwm_of_match[] = {
261ce20364bSShiraz Hashim { .compatible = "st,spear320-pwm" },
262ce20364bSShiraz Hashim { .compatible = "st,spear1340-pwm" },
263ce20364bSShiraz Hashim { }
264ce20364bSShiraz Hashim };
265ce20364bSShiraz Hashim
266ce20364bSShiraz Hashim MODULE_DEVICE_TABLE(of, spear_pwm_of_match);
267ce20364bSShiraz Hashim
268ce20364bSShiraz Hashim static struct platform_driver spear_pwm_driver = {
269ce20364bSShiraz Hashim .driver = {
270ce20364bSShiraz Hashim .name = "spear-pwm",
271ce20364bSShiraz Hashim .of_match_table = spear_pwm_of_match,
272ce20364bSShiraz Hashim },
273ce20364bSShiraz Hashim .probe = spear_pwm_probe,
274*f113c11bSUwe Kleine-König .remove_new = spear_pwm_remove,
275ce20364bSShiraz Hashim };
276ce20364bSShiraz Hashim
277ce20364bSShiraz Hashim module_platform_driver(spear_pwm_driver);
278ce20364bSShiraz Hashim
279ce20364bSShiraz Hashim MODULE_LICENSE("GPL");
2809cc23682SViresh Kumar MODULE_AUTHOR("Shiraz Hashim <shiraz.linux.kernel@gmail.com>");
281ce20364bSShiraz Hashim MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.com>");
282ce20364bSShiraz Hashim MODULE_ALIAS("platform:spear-pwm");
283