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