diff options
Diffstat (limited to 'drivers/pwm/pwm-imx1.c')
| -rw-r--r-- | drivers/pwm/pwm-imx1.c | 199 | 
1 files changed, 199 insertions, 0 deletions
| diff --git a/drivers/pwm/pwm-imx1.c b/drivers/pwm/pwm-imx1.c new file mode 100644 index 000000000000..f8b2c2e001a7 --- /dev/null +++ b/drivers/pwm/pwm-imx1.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * simple driver for PWM (Pulse Width Modulator) controller + * + * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com> + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#define MX1_PWMC			0x00   /* PWM Control Register */ +#define MX1_PWMS			0x04   /* PWM Sample Register */ +#define MX1_PWMP			0x08   /* PWM Period Register */ + +#define MX1_PWMC_EN			BIT(4) + +struct pwm_imx1_chip { +	struct clk *clk_ipg; +	struct clk *clk_per; +	void __iomem *mmio_base; +	struct pwm_chip chip; +}; + +#define to_pwm_imx1_chip(chip)	container_of(chip, struct pwm_imx1_chip, chip) + +static int pwm_imx1_clk_prepare_enable(struct pwm_chip *chip) +{ +	struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); +	int ret; + +	ret = clk_prepare_enable(imx->clk_ipg); +	if (ret) +		return ret; + +	ret = clk_prepare_enable(imx->clk_per); +	if (ret) { +		clk_disable_unprepare(imx->clk_ipg); +		return ret; +	} + +	return 0; +} + +static void pwm_imx1_clk_disable_unprepare(struct pwm_chip *chip) +{ +	struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); + +	clk_disable_unprepare(imx->clk_per); +	clk_disable_unprepare(imx->clk_ipg); +} + +static int pwm_imx1_config(struct pwm_chip *chip, +			   struct pwm_device *pwm, int duty_ns, int period_ns) +{ +	struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); +	u32 max, p; + +	/* +	 * The PWM subsystem allows for exact frequencies. However, +	 * I cannot connect a scope on my device to the PWM line and +	 * thus cannot provide the program the PWM controller +	 * exactly. Instead, I'm relying on the fact that the +	 * Bootloader (u-boot or WinCE+haret) has programmed the PWM +	 * function group already. So I'll just modify the PWM sample +	 * register to follow the ratio of duty_ns vs. period_ns +	 * accordingly. +	 * +	 * This is good enough for programming the brightness of +	 * the LCD backlight. +	 * +	 * The real implementation would divide PERCLK[0] first by +	 * both the prescaler (/1 .. /128) and then by CLKSEL +	 * (/2 .. /16). +	 */ +	max = readl(imx->mmio_base + MX1_PWMP); +	p = max * duty_ns / period_ns; + +	writel(max - p, imx->mmio_base + MX1_PWMS); + +	return 0; +} + +static int pwm_imx1_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); +	u32 value; +	int ret; + +	ret = pwm_imx1_clk_prepare_enable(chip); +	if (ret < 0) +		return ret; + +	value = readl(imx->mmio_base + MX1_PWMC); +	value |= MX1_PWMC_EN; +	writel(value, imx->mmio_base + MX1_PWMC); + +	return 0; +} + +static void pwm_imx1_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct pwm_imx1_chip *imx = to_pwm_imx1_chip(chip); +	u32 value; + +	value = readl(imx->mmio_base + MX1_PWMC); +	value &= ~MX1_PWMC_EN; +	writel(value, imx->mmio_base + MX1_PWMC); + +	pwm_imx1_clk_disable_unprepare(chip); +} + +static const struct pwm_ops pwm_imx1_ops = { +	.enable = pwm_imx1_enable, +	.disable = pwm_imx1_disable, +	.config = pwm_imx1_config, +	.owner = THIS_MODULE, +}; + +static const struct of_device_id pwm_imx1_dt_ids[] = { +	{ .compatible = "fsl,imx1-pwm", }, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pwm_imx1_dt_ids); + +static int pwm_imx1_probe(struct platform_device *pdev) +{ +	struct pwm_imx1_chip *imx; +	struct resource *r; + +	imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); +	if (!imx) +		return -ENOMEM; + +	platform_set_drvdata(pdev, imx); + +	imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); +	if (IS_ERR(imx->clk_ipg)) { +		dev_err(&pdev->dev, "getting ipg clock failed with %ld\n", +				PTR_ERR(imx->clk_ipg)); +		return PTR_ERR(imx->clk_ipg); +	} + +	imx->clk_per = devm_clk_get(&pdev->dev, "per"); +	if (IS_ERR(imx->clk_per)) { +		int ret = PTR_ERR(imx->clk_per); + +		if (ret != -EPROBE_DEFER) +			dev_err(&pdev->dev, +				"failed to get peripheral clock: %d\n", +				ret); + +		return ret; +	} + +	imx->chip.ops = &pwm_imx1_ops; +	imx->chip.dev = &pdev->dev; +	imx->chip.base = -1; +	imx->chip.npwm = 1; + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	imx->mmio_base = devm_ioremap_resource(&pdev->dev, r); +	if (IS_ERR(imx->mmio_base)) +		return PTR_ERR(imx->mmio_base); + +	return pwmchip_add(&imx->chip); +} + +static int pwm_imx1_remove(struct platform_device *pdev) +{ +	struct pwm_imx1_chip *imx = platform_get_drvdata(pdev); + +	pwm_imx1_clk_disable_unprepare(&imx->chip); + +	return pwmchip_remove(&imx->chip); +} + +static struct platform_driver pwm_imx1_driver = { +	.driver = { +		.name = "pwm-imx1", +		.of_match_table = pwm_imx1_dt_ids, +	}, +	.probe = pwm_imx1_probe, +	.remove = pwm_imx1_remove, +}; +module_platform_driver(pwm_imx1_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); | 

