brintos

brintos / linux-shallow public Read only

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