xref: /openbmc/linux/drivers/pwm/pwm-spear.c (revision f113c11b)
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