xref: /openbmc/linux/drivers/pwm/pwm-keembay.c (revision bd899ceb)
1*bd899cebSVijayakannan Ayyathurai // SPDX-License-Identifier: GPL-2.0
2*bd899cebSVijayakannan Ayyathurai /*
3*bd899cebSVijayakannan Ayyathurai  * Intel Keem Bay PWM driver
4*bd899cebSVijayakannan Ayyathurai  *
5*bd899cebSVijayakannan Ayyathurai  * Copyright (C) 2020 Intel Corporation
6*bd899cebSVijayakannan Ayyathurai  * Authors: Lai Poey Seng <poey.seng.lai@intel.com>
7*bd899cebSVijayakannan Ayyathurai  *          Vineetha G. Jaya Kumaran <vineetha.g.jaya.kumaran@intel.com>
8*bd899cebSVijayakannan Ayyathurai  *
9*bd899cebSVijayakannan Ayyathurai  * Limitations:
10*bd899cebSVijayakannan Ayyathurai  * - Upon disabling a channel, the currently running
11*bd899cebSVijayakannan Ayyathurai  *   period will not be completed. However, upon
12*bd899cebSVijayakannan Ayyathurai  *   reconfiguration of the duty cycle/period, the
13*bd899cebSVijayakannan Ayyathurai  *   currently running period will be completed first.
14*bd899cebSVijayakannan Ayyathurai  */
15*bd899cebSVijayakannan Ayyathurai 
16*bd899cebSVijayakannan Ayyathurai #include <linux/bitfield.h>
17*bd899cebSVijayakannan Ayyathurai #include <linux/clk.h>
18*bd899cebSVijayakannan Ayyathurai #include <linux/io.h>
19*bd899cebSVijayakannan Ayyathurai #include <linux/mod_devicetable.h>
20*bd899cebSVijayakannan Ayyathurai #include <linux/module.h>
21*bd899cebSVijayakannan Ayyathurai #include <linux/platform_device.h>
22*bd899cebSVijayakannan Ayyathurai #include <linux/pwm.h>
23*bd899cebSVijayakannan Ayyathurai #include <linux/regmap.h>
24*bd899cebSVijayakannan Ayyathurai 
25*bd899cebSVijayakannan Ayyathurai #define KMB_TOTAL_PWM_CHANNELS		6
26*bd899cebSVijayakannan Ayyathurai #define KMB_PWM_COUNT_MAX		U16_MAX
27*bd899cebSVijayakannan Ayyathurai #define KMB_PWM_EN_BIT			BIT(31)
28*bd899cebSVijayakannan Ayyathurai 
29*bd899cebSVijayakannan Ayyathurai /* Mask */
30*bd899cebSVijayakannan Ayyathurai #define KMB_PWM_HIGH_MASK		GENMASK(31, 16)
31*bd899cebSVijayakannan Ayyathurai #define KMB_PWM_LOW_MASK		GENMASK(15, 0)
32*bd899cebSVijayakannan Ayyathurai #define KMB_PWM_LEADIN_MASK		GENMASK(30, 0)
33*bd899cebSVijayakannan Ayyathurai 
34*bd899cebSVijayakannan Ayyathurai /* PWM Register offset */
35*bd899cebSVijayakannan Ayyathurai #define KMB_PWM_LEADIN_OFFSET(ch)	(0x00 + 4 * (ch))
36*bd899cebSVijayakannan Ayyathurai #define KMB_PWM_HIGHLOW_OFFSET(ch)	(0x20 + 4 * (ch))
37*bd899cebSVijayakannan Ayyathurai 
38*bd899cebSVijayakannan Ayyathurai struct keembay_pwm {
39*bd899cebSVijayakannan Ayyathurai 	struct pwm_chip chip;
40*bd899cebSVijayakannan Ayyathurai 	struct device *dev;
41*bd899cebSVijayakannan Ayyathurai 	struct clk *clk;
42*bd899cebSVijayakannan Ayyathurai 	void __iomem *base;
43*bd899cebSVijayakannan Ayyathurai };
44*bd899cebSVijayakannan Ayyathurai 
45*bd899cebSVijayakannan Ayyathurai static inline struct keembay_pwm *to_keembay_pwm_dev(struct pwm_chip *chip)
46*bd899cebSVijayakannan Ayyathurai {
47*bd899cebSVijayakannan Ayyathurai 	return container_of(chip, struct keembay_pwm, chip);
48*bd899cebSVijayakannan Ayyathurai }
49*bd899cebSVijayakannan Ayyathurai 
50*bd899cebSVijayakannan Ayyathurai static void keembay_clk_unprepare(void *data)
51*bd899cebSVijayakannan Ayyathurai {
52*bd899cebSVijayakannan Ayyathurai 	clk_disable_unprepare(data);
53*bd899cebSVijayakannan Ayyathurai }
54*bd899cebSVijayakannan Ayyathurai 
55*bd899cebSVijayakannan Ayyathurai static int keembay_clk_enable(struct device *dev, struct clk *clk)
56*bd899cebSVijayakannan Ayyathurai {
57*bd899cebSVijayakannan Ayyathurai 	int ret;
58*bd899cebSVijayakannan Ayyathurai 
59*bd899cebSVijayakannan Ayyathurai 	ret = clk_prepare_enable(clk);
60*bd899cebSVijayakannan Ayyathurai 	if (ret)
61*bd899cebSVijayakannan Ayyathurai 		return ret;
62*bd899cebSVijayakannan Ayyathurai 
63*bd899cebSVijayakannan Ayyathurai 	return devm_add_action_or_reset(dev, keembay_clk_unprepare, clk);
64*bd899cebSVijayakannan Ayyathurai }
65*bd899cebSVijayakannan Ayyathurai 
66*bd899cebSVijayakannan Ayyathurai static inline void keembay_pwm_update_bits(struct keembay_pwm *priv, u32 mask,
67*bd899cebSVijayakannan Ayyathurai 					   u32 val, u32 offset)
68*bd899cebSVijayakannan Ayyathurai {
69*bd899cebSVijayakannan Ayyathurai 	u32 buff = readl(priv->base + offset);
70*bd899cebSVijayakannan Ayyathurai 
71*bd899cebSVijayakannan Ayyathurai 	buff = u32_replace_bits(buff, val, mask);
72*bd899cebSVijayakannan Ayyathurai 	writel(buff, priv->base + offset);
73*bd899cebSVijayakannan Ayyathurai }
74*bd899cebSVijayakannan Ayyathurai 
75*bd899cebSVijayakannan Ayyathurai static void keembay_pwm_enable(struct keembay_pwm *priv, int ch)
76*bd899cebSVijayakannan Ayyathurai {
77*bd899cebSVijayakannan Ayyathurai 	keembay_pwm_update_bits(priv, KMB_PWM_EN_BIT, 1,
78*bd899cebSVijayakannan Ayyathurai 				KMB_PWM_LEADIN_OFFSET(ch));
79*bd899cebSVijayakannan Ayyathurai }
80*bd899cebSVijayakannan Ayyathurai 
81*bd899cebSVijayakannan Ayyathurai static void keembay_pwm_disable(struct keembay_pwm *priv, int ch)
82*bd899cebSVijayakannan Ayyathurai {
83*bd899cebSVijayakannan Ayyathurai 	keembay_pwm_update_bits(priv, KMB_PWM_EN_BIT, 0,
84*bd899cebSVijayakannan Ayyathurai 				KMB_PWM_LEADIN_OFFSET(ch));
85*bd899cebSVijayakannan Ayyathurai }
86*bd899cebSVijayakannan Ayyathurai 
87*bd899cebSVijayakannan Ayyathurai static void keembay_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
88*bd899cebSVijayakannan Ayyathurai 				  struct pwm_state *state)
89*bd899cebSVijayakannan Ayyathurai {
90*bd899cebSVijayakannan Ayyathurai 	struct keembay_pwm *priv = to_keembay_pwm_dev(chip);
91*bd899cebSVijayakannan Ayyathurai 	unsigned long long high, low;
92*bd899cebSVijayakannan Ayyathurai 	unsigned long clk_rate;
93*bd899cebSVijayakannan Ayyathurai 	u32 highlow;
94*bd899cebSVijayakannan Ayyathurai 
95*bd899cebSVijayakannan Ayyathurai 	clk_rate = clk_get_rate(priv->clk);
96*bd899cebSVijayakannan Ayyathurai 
97*bd899cebSVijayakannan Ayyathurai 	/* Read channel enabled status */
98*bd899cebSVijayakannan Ayyathurai 	highlow = readl(priv->base + KMB_PWM_LEADIN_OFFSET(pwm->hwpwm));
99*bd899cebSVijayakannan Ayyathurai 	if (highlow & KMB_PWM_EN_BIT)
100*bd899cebSVijayakannan Ayyathurai 		state->enabled = true;
101*bd899cebSVijayakannan Ayyathurai 	else
102*bd899cebSVijayakannan Ayyathurai 		state->enabled = false;
103*bd899cebSVijayakannan Ayyathurai 
104*bd899cebSVijayakannan Ayyathurai 	/* Read period and duty cycle */
105*bd899cebSVijayakannan Ayyathurai 	highlow = readl(priv->base + KMB_PWM_HIGHLOW_OFFSET(pwm->hwpwm));
106*bd899cebSVijayakannan Ayyathurai 	low = FIELD_GET(KMB_PWM_LOW_MASK, highlow) * NSEC_PER_SEC;
107*bd899cebSVijayakannan Ayyathurai 	high = FIELD_GET(KMB_PWM_HIGH_MASK, highlow) * NSEC_PER_SEC;
108*bd899cebSVijayakannan Ayyathurai 	state->duty_cycle = DIV_ROUND_UP_ULL(high, clk_rate);
109*bd899cebSVijayakannan Ayyathurai 	state->period = DIV_ROUND_UP_ULL(high + low, clk_rate);
110*bd899cebSVijayakannan Ayyathurai 	state->polarity = PWM_POLARITY_NORMAL;
111*bd899cebSVijayakannan Ayyathurai }
112*bd899cebSVijayakannan Ayyathurai 
113*bd899cebSVijayakannan Ayyathurai static int keembay_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
114*bd899cebSVijayakannan Ayyathurai 			     const struct pwm_state *state)
115*bd899cebSVijayakannan Ayyathurai {
116*bd899cebSVijayakannan Ayyathurai 	struct keembay_pwm *priv = to_keembay_pwm_dev(chip);
117*bd899cebSVijayakannan Ayyathurai 	struct pwm_state current_state;
118*bd899cebSVijayakannan Ayyathurai 	unsigned long long div;
119*bd899cebSVijayakannan Ayyathurai 	unsigned long clk_rate;
120*bd899cebSVijayakannan Ayyathurai 	u32 pwm_count = 0;
121*bd899cebSVijayakannan Ayyathurai 	u16 high, low;
122*bd899cebSVijayakannan Ayyathurai 
123*bd899cebSVijayakannan Ayyathurai 	if (state->polarity != PWM_POLARITY_NORMAL)
124*bd899cebSVijayakannan Ayyathurai 		return -EINVAL;
125*bd899cebSVijayakannan Ayyathurai 
126*bd899cebSVijayakannan Ayyathurai 	/*
127*bd899cebSVijayakannan Ayyathurai 	 * Configure the pwm repeat count as infinite at (15:0) and leadin
128*bd899cebSVijayakannan Ayyathurai 	 * low time as 0 at (30:16), which is in terms of clock cycles.
129*bd899cebSVijayakannan Ayyathurai 	 */
130*bd899cebSVijayakannan Ayyathurai 	keembay_pwm_update_bits(priv, KMB_PWM_LEADIN_MASK, 0,
131*bd899cebSVijayakannan Ayyathurai 				KMB_PWM_LEADIN_OFFSET(pwm->hwpwm));
132*bd899cebSVijayakannan Ayyathurai 
133*bd899cebSVijayakannan Ayyathurai 	keembay_pwm_get_state(chip, pwm, &current_state);
134*bd899cebSVijayakannan Ayyathurai 
135*bd899cebSVijayakannan Ayyathurai 	if (!state->enabled) {
136*bd899cebSVijayakannan Ayyathurai 		if (current_state.enabled)
137*bd899cebSVijayakannan Ayyathurai 			keembay_pwm_disable(priv, pwm->hwpwm);
138*bd899cebSVijayakannan Ayyathurai 		return 0;
139*bd899cebSVijayakannan Ayyathurai 	}
140*bd899cebSVijayakannan Ayyathurai 
141*bd899cebSVijayakannan Ayyathurai 	/*
142*bd899cebSVijayakannan Ayyathurai 	 * The upper 16 bits and lower 16 bits of the KMB_PWM_HIGHLOW_OFFSET
143*bd899cebSVijayakannan Ayyathurai 	 * register contain the high time and low time of waveform accordingly.
144*bd899cebSVijayakannan Ayyathurai 	 * All the values are in terms of clock cycles.
145*bd899cebSVijayakannan Ayyathurai 	 */
146*bd899cebSVijayakannan Ayyathurai 
147*bd899cebSVijayakannan Ayyathurai 	clk_rate = clk_get_rate(priv->clk);
148*bd899cebSVijayakannan Ayyathurai 	div = clk_rate * state->duty_cycle;
149*bd899cebSVijayakannan Ayyathurai 	div = DIV_ROUND_DOWN_ULL(div, NSEC_PER_SEC);
150*bd899cebSVijayakannan Ayyathurai 	if (div > KMB_PWM_COUNT_MAX)
151*bd899cebSVijayakannan Ayyathurai 		return -ERANGE;
152*bd899cebSVijayakannan Ayyathurai 
153*bd899cebSVijayakannan Ayyathurai 	high = div;
154*bd899cebSVijayakannan Ayyathurai 	div = clk_rate * state->period;
155*bd899cebSVijayakannan Ayyathurai 	div = DIV_ROUND_DOWN_ULL(div, NSEC_PER_SEC);
156*bd899cebSVijayakannan Ayyathurai 	div = div - high;
157*bd899cebSVijayakannan Ayyathurai 	if (div > KMB_PWM_COUNT_MAX)
158*bd899cebSVijayakannan Ayyathurai 		return -ERANGE;
159*bd899cebSVijayakannan Ayyathurai 
160*bd899cebSVijayakannan Ayyathurai 	low = div;
161*bd899cebSVijayakannan Ayyathurai 
162*bd899cebSVijayakannan Ayyathurai 	pwm_count = FIELD_PREP(KMB_PWM_HIGH_MASK, high) |
163*bd899cebSVijayakannan Ayyathurai 		    FIELD_PREP(KMB_PWM_LOW_MASK, low);
164*bd899cebSVijayakannan Ayyathurai 
165*bd899cebSVijayakannan Ayyathurai 	writel(pwm_count, priv->base + KMB_PWM_HIGHLOW_OFFSET(pwm->hwpwm));
166*bd899cebSVijayakannan Ayyathurai 
167*bd899cebSVijayakannan Ayyathurai 	if (state->enabled && !current_state.enabled)
168*bd899cebSVijayakannan Ayyathurai 		keembay_pwm_enable(priv, pwm->hwpwm);
169*bd899cebSVijayakannan Ayyathurai 
170*bd899cebSVijayakannan Ayyathurai 	return 0;
171*bd899cebSVijayakannan Ayyathurai }
172*bd899cebSVijayakannan Ayyathurai 
173*bd899cebSVijayakannan Ayyathurai static const struct pwm_ops keembay_pwm_ops = {
174*bd899cebSVijayakannan Ayyathurai 	.owner = THIS_MODULE,
175*bd899cebSVijayakannan Ayyathurai 	.apply = keembay_pwm_apply,
176*bd899cebSVijayakannan Ayyathurai 	.get_state = keembay_pwm_get_state,
177*bd899cebSVijayakannan Ayyathurai };
178*bd899cebSVijayakannan Ayyathurai 
179*bd899cebSVijayakannan Ayyathurai static int keembay_pwm_probe(struct platform_device *pdev)
180*bd899cebSVijayakannan Ayyathurai {
181*bd899cebSVijayakannan Ayyathurai 	struct device *dev = &pdev->dev;
182*bd899cebSVijayakannan Ayyathurai 	struct keembay_pwm *priv;
183*bd899cebSVijayakannan Ayyathurai 	int ret;
184*bd899cebSVijayakannan Ayyathurai 
185*bd899cebSVijayakannan Ayyathurai 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
186*bd899cebSVijayakannan Ayyathurai 	if (!priv)
187*bd899cebSVijayakannan Ayyathurai 		return -ENOMEM;
188*bd899cebSVijayakannan Ayyathurai 
189*bd899cebSVijayakannan Ayyathurai 	priv->clk = devm_clk_get(dev, NULL);
190*bd899cebSVijayakannan Ayyathurai 	if (IS_ERR(priv->clk))
191*bd899cebSVijayakannan Ayyathurai 		return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to get clock\n");
192*bd899cebSVijayakannan Ayyathurai 
193*bd899cebSVijayakannan Ayyathurai 	priv->base = devm_platform_ioremap_resource(pdev, 0);
194*bd899cebSVijayakannan Ayyathurai 	if (IS_ERR(priv->base))
195*bd899cebSVijayakannan Ayyathurai 		return PTR_ERR(priv->base);
196*bd899cebSVijayakannan Ayyathurai 
197*bd899cebSVijayakannan Ayyathurai 	ret = keembay_clk_enable(dev, priv->clk);
198*bd899cebSVijayakannan Ayyathurai 	if (ret)
199*bd899cebSVijayakannan Ayyathurai 		return ret;
200*bd899cebSVijayakannan Ayyathurai 
201*bd899cebSVijayakannan Ayyathurai 	priv->chip.base = -1;
202*bd899cebSVijayakannan Ayyathurai 	priv->chip.dev = dev;
203*bd899cebSVijayakannan Ayyathurai 	priv->chip.ops = &keembay_pwm_ops;
204*bd899cebSVijayakannan Ayyathurai 	priv->chip.npwm = KMB_TOTAL_PWM_CHANNELS;
205*bd899cebSVijayakannan Ayyathurai 
206*bd899cebSVijayakannan Ayyathurai 	ret = pwmchip_add(&priv->chip);
207*bd899cebSVijayakannan Ayyathurai 	if (ret)
208*bd899cebSVijayakannan Ayyathurai 		return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
209*bd899cebSVijayakannan Ayyathurai 
210*bd899cebSVijayakannan Ayyathurai 	platform_set_drvdata(pdev, priv);
211*bd899cebSVijayakannan Ayyathurai 
212*bd899cebSVijayakannan Ayyathurai 	return 0;
213*bd899cebSVijayakannan Ayyathurai }
214*bd899cebSVijayakannan Ayyathurai 
215*bd899cebSVijayakannan Ayyathurai static int keembay_pwm_remove(struct platform_device *pdev)
216*bd899cebSVijayakannan Ayyathurai {
217*bd899cebSVijayakannan Ayyathurai 	struct keembay_pwm *priv = platform_get_drvdata(pdev);
218*bd899cebSVijayakannan Ayyathurai 
219*bd899cebSVijayakannan Ayyathurai 	return pwmchip_remove(&priv->chip);
220*bd899cebSVijayakannan Ayyathurai }
221*bd899cebSVijayakannan Ayyathurai 
222*bd899cebSVijayakannan Ayyathurai static const struct of_device_id keembay_pwm_of_match[] = {
223*bd899cebSVijayakannan Ayyathurai 	{ .compatible = "intel,keembay-pwm" },
224*bd899cebSVijayakannan Ayyathurai 	{ }
225*bd899cebSVijayakannan Ayyathurai };
226*bd899cebSVijayakannan Ayyathurai MODULE_DEVICE_TABLE(of, keembay_pwm_of_match);
227*bd899cebSVijayakannan Ayyathurai 
228*bd899cebSVijayakannan Ayyathurai static struct platform_driver keembay_pwm_driver = {
229*bd899cebSVijayakannan Ayyathurai 	.probe	= keembay_pwm_probe,
230*bd899cebSVijayakannan Ayyathurai 	.remove	= keembay_pwm_remove,
231*bd899cebSVijayakannan Ayyathurai 	.driver	= {
232*bd899cebSVijayakannan Ayyathurai 		.name = "pwm-keembay",
233*bd899cebSVijayakannan Ayyathurai 		.of_match_table = keembay_pwm_of_match,
234*bd899cebSVijayakannan Ayyathurai 	},
235*bd899cebSVijayakannan Ayyathurai };
236*bd899cebSVijayakannan Ayyathurai module_platform_driver(keembay_pwm_driver);
237*bd899cebSVijayakannan Ayyathurai 
238*bd899cebSVijayakannan Ayyathurai MODULE_ALIAS("platform:pwm-keembay");
239*bd899cebSVijayakannan Ayyathurai MODULE_DESCRIPTION("Intel Keem Bay PWM driver");
240*bd899cebSVijayakannan Ayyathurai MODULE_LICENSE("GPL v2");
241