diff options
Diffstat (limited to 'drivers/pwm/pwm-imx.c')
-rw-r--r-- | drivers/pwm/pwm-imx.c | 194 |
1 files changed, 164 insertions, 30 deletions
diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 1d5242c9cde0..55a3a363d5be 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -5,17 +5,19 @@ * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com> */ -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <linux/err.h> +#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/pwm.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> /* i.MX1 and i.MX21 share the same PWM function block: */ @@ -23,7 +25,7 @@ #define MX1_PWMS 0x04 /* PWM Sample Register */ #define MX1_PWMP 0x08 /* PWM Period Register */ -#define MX1_PWMC_EN (1 << 4) +#define MX1_PWMC_EN BIT(4) /* i.MX27, i.MX31, i.MX35 share the same PWM function block: */ @@ -31,22 +33,62 @@ #define MX3_PWMSR 0x04 /* PWM Status Register */ #define MX3_PWMSAR 0x0C /* PWM Sample Register */ #define MX3_PWMPR 0x10 /* PWM Period Register */ -#define MX3_PWMCR_PRESCALER(x) ((((x) - 1) & 0xFFF) << 4) -#define MX3_PWMCR_STOPEN (1 << 25) -#define MX3_PWMCR_DOZEEN (1 << 24) -#define MX3_PWMCR_WAITEN (1 << 23) -#define MX3_PWMCR_DBGEN (1 << 22) -#define MX3_PWMCR_POUTC (1 << 18) -#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16) -#define MX3_PWMCR_CLKSRC_IPG (1 << 16) -#define MX3_PWMCR_SWR (1 << 3) -#define MX3_PWMCR_EN (1 << 0) -#define MX3_PWMSR_FIFOAV_4WORDS 0x4 -#define MX3_PWMSR_FIFOAV_MASK 0x7 + +#define MX3_PWMCR_FWM GENMASK(27, 26) +#define MX3_PWMCR_STOPEN BIT(25) +#define MX3_PWMCR_DOZEN BIT(24) +#define MX3_PWMCR_WAITEN BIT(23) +#define MX3_PWMCR_DBGEN BIT(22) +#define MX3_PWMCR_BCTR BIT(21) +#define MX3_PWMCR_HCTR BIT(20) + +#define MX3_PWMCR_POUTC GENMASK(19, 18) +#define MX3_PWMCR_POUTC_NORMAL 0 +#define MX3_PWMCR_POUTC_INVERTED 1 +#define MX3_PWMCR_POUTC_OFF 2 + +#define MX3_PWMCR_CLKSRC GENMASK(17, 16) +#define MX3_PWMCR_CLKSRC_OFF 0 +#define MX3_PWMCR_CLKSRC_IPG 1 +#define MX3_PWMCR_CLKSRC_IPG_HIGH 2 +#define MX3_PWMCR_CLKSRC_IPG_32K 3 + +#define MX3_PWMCR_PRESCALER GENMASK(15, 4) + +#define MX3_PWMCR_SWR BIT(3) + +#define MX3_PWMCR_REPEAT GENMASK(2, 1) +#define MX3_PWMCR_REPEAT_1X 0 +#define MX3_PWMCR_REPEAT_2X 1 +#define MX3_PWMCR_REPEAT_4X 2 +#define MX3_PWMCR_REPEAT_8X 3 + +#define MX3_PWMCR_EN BIT(0) + +#define MX3_PWMSR_FWE BIT(6) +#define MX3_PWMSR_CMP BIT(5) +#define MX3_PWMSR_ROV BIT(4) +#define MX3_PWMSR_FE BIT(3) + +#define MX3_PWMSR_FIFOAV GENMASK(2, 0) +#define MX3_PWMSR_FIFOAV_EMPTY 0 +#define MX3_PWMSR_FIFOAV_1WORD 1 +#define MX3_PWMSR_FIFOAV_2WORDS 2 +#define MX3_PWMSR_FIFOAV_3WORDS 3 +#define MX3_PWMSR_FIFOAV_4WORDS 4 + +#define MX3_PWMCR_PRESCALER_SET(x) FIELD_PREP(MX3_PWMCR_PRESCALER, (x) - 1) +#define MX3_PWMCR_PRESCALER_GET(x) (FIELD_GET(MX3_PWMCR_PRESCALER, \ + (x)) + 1) #define MX3_PWM_SWR_LOOP 5 +/* PWMPR register value of 0xffff has the same effect as 0xfffe */ +#define MX3_PWMPR_MAX 0xfffe + struct imx_chip { + struct clk *clk_ipg; + struct clk *clk_per; void __iomem *mmio_base; @@ -56,6 +98,87 @@ struct imx_chip { #define to_imx_chip(chip) container_of(chip, struct imx_chip, chip) +static int imx_pwm_clk_prepare_enable(struct pwm_chip *chip) +{ + struct imx_chip *imx = to_imx_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 imx_pwm_clk_disable_unprepare(struct pwm_chip *chip) +{ + struct imx_chip *imx = to_imx_chip(chip); + + clk_disable_unprepare(imx->clk_per); + clk_disable_unprepare(imx->clk_ipg); +} + +static void imx_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, struct pwm_state *state) +{ + struct imx_chip *imx = to_imx_chip(chip); + u32 period, prescaler, pwm_clk, ret, val; + u64 tmp; + + ret = imx_pwm_clk_prepare_enable(chip); + if (ret < 0) + return; + + val = readl(imx->mmio_base + MX3_PWMCR); + + if (val & MX3_PWMCR_EN) { + state->enabled = true; + ret = imx_pwm_clk_prepare_enable(chip); + if (ret) + return; + } else { + state->enabled = false; + } + + switch (FIELD_GET(MX3_PWMCR_POUTC, val)) { + case MX3_PWMCR_POUTC_NORMAL: + state->polarity = PWM_POLARITY_NORMAL; + break; + case MX3_PWMCR_POUTC_INVERTED: + state->polarity = PWM_POLARITY_INVERSED; + break; + default: + dev_warn(chip->dev, "can't set polarity, output disconnected"); + } + + prescaler = MX3_PWMCR_PRESCALER_GET(val); + pwm_clk = clk_get_rate(imx->clk_per); + pwm_clk = DIV_ROUND_CLOSEST_ULL(pwm_clk, prescaler); + val = readl(imx->mmio_base + MX3_PWMPR); + period = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val; + + /* PWMOUT (Hz) = PWMCLK / (PWMPR + 2) */ + tmp = NSEC_PER_SEC * (u64)(period + 2); + state->period = DIV_ROUND_CLOSEST_ULL(tmp, pwm_clk); + + /* PWMSAR can be read only if PWM is enabled */ + if (state->enabled) { + val = readl(imx->mmio_base + MX3_PWMSAR); + tmp = NSEC_PER_SEC * (u64)(val); + state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, pwm_clk); + } else { + state->duty_cycle = 0; + } + + imx_pwm_clk_disable_unprepare(chip); +} + static int imx_pwm_config_v1(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { @@ -91,7 +214,7 @@ static int imx_pwm_enable_v1(struct pwm_chip *chip, struct pwm_device *pwm) u32 val; int ret; - ret = clk_prepare_enable(imx->clk_per); + ret = imx_pwm_clk_prepare_enable(chip); if (ret < 0) return ret; @@ -111,7 +234,7 @@ static void imx_pwm_disable_v1(struct pwm_chip *chip, struct pwm_device *pwm) val &= ~MX1_PWMC_EN; writel(val, imx->mmio_base + MX1_PWMC); - clk_disable_unprepare(imx->clk_per); + imx_pwm_clk_disable_unprepare(chip); } static void imx_pwm_sw_reset(struct pwm_chip *chip) @@ -142,14 +265,14 @@ static void imx_pwm_wait_fifo_slot(struct pwm_chip *chip, u32 sr; sr = readl(imx->mmio_base + MX3_PWMSR); - fifoav = sr & MX3_PWMSR_FIFOAV_MASK; + fifoav = FIELD_GET(MX3_PWMSR_FIFOAV, sr); if (fifoav == MX3_PWMSR_FIFOAV_4WORDS) { period_ms = DIV_ROUND_UP(pwm_get_period(pwm), NSEC_PER_MSEC); msleep(period_ms); sr = readl(imx->mmio_base + MX3_PWMSR); - if (fifoav == (sr & MX3_PWMSR_FIFOAV_MASK)) + if (fifoav == FIELD_GET(MX3_PWMSR_FIFOAV, sr)) dev_warn(dev, "there is no free FIFO slot\n"); } } @@ -197,7 +320,7 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm, if (cstate.enabled) { imx_pwm_wait_fifo_slot(chip, pwm); } else { - ret = clk_prepare_enable(imx->clk_per); + ret = imx_pwm_clk_prepare_enable(chip); if (ret) return ret; @@ -207,19 +330,20 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm, writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); writel(period_cycles, imx->mmio_base + MX3_PWMPR); - cr = MX3_PWMCR_PRESCALER(prescale) | - MX3_PWMCR_STOPEN | MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | - MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH | - MX3_PWMCR_EN; + cr = MX3_PWMCR_PRESCALER_SET(prescale) | + MX3_PWMCR_STOPEN | MX3_PWMCR_DOZEN | MX3_PWMCR_WAITEN | + FIELD_PREP(MX3_PWMCR_CLKSRC, MX3_PWMCR_CLKSRC_IPG_HIGH) | + MX3_PWMCR_DBGEN | MX3_PWMCR_EN; if (state->polarity == PWM_POLARITY_INVERSED) - cr |= MX3_PWMCR_POUTC; + cr |= FIELD_PREP(MX3_PWMCR_POUTC, + MX3_PWMCR_POUTC_INVERTED); writel(cr, imx->mmio_base + MX3_PWMCR); } else if (cstate.enabled) { writel(0, imx->mmio_base + MX3_PWMCR); - clk_disable_unprepare(imx->clk_per); + imx_pwm_clk_disable_unprepare(chip); } return 0; @@ -234,6 +358,7 @@ static const struct pwm_ops imx_pwm_ops_v1 = { static const struct pwm_ops imx_pwm_ops_v2 = { .apply = imx_pwm_apply_v2, + .get_state = imx_pwm_get_state, .owner = THIS_MODULE, }; @@ -276,6 +401,13 @@ static int imx_pwm_probe(struct platform_device *pdev) if (imx == NULL) return -ENOMEM; + 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)) { dev_err(&pdev->dev, "getting per clock failed with %ld\n", @@ -315,6 +447,8 @@ static int imx_pwm_remove(struct platform_device *pdev) if (imx == NULL) return -ENODEV; + imx_pwm_clk_disable_unprepare(&imx->chip); + return pwmchip_remove(&imx->chip); } |