Commit d80f8206 authored by Uwe Kleine-König's avatar Uwe Kleine-König Committed by Thierry Reding

pwm: imx: Split into two drivers

The two PWM implementations called v1 (for i.MX1 and i.MX21) and v2 (for
i.MX27 and later) have nothing in common apart from needing two clocks
named "per" and "ipg" and being integrated in a SoC named i.MX.

So split the file containing the two disjunct drivers into two files and
two complete separate drivers.
Signed-off-by: default avatarUwe Kleine-König <u.kleine-koenig@pengutronix.de>
[thierry.reding@gmail.com: fix a modular build issue]
Signed-off-by: default avatarThierry Reding <thierry.reding@gmail.com>
parent b9a5c60b
...@@ -192,14 +192,23 @@ config PWM_IMG ...@@ -192,14 +192,23 @@ config PWM_IMG
To compile this driver as a module, choose M here: the module To compile this driver as a module, choose M here: the module
will be called pwm-img will be called pwm-img
config PWM_IMX config PWM_IMX1
tristate "i.MX PWM support" tristate "i.MX1 PWM support"
depends on ARCH_MXC depends on ARCH_MXC
help help
Generic PWM framework driver for i.MX. Generic PWM framework driver for i.MX1 and i.MX21
To compile this driver as a module, choose M here: the module To compile this driver as a module, choose M here: the module
will be called pwm-imx. will be called pwm-imx1.
config PWM_IMX27
tristate "i.MX27 PWM support"
depends on ARCH_MXC
help
Generic PWM framework driver for i.MX27 and later i.MX SoCs.
To compile this driver as a module, choose M here: the module
will be called pwm-imx27.
config PWM_JZ4740 config PWM_JZ4740
tristate "Ingenic JZ47xx PWM support" tristate "Ingenic JZ47xx PWM support"
......
...@@ -17,7 +17,8 @@ obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o ...@@ -17,7 +17,8 @@ obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o
obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o
obj-$(CONFIG_PWM_IMG) += pwm-img.o obj-$(CONFIG_PWM_IMG) += pwm-img.o
obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o
obj-$(CONFIG_PWM_IMX27) += pwm-imx27.o
obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o
obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o
obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o
......
// 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>");
...@@ -19,16 +19,6 @@ ...@@ -19,16 +19,6 @@
#include <linux/pwm.h> #include <linux/pwm.h>
#include <linux/slab.h> #include <linux/slab.h>
/* i.MX1 and i.MX21 share the same PWM function block: */
#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)
/* i.MX27, i.MX31, i.MX35 share the same PWM function block: */
#define MX3_PWMCR 0x00 /* PWM Control Register */ #define MX3_PWMCR 0x00 /* PWM Control Register */
#define MX3_PWMSR 0x04 /* PWM Status Register */ #define MX3_PWMSR 0x04 /* PWM Status Register */
#define MX3_PWMSAR 0x0C /* PWM Sample Register */ #define MX3_PWMSAR 0x0C /* PWM Sample Register */
...@@ -86,21 +76,18 @@ ...@@ -86,21 +76,18 @@
/* PWMPR register value of 0xffff has the same effect as 0xfffe */ /* PWMPR register value of 0xffff has the same effect as 0xfffe */
#define MX3_PWMPR_MAX 0xfffe #define MX3_PWMPR_MAX 0xfffe
struct imx_chip { struct pwm_imx27_chip {
struct clk *clk_ipg; struct clk *clk_ipg;
struct clk *clk_per; struct clk *clk_per;
void __iomem *mmio_base; void __iomem *mmio_base;
struct pwm_chip chip; struct pwm_chip chip;
}; };
#define to_imx_chip(chip) container_of(chip, struct imx_chip, chip) #define to_pwm_imx27_chip(chip) container_of(chip, struct pwm_imx27_chip, chip)
static int imx_pwm_clk_prepare_enable(struct pwm_chip *chip) static int pwm_imx27_clk_prepare_enable(struct pwm_chip *chip)
{ {
struct imx_chip *imx = to_imx_chip(chip); struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
int ret; int ret;
ret = clk_prepare_enable(imx->clk_ipg); ret = clk_prepare_enable(imx->clk_ipg);
...@@ -116,22 +103,22 @@ static int imx_pwm_clk_prepare_enable(struct pwm_chip *chip) ...@@ -116,22 +103,22 @@ static int imx_pwm_clk_prepare_enable(struct pwm_chip *chip)
return 0; return 0;
} }
static void imx_pwm_clk_disable_unprepare(struct pwm_chip *chip) static void pwm_imx27_clk_disable_unprepare(struct pwm_chip *chip)
{ {
struct imx_chip *imx = to_imx_chip(chip); struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
clk_disable_unprepare(imx->clk_per); clk_disable_unprepare(imx->clk_per);
clk_disable_unprepare(imx->clk_ipg); clk_disable_unprepare(imx->clk_ipg);
} }
static void imx_pwm_get_state(struct pwm_chip *chip, static void pwm_imx27_get_state(struct pwm_chip *chip,
struct pwm_device *pwm, struct pwm_state *state) struct pwm_device *pwm, struct pwm_state *state)
{ {
struct imx_chip *imx = to_imx_chip(chip); struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
u32 period, prescaler, pwm_clk, ret, val; u32 period, prescaler, pwm_clk, ret, val;
u64 tmp; u64 tmp;
ret = imx_pwm_clk_prepare_enable(chip); ret = pwm_imx27_clk_prepare_enable(chip);
if (ret < 0) if (ret < 0)
return; return;
...@@ -139,7 +126,7 @@ static void imx_pwm_get_state(struct pwm_chip *chip, ...@@ -139,7 +126,7 @@ static void imx_pwm_get_state(struct pwm_chip *chip,
if (val & MX3_PWMCR_EN) { if (val & MX3_PWMCR_EN) {
state->enabled = true; state->enabled = true;
ret = imx_pwm_clk_prepare_enable(chip); ret = pwm_imx27_clk_prepare_enable(chip);
if (ret) if (ret)
return; return;
} else { } else {
...@@ -176,70 +163,12 @@ static void imx_pwm_get_state(struct pwm_chip *chip, ...@@ -176,70 +163,12 @@ static void imx_pwm_get_state(struct pwm_chip *chip,
state->duty_cycle = 0; state->duty_cycle = 0;
} }
imx_pwm_clk_disable_unprepare(chip); pwm_imx27_clk_disable_unprepare(chip);
}
static int imx_pwm_config_v1(struct pwm_chip *chip,
struct pwm_device *pwm, int duty_ns, int period_ns)
{
struct imx_chip *imx = to_imx_chip(chip);
/*
* 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).
*/
u32 max = readl(imx->mmio_base + MX1_PWMP);
u32 p = max * duty_ns / period_ns;
writel(max - p, imx->mmio_base + MX1_PWMS);
return 0;
}
static int imx_pwm_enable_v1(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct imx_chip *imx = to_imx_chip(chip);
u32 val;
int ret;
ret = imx_pwm_clk_prepare_enable(chip);
if (ret < 0)
return ret;
val = readl(imx->mmio_base + MX1_PWMC);
val |= MX1_PWMC_EN;
writel(val, imx->mmio_base + MX1_PWMC);
return 0;
}
static void imx_pwm_disable_v1(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct imx_chip *imx = to_imx_chip(chip);
u32 val;
val = readl(imx->mmio_base + MX1_PWMC);
val &= ~MX1_PWMC_EN;
writel(val, imx->mmio_base + MX1_PWMC);
imx_pwm_clk_disable_unprepare(chip);
} }
static void imx_pwm_sw_reset(struct pwm_chip *chip) static void pwm_imx27_sw_reset(struct pwm_chip *chip)
{ {
struct imx_chip *imx = to_imx_chip(chip); struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
struct device *dev = chip->dev; struct device *dev = chip->dev;
int wait_count = 0; int wait_count = 0;
u32 cr; u32 cr;
...@@ -255,10 +184,10 @@ static void imx_pwm_sw_reset(struct pwm_chip *chip) ...@@ -255,10 +184,10 @@ static void imx_pwm_sw_reset(struct pwm_chip *chip)
dev_warn(dev, "software reset timeout\n"); dev_warn(dev, "software reset timeout\n");
} }
static void imx_pwm_wait_fifo_slot(struct pwm_chip *chip, static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip,
struct pwm_device *pwm) struct pwm_device *pwm)
{ {
struct imx_chip *imx = to_imx_chip(chip); struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
struct device *dev = chip->dev; struct device *dev = chip->dev;
unsigned int period_ms; unsigned int period_ms;
int fifoav; int fifoav;
...@@ -277,11 +206,11 @@ static void imx_pwm_wait_fifo_slot(struct pwm_chip *chip, ...@@ -277,11 +206,11 @@ static void imx_pwm_wait_fifo_slot(struct pwm_chip *chip,
} }
} }
static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm, static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state) struct pwm_state *state)
{ {
unsigned long period_cycles, duty_cycles, prescale; unsigned long period_cycles, duty_cycles, prescale;
struct imx_chip *imx = to_imx_chip(chip); struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
struct pwm_state cstate; struct pwm_state cstate;
unsigned long long c; unsigned long long c;
int ret; int ret;
...@@ -318,13 +247,13 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm, ...@@ -318,13 +247,13 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm,
* enabled. * enabled.
*/ */
if (cstate.enabled) { if (cstate.enabled) {
imx_pwm_wait_fifo_slot(chip, pwm); pwm_imx27_wait_fifo_slot(chip, pwm);
} else { } else {
ret = imx_pwm_clk_prepare_enable(chip); ret = pwm_imx27_clk_prepare_enable(chip);
if (ret) if (ret)
return ret; return ret;
imx_pwm_sw_reset(chip); pwm_imx27_sw_reset(chip);
} }
writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);
...@@ -343,59 +272,29 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm, ...@@ -343,59 +272,29 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm,
} else if (cstate.enabled) { } else if (cstate.enabled) {
writel(0, imx->mmio_base + MX3_PWMCR); writel(0, imx->mmio_base + MX3_PWMCR);
imx_pwm_clk_disable_unprepare(chip); pwm_imx27_clk_disable_unprepare(chip);
} }
return 0; return 0;
} }
static const struct pwm_ops imx_pwm_ops_v1 = { static const struct pwm_ops pwm_imx27_ops = {
.enable = imx_pwm_enable_v1, .apply = pwm_imx27_apply,
.disable = imx_pwm_disable_v1, .get_state = pwm_imx27_get_state,
.config = imx_pwm_config_v1,
.owner = THIS_MODULE,
};
static const struct pwm_ops imx_pwm_ops_v2 = {
.apply = imx_pwm_apply_v2,
.get_state = imx_pwm_get_state,
.owner = THIS_MODULE, .owner = THIS_MODULE,
}; };
struct imx_pwm_data { static const struct of_device_id pwm_imx27_dt_ids[] = {
bool polarity_supported; { .compatible = "fsl,imx27-pwm", },
const struct pwm_ops *ops;
};
static struct imx_pwm_data imx_pwm_data_v1 = {
.ops = &imx_pwm_ops_v1,
};
static struct imx_pwm_data imx_pwm_data_v2 = {
.polarity_supported = true,
.ops = &imx_pwm_ops_v2,
};
static const struct of_device_id imx_pwm_dt_ids[] = {
{ .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, },
{ .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, },
{ /* sentinel */ } { /* sentinel */ }
}; };
MODULE_DEVICE_TABLE(of, imx_pwm_dt_ids); MODULE_DEVICE_TABLE(of, pwm_imx27_dt_ids);
static int imx_pwm_probe(struct platform_device *pdev) static int pwm_imx27_probe(struct platform_device *pdev)
{ {
const struct of_device_id *of_id = struct pwm_imx27_chip *imx;
of_match_device(imx_pwm_dt_ids, &pdev->dev);
const struct imx_pwm_data *data;
struct imx_chip *imx;
struct resource *r; struct resource *r;
if (!of_id)
return -ENODEV;
data = of_id->data;
imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
if (imx == NULL) if (imx == NULL)
return -ENOMEM; return -ENOMEM;
...@@ -421,16 +320,13 @@ static int imx_pwm_probe(struct platform_device *pdev) ...@@ -421,16 +320,13 @@ static int imx_pwm_probe(struct platform_device *pdev)
return ret; return ret;
} }
imx->chip.ops = data->ops; imx->chip.ops = &pwm_imx27_ops;
imx->chip.dev = &pdev->dev; imx->chip.dev = &pdev->dev;
imx->chip.base = -1; imx->chip.base = -1;
imx->chip.npwm = 1; imx->chip.npwm = 1;
if (data->polarity_supported) { imx->chip.of_xlate = of_pwm_xlate_with_flags;
dev_dbg(&pdev->dev, "PWM supports output inversion\n"); imx->chip.of_pwm_n_cells = 3;
imx->chip.of_xlate = of_pwm_xlate_with_flags;
imx->chip.of_pwm_n_cells = 3;
}
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
imx->mmio_base = devm_ioremap_resource(&pdev->dev, r); imx->mmio_base = devm_ioremap_resource(&pdev->dev, r);
...@@ -440,26 +336,25 @@ static int imx_pwm_probe(struct platform_device *pdev) ...@@ -440,26 +336,25 @@ static int imx_pwm_probe(struct platform_device *pdev)
return pwmchip_add(&imx->chip); return pwmchip_add(&imx->chip);
} }
static int imx_pwm_remove(struct platform_device *pdev) static int pwm_imx27_remove(struct platform_device *pdev)
{ {
struct imx_chip *imx; struct pwm_imx27_chip *imx;
imx = platform_get_drvdata(pdev); imx = platform_get_drvdata(pdev);
imx_pwm_clk_disable_unprepare(&imx->chip); pwm_imx27_clk_disable_unprepare(&imx->chip);
return pwmchip_remove(&imx->chip); return pwmchip_remove(&imx->chip);
} }
static struct platform_driver imx_pwm_driver = { static struct platform_driver imx_pwm_driver = {
.driver = { .driver = {
.name = "imx-pwm", .name = "pwm-imx27",
.of_match_table = imx_pwm_dt_ids, .of_match_table = pwm_imx27_dt_ids,
}, },
.probe = imx_pwm_probe, .probe = pwm_imx27_probe,
.remove = imx_pwm_remove, .remove = pwm_imx27_remove,
}; };
module_platform_driver(imx_pwm_driver); module_platform_driver(imx_pwm_driver);
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment