brintos

brintos / linux-shallow public Read only

0
0
Text · 4.8 KiB · d5535d2 Raw
200 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * simple driver for PWM (Pulse Width Modulator) controller4 *5 * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com>6 */7 8#include <linux/bitfield.h>9#include <linux/bitops.h>10#include <linux/clk.h>11#include <linux/delay.h>12#include <linux/err.h>13#include <linux/io.h>14#include <linux/kernel.h>15#include <linux/module.h>16#include <linux/of.h>17#include <linux/platform_device.h>18#include <linux/pwm.h>19#include <linux/slab.h>20 21#define MX1_PWMC			0x00   /* PWM Control Register */22#define MX1_PWMS			0x04   /* PWM Sample Register */23#define MX1_PWMP			0x08   /* PWM Period Register */24 25#define MX1_PWMC_EN			BIT(4)26 27struct pwm_imx1_chip {28	struct clk *clk_ipg;29	struct clk *clk_per;30	void __iomem *mmio_base;31};32 33static inline struct pwm_imx1_chip *to_pwm_imx1_chip(struct pwm_chip *chip)34{35	return pwmchip_get_drvdata(chip);36}37 38static int pwm_imx1_clk_prepare_enable(struct pwm_chip *chip)39{40	struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);41	int ret;42 43	ret = clk_prepare_enable(imx->clk_ipg);44	if (ret)45		return ret;46 47	ret = clk_prepare_enable(imx->clk_per);48	if (ret) {49		clk_disable_unprepare(imx->clk_ipg);50		return ret;51	}52 53	return 0;54}55 56static void pwm_imx1_clk_disable_unprepare(struct pwm_chip *chip)57{58	struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);59 60	clk_disable_unprepare(imx->clk_per);61	clk_disable_unprepare(imx->clk_ipg);62}63 64static int pwm_imx1_config(struct pwm_chip *chip,65			   struct pwm_device *pwm, u64 duty_ns, u64 period_ns)66{67	struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);68	u32 max, p;69 70	/*71	 * The PWM subsystem allows for exact frequencies. However,72	 * I cannot connect a scope on my device to the PWM line and73	 * thus cannot provide the program the PWM controller74	 * exactly. Instead, I'm relying on the fact that the75	 * Bootloader (u-boot or WinCE+haret) has programmed the PWM76	 * function group already. So I'll just modify the PWM sample77	 * register to follow the ratio of duty_ns vs. period_ns78	 * accordingly.79	 *80	 * This is good enough for programming the brightness of81	 * the LCD backlight.82	 *83	 * The real implementation would divide PERCLK[0] first by84	 * both the prescaler (/1 .. /128) and then by CLKSEL85	 * (/2 .. /16).86	 */87	max = readl(imx->mmio_base + MX1_PWMP);88	p = mul_u64_u64_div_u64(max, duty_ns, period_ns);89 90	writel(max - p, imx->mmio_base + MX1_PWMS);91 92	return 0;93}94 95static int pwm_imx1_enable(struct pwm_chip *chip, struct pwm_device *pwm)96{97	struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);98	u32 value;99	int ret;100 101	ret = pwm_imx1_clk_prepare_enable(chip);102	if (ret < 0)103		return ret;104 105	value = readl(imx->mmio_base + MX1_PWMC);106	value |= MX1_PWMC_EN;107	writel(value, imx->mmio_base + MX1_PWMC);108 109	return 0;110}111 112static void pwm_imx1_disable(struct pwm_chip *chip, struct pwm_device *pwm)113{114	struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip);115	u32 value;116 117	value = readl(imx->mmio_base + MX1_PWMC);118	value &= ~MX1_PWMC_EN;119	writel(value, imx->mmio_base + MX1_PWMC);120 121	pwm_imx1_clk_disable_unprepare(chip);122}123 124static int pwm_imx1_apply(struct pwm_chip *chip, struct pwm_device *pwm,125			  const struct pwm_state *state)126{127	int err;128 129	if (state->polarity != PWM_POLARITY_NORMAL)130		return -EINVAL;131 132	if (!state->enabled) {133		if (pwm->state.enabled)134			pwm_imx1_disable(chip, pwm);135 136		return 0;137	}138 139	err = pwm_imx1_config(chip, pwm, state->duty_cycle, state->period);140	if (err)141		return err;142 143	if (!pwm->state.enabled)144		return pwm_imx1_enable(chip, pwm);145 146	return 0;147}148 149static const struct pwm_ops pwm_imx1_ops = {150	.apply = pwm_imx1_apply,151};152 153static const struct of_device_id pwm_imx1_dt_ids[] = {154	{ .compatible = "fsl,imx1-pwm", },155	{ /* sentinel */ }156};157MODULE_DEVICE_TABLE(of, pwm_imx1_dt_ids);158 159static int pwm_imx1_probe(struct platform_device *pdev)160{161	struct pwm_chip *chip;162	struct pwm_imx1_chip *imx;163 164	chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*imx));165	if (IS_ERR(chip))166		return PTR_ERR(chip);167	imx = to_pwm_imx1_chip(chip);168 169	imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");170	if (IS_ERR(imx->clk_ipg))171		return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_ipg),172				     "getting ipg clock failed\n");173 174	imx->clk_per = devm_clk_get(&pdev->dev, "per");175	if (IS_ERR(imx->clk_per))176		return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_per),177				     "failed to get peripheral clock\n");178 179	chip->ops = &pwm_imx1_ops;180 181	imx->mmio_base = devm_platform_ioremap_resource(pdev, 0);182	if (IS_ERR(imx->mmio_base))183		return PTR_ERR(imx->mmio_base);184 185	return devm_pwmchip_add(&pdev->dev, chip);186}187 188static struct platform_driver pwm_imx1_driver = {189	.driver = {190		.name = "pwm-imx1",191		.of_match_table = pwm_imx1_dt_ids,192	},193	.probe = pwm_imx1_probe,194};195module_platform_driver(pwm_imx1_driver);196 197MODULE_DESCRIPTION("i.MX1 and i.MX21 Pulse Width Modulator driver");198MODULE_LICENSE("GPL v2");199MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");200