whiterose

linux unikernel
Log | Files | Refs | README | LICENSE | git clone https://git.ne02ptzero.me/git/whiterose

commit add8462a60421ca1b03a6864e295d22de532a5e7
parent 3a186d38561d2844072829c6c0811e407c6ec1aa
Author: Linus Torvalds <torvalds@linux-foundation.org>
Date:   Wed, 13 Mar 2019 10:01:10 -0700

Merge tag 'pwm/for-5.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm

Pull pwm updates from Thierry Reding:
 "The changes for this cycle are across the board.

  The bulk of it is cleanups, but there's also new device support in
  some drivers as well as more conversions to the atomic API"

* tag 'pwm/for-5.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (24 commits)
  pwm: atmel: Remove useless symbolic definitions
  pwm: bcm-kona: Update macros to remove braces around numbers
  pwm: imx27: Only enable the clocks once in .get_state()
  pwm: rcar: Improve calculation of divider
  pwm: rcar: Remove legacy APIs
  pwm: rcar: Use "atomic" API on rcar_pwm_resume()
  pwm: rcar: Add support "atomic" API
  pwm: atmel: Add support for SAM9X60's PWM controller
  pwm: atmel: Add PWM binding for SAM9X60
  pwm: atmel: Rename objects of type atmel_pwm_data
  pwm: atmel: Add support for controllers with 32 bit counters
  pwm: atmel: Add struct atmel_pwm_data
  pwm: Add MediaTek MT8183 display PWM driver support
  pwm: hibvt: Add hi3559v100 support
  dt-bindings: pwm: hibvt: Add hi3559v100 support
  pwm: hibvt: Use individual struct per of-data
  pwm: imx: Signedness bug in imx_pwm_get_state()
  pwm: imx: Split into two drivers
  pwm: imx: Don't print an error on -EPROBE_DEFER
  pwm: imx: Set driver data earlier simplifying the end of ->probe()
  ...

Diffstat:
MDocumentation/devicetree/bindings/pwm/atmel-pwm.txt | 1+
MDocumentation/devicetree/bindings/pwm/pwm-hibvt.txt | 2++
Mdrivers/pwm/Kconfig | 17+++++++++++++----
Mdrivers/pwm/Makefile | 3++-
Mdrivers/pwm/core.c | 10+++++-----
Mdrivers/pwm/pwm-atmel.c | 111++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mdrivers/pwm/pwm-bcm-kona.c | 16++++++++--------
Mdrivers/pwm/pwm-hibvt.c | 44++++++++++++++++++++++++++++++++++++++------
Ddrivers/pwm/pwm-imx.c | 467-------------------------------------------------------------------------------
Adrivers/pwm/pwm-imx1.c | 199+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adrivers/pwm/pwm-imx27.c | 359+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdrivers/pwm/pwm-mtk-disp.c | 11+++++++++++
Mdrivers/pwm/pwm-rcar.c | 99++++++++++++++++++++++++++++++++++++++-----------------------------------------
Minclude/linux/pwm.h | 37++++++++++++++++++-------------------
14 files changed, 775 insertions(+), 601 deletions(-)

diff --git a/Documentation/devicetree/bindings/pwm/atmel-pwm.txt b/Documentation/devicetree/bindings/pwm/atmel-pwm.txt @@ -5,6 +5,7 @@ Required properties: - "atmel,at91sam9rl-pwm" - "atmel,sama5d3-pwm" - "atmel,sama5d2-pwm" + - "microchip,sam9x60-pwm" - reg: physical base address and length of the controller's registers - #pwm-cells: Should be 3. See pwm.txt in this directory for a description of the cells format. diff --git a/Documentation/devicetree/bindings/pwm/pwm-hibvt.txt b/Documentation/devicetree/bindings/pwm/pwm-hibvt.txt @@ -5,6 +5,8 @@ Required properties: The SoC specific strings supported including: "hisilicon,hi3516cv300-pwm" "hisilicon,hi3519v100-pwm" + "hisilicon,hi3559v100-shub-pwm" + "hisilicon,hi3559v100-pwm - reg: physical base address and length of the controller's registers. - clocks: phandle and clock specifier of the PWM reference clock. - resets: phandle and reset specifier for the PWM controller reset. diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig @@ -192,14 +192,23 @@ config PWM_IMG To compile this driver as a module, choose M here: the module will be called pwm-img -config PWM_IMX - tristate "i.MX PWM support" +config PWM_IMX1 + tristate "i.MX1 PWM support" depends on ARCH_MXC 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 - 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 tristate "Ingenic JZ47xx PWM support" diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile @@ -17,7 +17,8 @@ obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.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_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c @@ -472,7 +472,10 @@ int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state) state->duty_cycle > state->period) return -EINVAL; - if (!memcmp(state, &pwm->state, sizeof(*state))) + if (state->period == pwm->state.period && + state->duty_cycle == pwm->state.duty_cycle && + state->polarity == pwm->state.polarity && + state->enabled == pwm->state.enabled) return 0; if (pwm->chip->ops->apply) { @@ -1033,10 +1036,7 @@ static int pwm_seq_show(struct seq_file *s, void *v) dev_name(chip->dev), chip->npwm, (chip->npwm != 1) ? "s" : ""); - if (chip->ops->dbg_show) - chip->ops->dbg_show(chip, s); - else - pwm_dbg_show(chip, s); + pwm_dbg_show(chip, s); return 0; } diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c @@ -48,16 +48,6 @@ #define PWMV2_CPRD 0x0C #define PWMV2_CPRDUPD 0x10 -/* - * Max value for duty and period - * - * Although the duty and period register is 32 bit, - * however only the LSB 16 bits are significant. - */ -#define PWM_MAX_DTY 0xFFFF -#define PWM_MAX_PRD 0xFFFF -#define PRD_MAX_PRES 10 - struct atmel_pwm_registers { u8 period; u8 period_upd; @@ -65,11 +55,21 @@ struct atmel_pwm_registers { u8 duty_upd; }; +struct atmel_pwm_config { + u32 max_period; + u32 max_pres; +}; + +struct atmel_pwm_data { + struct atmel_pwm_registers regs; + struct atmel_pwm_config cfg; +}; + struct atmel_pwm_chip { struct pwm_chip chip; struct clk *clk; void __iomem *base; - const struct atmel_pwm_registers *regs; + const struct atmel_pwm_data *data; unsigned int updated_pwms; /* ISR is cleared when read, ensure only one thread does that */ @@ -121,10 +121,10 @@ static int atmel_pwm_calculate_cprd_and_pres(struct pwm_chip *chip, cycles *= clk_get_rate(atmel_pwm->clk); do_div(cycles, NSEC_PER_SEC); - for (*pres = 0; cycles > PWM_MAX_PRD; cycles >>= 1) + for (*pres = 0; cycles > atmel_pwm->data->cfg.max_period; cycles >>= 1) (*pres)++; - if (*pres > PRD_MAX_PRES) { + if (*pres > atmel_pwm->data->cfg.max_pres) { dev_err(chip->dev, "pres exceeds the maximum value\n"); return -EINVAL; } @@ -150,15 +150,15 @@ static void atmel_pwm_update_cdty(struct pwm_chip *chip, struct pwm_device *pwm, struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); u32 val; - if (atmel_pwm->regs->duty_upd == - atmel_pwm->regs->period_upd) { + if (atmel_pwm->data->regs.duty_upd == + atmel_pwm->data->regs.period_upd) { val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR); val &= ~PWM_CMR_UPD_CDTY; atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val); } atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, - atmel_pwm->regs->duty_upd, cdty); + atmel_pwm->data->regs.duty_upd, cdty); } static void atmel_pwm_set_cprd_cdty(struct pwm_chip *chip, @@ -168,9 +168,9 @@ static void atmel_pwm_set_cprd_cdty(struct pwm_chip *chip, struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, - atmel_pwm->regs->duty, cdty); + atmel_pwm->data->regs.duty, cdty); atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, - atmel_pwm->regs->period, cprd); + atmel_pwm->data->regs.period, cprd); } static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm, @@ -225,7 +225,7 @@ static int atmel_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, cstate.polarity == state->polarity && cstate.period == state->period) { cprd = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, - atmel_pwm->regs->period); + atmel_pwm->data->regs.period); atmel_pwm_calculate_cdty(state, cprd, &cdty); atmel_pwm_update_cdty(chip, pwm, cdty); return 0; @@ -277,27 +277,55 @@ static const struct pwm_ops atmel_pwm_ops = { .owner = THIS_MODULE, }; -static const struct atmel_pwm_registers atmel_pwm_regs_v1 = { - .period = PWMV1_CPRD, - .period_upd = PWMV1_CUPD, - .duty = PWMV1_CDTY, - .duty_upd = PWMV1_CUPD, +static const struct atmel_pwm_data atmel_sam9rl_pwm_data = { + .regs = { + .period = PWMV1_CPRD, + .period_upd = PWMV1_CUPD, + .duty = PWMV1_CDTY, + .duty_upd = PWMV1_CUPD, + }, + .cfg = { + /* 16 bits to keep period and duty. */ + .max_period = 0xffff, + .max_pres = 10, + }, +}; + +static const struct atmel_pwm_data atmel_sama5_pwm_data = { + .regs = { + .period = PWMV2_CPRD, + .period_upd = PWMV2_CPRDUPD, + .duty = PWMV2_CDTY, + .duty_upd = PWMV2_CDTYUPD, + }, + .cfg = { + /* 16 bits to keep period and duty. */ + .max_period = 0xffff, + .max_pres = 10, + }, }; -static const struct atmel_pwm_registers atmel_pwm_regs_v2 = { - .period = PWMV2_CPRD, - .period_upd = PWMV2_CPRDUPD, - .duty = PWMV2_CDTY, - .duty_upd = PWMV2_CDTYUPD, +static const struct atmel_pwm_data mchp_sam9x60_pwm_data = { + .regs = { + .period = PWMV1_CPRD, + .period_upd = PWMV1_CUPD, + .duty = PWMV1_CDTY, + .duty_upd = PWMV1_CUPD, + }, + .cfg = { + /* 32 bits to keep period and duty. */ + .max_period = 0xffffffff, + .max_pres = 10, + }, }; static const struct platform_device_id atmel_pwm_devtypes[] = { { .name = "at91sam9rl-pwm", - .driver_data = (kernel_ulong_t)&atmel_pwm_regs_v1, + .driver_data = (kernel_ulong_t)&atmel_sam9rl_pwm_data, }, { .name = "sama5d3-pwm", - .driver_data = (kernel_ulong_t)&atmel_pwm_regs_v2, + .driver_data = (kernel_ulong_t)&atmel_sama5_pwm_data, }, { /* sentinel */ }, @@ -307,20 +335,23 @@ MODULE_DEVICE_TABLE(platform, atmel_pwm_devtypes); static const struct of_device_id atmel_pwm_dt_ids[] = { { .compatible = "atmel,at91sam9rl-pwm", - .data = &atmel_pwm_regs_v1, + .data = &atmel_sam9rl_pwm_data, }, { .compatible = "atmel,sama5d3-pwm", - .data = &atmel_pwm_regs_v2, + .data = &atmel_sama5_pwm_data, }, { .compatible = "atmel,sama5d2-pwm", - .data = &atmel_pwm_regs_v2, + .data = &atmel_sama5_pwm_data, + }, { + .compatible = "microchip,sam9x60-pwm", + .data = &mchp_sam9x60_pwm_data, }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids); -static inline const struct atmel_pwm_registers * +static inline const struct atmel_pwm_data * atmel_pwm_get_driver_data(struct platform_device *pdev) { const struct platform_device_id *id; @@ -330,18 +361,18 @@ atmel_pwm_get_driver_data(struct platform_device *pdev) id = platform_get_device_id(pdev); - return (struct atmel_pwm_registers *)id->driver_data; + return (struct atmel_pwm_data *)id->driver_data; } static int atmel_pwm_probe(struct platform_device *pdev) { - const struct atmel_pwm_registers *regs; + const struct atmel_pwm_data *data; struct atmel_pwm_chip *atmel_pwm; struct resource *res; int ret; - regs = atmel_pwm_get_driver_data(pdev); - if (!regs) + data = atmel_pwm_get_driver_data(pdev); + if (!data) return -ENODEV; atmel_pwm = devm_kzalloc(&pdev->dev, sizeof(*atmel_pwm), GFP_KERNEL); @@ -373,7 +404,7 @@ static int atmel_pwm_probe(struct platform_device *pdev) atmel_pwm->chip.base = -1; atmel_pwm->chip.npwm = 4; - atmel_pwm->regs = regs; + atmel_pwm->data = data; atmel_pwm->updated_pwms = 0; mutex_init(&atmel_pwm->isr_lock); diff --git a/drivers/pwm/pwm-bcm-kona.c b/drivers/pwm/pwm-bcm-kona.c @@ -45,25 +45,25 @@ * high or low depending on its state at that exact instant. */ -#define PWM_CONTROL_OFFSET (0x00000000) +#define PWM_CONTROL_OFFSET 0x00000000 #define PWM_CONTROL_SMOOTH_SHIFT(chan) (24 + (chan)) #define PWM_CONTROL_TYPE_SHIFT(chan) (16 + (chan)) #define PWM_CONTROL_POLARITY_SHIFT(chan) (8 + (chan)) #define PWM_CONTROL_TRIGGER_SHIFT(chan) (chan) -#define PRESCALE_OFFSET (0x00000004) +#define PRESCALE_OFFSET 0x00000004 #define PRESCALE_SHIFT(chan) ((chan) << 2) #define PRESCALE_MASK(chan) (0x7 << PRESCALE_SHIFT(chan)) -#define PRESCALE_MIN (0x00000000) -#define PRESCALE_MAX (0x00000007) +#define PRESCALE_MIN 0x00000000 +#define PRESCALE_MAX 0x00000007 #define PERIOD_COUNT_OFFSET(chan) (0x00000008 + ((chan) << 3)) -#define PERIOD_COUNT_MIN (0x00000002) -#define PERIOD_COUNT_MAX (0x00ffffff) +#define PERIOD_COUNT_MIN 0x00000002 +#define PERIOD_COUNT_MAX 0x00ffffff #define DUTY_CYCLE_HIGH_OFFSET(chan) (0x0000000c + ((chan) << 3)) -#define DUTY_CYCLE_HIGH_MIN (0x00000000) -#define DUTY_CYCLE_HIGH_MAX (0x00ffffff) +#define DUTY_CYCLE_HIGH_MIN 0x00000000 +#define DUTY_CYCLE_HIGH_MAX 0x00ffffff struct kona_pwmc { struct pwm_chip chip; diff --git a/drivers/pwm/pwm-hibvt.c b/drivers/pwm/pwm-hibvt.c @@ -49,15 +49,30 @@ struct hibvt_pwm_chip { struct clk *clk; void __iomem *base; struct reset_control *rstc; + const struct hibvt_pwm_soc *soc; }; struct hibvt_pwm_soc { u32 num_pwms; + bool quirk_force_enable; }; -static const struct hibvt_pwm_soc pwm_soc[2] = { - { .num_pwms = 4 }, - { .num_pwms = 8 }, +static const struct hibvt_pwm_soc hi3516cv300_soc_info = { + .num_pwms = 4, +}; + +static const struct hibvt_pwm_soc hi3519v100_soc_info = { + .num_pwms = 8, +}; + +static const struct hibvt_pwm_soc hi3559v100_shub_soc_info = { + .num_pwms = 8, + .quirk_force_enable = true, +}; + +static const struct hibvt_pwm_soc hi3559v100_soc_info = { + .num_pwms = 2, + .quirk_force_enable = true, }; static inline struct hibvt_pwm_chip *to_hibvt_pwm_chip(struct pwm_chip *chip) @@ -148,13 +163,23 @@ static void hibvt_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, static int hibvt_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { + struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip); + if (state->polarity != pwm->state.polarity) hibvt_pwm_set_polarity(chip, pwm, state->polarity); if (state->period != pwm->state.period || - state->duty_cycle != pwm->state.duty_cycle) + state->duty_cycle != pwm->state.duty_cycle) { hibvt_pwm_config(chip, pwm, state->duty_cycle, state->period); + /* + * Some implementations require the PWM to be enabled twice + * each time the duty cycle is refreshed. + */ + if (hi_pwm_chip->soc->quirk_force_enable && state->enabled) + hibvt_pwm_enable(chip, pwm); + } + if (state->enabled != pwm->state.enabled) { if (state->enabled) hibvt_pwm_enable(chip, pwm); @@ -198,6 +223,7 @@ static int hibvt_pwm_probe(struct platform_device *pdev) pwm_chip->chip.npwm = soc->num_pwms; pwm_chip->chip.of_xlate = of_pwm_xlate_with_flags; pwm_chip->chip.of_pwm_n_cells = 3; + pwm_chip->soc = soc; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); pwm_chip->base = devm_ioremap_resource(&pdev->dev, res); @@ -250,8 +276,14 @@ static int hibvt_pwm_remove(struct platform_device *pdev) } static const struct of_device_id hibvt_pwm_of_match[] = { - { .compatible = "hisilicon,hi3516cv300-pwm", .data = &pwm_soc[0] }, - { .compatible = "hisilicon,hi3519v100-pwm", .data = &pwm_soc[1] }, + { .compatible = "hisilicon,hi3516cv300-pwm", + .data = &hi3516cv300_soc_info }, + { .compatible = "hisilicon,hi3519v100-pwm", + .data = &hi3519v100_soc_info }, + { .compatible = "hisilicon,hi3559v100-shub-pwm", + .data = &hi3559v100_shub_soc_info }, + { .compatible = "hisilicon,hi3559v100-pwm", + .data = &hi3559v100_soc_info }, { } }; MODULE_DEVICE_TABLE(of, hibvt_pwm_of_match); diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c @@ -1,467 +0,0 @@ -// 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> - -/* 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_PWMSR 0x04 /* PWM Status Register */ -#define MX3_PWMSAR 0x0C /* PWM Sample Register */ -#define MX3_PWMPR 0x10 /* PWM Period Register */ - -#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; - - struct pwm_chip 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) -{ - 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) -{ - struct imx_chip *imx = to_imx_chip(chip); - struct device *dev = chip->dev; - int wait_count = 0; - u32 cr; - - writel(MX3_PWMCR_SWR, imx->mmio_base + MX3_PWMCR); - do { - usleep_range(200, 1000); - cr = readl(imx->mmio_base + MX3_PWMCR); - } while ((cr & MX3_PWMCR_SWR) && - (wait_count++ < MX3_PWM_SWR_LOOP)); - - if (cr & MX3_PWMCR_SWR) - dev_warn(dev, "software reset timeout\n"); -} - -static void imx_pwm_wait_fifo_slot(struct pwm_chip *chip, - struct pwm_device *pwm) -{ - struct imx_chip *imx = to_imx_chip(chip); - struct device *dev = chip->dev; - unsigned int period_ms; - int fifoav; - u32 sr; - - sr = readl(imx->mmio_base + MX3_PWMSR); - 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 == FIELD_GET(MX3_PWMSR_FIFOAV, sr)) - dev_warn(dev, "there is no free FIFO slot\n"); - } -} - -static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm, - struct pwm_state *state) -{ - unsigned long period_cycles, duty_cycles, prescale; - struct imx_chip *imx = to_imx_chip(chip); - struct pwm_state cstate; - unsigned long long c; - int ret; - u32 cr; - - pwm_get_state(pwm, &cstate); - - if (state->enabled) { - c = clk_get_rate(imx->clk_per); - c *= state->period; - - do_div(c, 1000000000); - period_cycles = c; - - prescale = period_cycles / 0x10000 + 1; - - period_cycles /= prescale; - c = (unsigned long long)period_cycles * state->duty_cycle; - do_div(c, state->period); - duty_cycles = c; - - /* - * according to imx pwm RM, the real period value should be - * PERIOD value in PWMPR plus 2. - */ - if (period_cycles > 2) - period_cycles -= 2; - else - period_cycles = 0; - - /* - * Wait for a free FIFO slot if the PWM is already enabled, and - * flush the FIFO if the PWM was disabled and is about to be - * enabled. - */ - if (cstate.enabled) { - imx_pwm_wait_fifo_slot(chip, pwm); - } else { - ret = imx_pwm_clk_prepare_enable(chip); - if (ret) - return ret; - - imx_pwm_sw_reset(chip); - } - - writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); - writel(period_cycles, imx->mmio_base + MX3_PWMPR); - - 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 |= 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); - - imx_pwm_clk_disable_unprepare(chip); - } - - return 0; -} - -static const struct pwm_ops imx_pwm_ops_v1 = { - .enable = imx_pwm_enable_v1, - .disable = imx_pwm_disable_v1, - .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, -}; - -struct imx_pwm_data { - bool polarity_supported; - 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 */ } -}; -MODULE_DEVICE_TABLE(of, imx_pwm_dt_ids); - -static int imx_pwm_probe(struct platform_device *pdev) -{ - const struct of_device_id *of_id = - of_match_device(imx_pwm_dt_ids, &pdev->dev); - const struct imx_pwm_data *data; - struct imx_chip *imx; - struct resource *r; - int ret = 0; - - if (!of_id) - return -ENODEV; - - data = of_id->data; - - imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); - 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", - PTR_ERR(imx->clk_per)); - return PTR_ERR(imx->clk_per); - } - - imx->chip.ops = data->ops; - imx->chip.dev = &pdev->dev; - imx->chip.base = -1; - imx->chip.npwm = 1; - - if (data->polarity_supported) { - dev_dbg(&pdev->dev, "PWM supports output inversion\n"); - imx->chip.of_xlate = of_pwm_xlate_with_flags; - imx->chip.of_pwm_n_cells = 3; - } - - 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); - - ret = pwmchip_add(&imx->chip); - if (ret < 0) - return ret; - - platform_set_drvdata(pdev, imx); - return 0; -} - -static int imx_pwm_remove(struct platform_device *pdev) -{ - struct imx_chip *imx; - - imx = platform_get_drvdata(pdev); - if (imx == NULL) - return -ENODEV; - - imx_pwm_clk_disable_unprepare(&imx->chip); - - return pwmchip_remove(&imx->chip); -} - -static struct platform_driver imx_pwm_driver = { - .driver = { - .name = "imx-pwm", - .of_match_table = imx_pwm_dt_ids, - }, - .probe = imx_pwm_probe, - .remove = imx_pwm_remove, -}; - -module_platform_driver(imx_pwm_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); diff --git a/drivers/pwm/pwm-imx1.c 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>"); diff --git a/drivers/pwm/pwm-imx27.c b/drivers/pwm/pwm-imx27.c @@ -0,0 +1,359 @@ +// 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 MX3_PWMCR 0x00 /* PWM Control Register */ +#define MX3_PWMSR 0x04 /* PWM Status Register */ +#define MX3_PWMSAR 0x0C /* PWM Sample Register */ +#define MX3_PWMPR 0x10 /* PWM Period Register */ + +#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 pwm_imx27_chip { + struct clk *clk_ipg; + struct clk *clk_per; + void __iomem *mmio_base; + struct pwm_chip chip; +}; + +#define to_pwm_imx27_chip(chip) container_of(chip, struct pwm_imx27_chip, chip) + +static int pwm_imx27_clk_prepare_enable(struct pwm_chip *chip) +{ + struct pwm_imx27_chip *imx = to_pwm_imx27_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_imx27_clk_disable_unprepare(struct pwm_chip *chip) +{ + struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); + + clk_disable_unprepare(imx->clk_per); + clk_disable_unprepare(imx->clk_ipg); +} + +static void pwm_imx27_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, struct pwm_state *state) +{ + struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); + u32 period, prescaler, pwm_clk, val; + u64 tmp; + int ret; + + ret = pwm_imx27_clk_prepare_enable(chip); + if (ret < 0) + return; + + val = readl(imx->mmio_base + MX3_PWMCR); + + if (val & MX3_PWMCR_EN) + state->enabled = true; + 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; + } + + if (!state->enabled) + pwm_imx27_clk_disable_unprepare(chip); +} + +static void pwm_imx27_sw_reset(struct pwm_chip *chip) +{ + struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); + struct device *dev = chip->dev; + int wait_count = 0; + u32 cr; + + writel(MX3_PWMCR_SWR, imx->mmio_base + MX3_PWMCR); + do { + usleep_range(200, 1000); + cr = readl(imx->mmio_base + MX3_PWMCR); + } while ((cr & MX3_PWMCR_SWR) && + (wait_count++ < MX3_PWM_SWR_LOOP)); + + if (cr & MX3_PWMCR_SWR) + dev_warn(dev, "software reset timeout\n"); +} + +static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); + struct device *dev = chip->dev; + unsigned int period_ms; + int fifoav; + u32 sr; + + sr = readl(imx->mmio_base + MX3_PWMSR); + 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 == FIELD_GET(MX3_PWMSR_FIFOAV, sr)) + dev_warn(dev, "there is no free FIFO slot\n"); + } +} + +static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + unsigned long period_cycles, duty_cycles, prescale; + struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip); + struct pwm_state cstate; + unsigned long long c; + int ret; + u32 cr; + + pwm_get_state(pwm, &cstate); + + if (state->enabled) { + c = clk_get_rate(imx->clk_per); + c *= state->period; + + do_div(c, 1000000000); + period_cycles = c; + + prescale = period_cycles / 0x10000 + 1; + + period_cycles /= prescale; + c = (unsigned long long)period_cycles * state->duty_cycle; + do_div(c, state->period); + duty_cycles = c; + + /* + * according to imx pwm RM, the real period value should be + * PERIOD value in PWMPR plus 2. + */ + if (period_cycles > 2) + period_cycles -= 2; + else + period_cycles = 0; + + /* + * Wait for a free FIFO slot if the PWM is already enabled, and + * flush the FIFO if the PWM was disabled and is about to be + * enabled. + */ + if (cstate.enabled) { + pwm_imx27_wait_fifo_slot(chip, pwm); + } else { + ret = pwm_imx27_clk_prepare_enable(chip); + if (ret) + return ret; + + pwm_imx27_sw_reset(chip); + } + + writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); + writel(period_cycles, imx->mmio_base + MX3_PWMPR); + + 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 |= 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); + + pwm_imx27_clk_disable_unprepare(chip); + } + + return 0; +} + +static const struct pwm_ops pwm_imx27_ops = { + .apply = pwm_imx27_apply, + .get_state = pwm_imx27_get_state, + .owner = THIS_MODULE, +}; + +static const struct of_device_id pwm_imx27_dt_ids[] = { + { .compatible = "fsl,imx27-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pwm_imx27_dt_ids); + +static int pwm_imx27_probe(struct platform_device *pdev) +{ + struct pwm_imx27_chip *imx; + struct resource *r; + + imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); + if (imx == NULL) + 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_imx27_ops; + imx->chip.dev = &pdev->dev; + imx->chip.base = -1; + imx->chip.npwm = 1; + + imx->chip.of_xlate = of_pwm_xlate_with_flags; + imx->chip.of_pwm_n_cells = 3; + + 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_imx27_remove(struct platform_device *pdev) +{ + struct pwm_imx27_chip *imx; + + imx = platform_get_drvdata(pdev); + + pwm_imx27_clk_disable_unprepare(&imx->chip); + + return pwmchip_remove(&imx->chip); +} + +static struct platform_driver imx_pwm_driver = { + .driver = { + .name = "pwm-imx27", + .of_match_table = pwm_imx27_dt_ids, + }, + .probe = pwm_imx27_probe, + .remove = pwm_imx27_remove, +}; +module_platform_driver(imx_pwm_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); diff --git a/drivers/pwm/pwm-mtk-disp.c b/drivers/pwm/pwm-mtk-disp.c @@ -277,10 +277,21 @@ static const struct mtk_pwm_data mt8173_pwm_data = { .commit_mask = 0x1, }; +static const struct mtk_pwm_data mt8183_pwm_data = { + .enable_mask = BIT(0), + .con0 = 0x18, + .con0_sel = 0x0, + .con1 = 0x1c, + .has_commit = false, + .bls_debug = 0x80, + .bls_debug_mask = 0x3, +}; + static const struct of_device_id mtk_disp_pwm_of_match[] = { { .compatible = "mediatek,mt2701-disp-pwm", .data = &mt2701_pwm_data}, { .compatible = "mediatek,mt6595-disp-pwm", .data = &mt8173_pwm_data}, { .compatible = "mediatek,mt8173-disp-pwm", .data = &mt8173_pwm_data}, + { .compatible = "mediatek,mt8183-disp-pwm", .data = &mt8183_pwm_data}, { } }; MODULE_DEVICE_TABLE(of, mtk_disp_pwm_of_match); diff --git a/drivers/pwm/pwm-rcar.c b/drivers/pwm/pwm-rcar.c @@ -8,6 +8,8 @@ #include <linux/clk.h> #include <linux/err.h> #include <linux/io.h> +#include <linux/log2.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> @@ -68,19 +70,15 @@ static void rcar_pwm_update(struct rcar_pwm_chip *rp, u32 mask, u32 data, static int rcar_pwm_get_clock_division(struct rcar_pwm_chip *rp, int period_ns) { unsigned long clk_rate = clk_get_rate(rp->clk); - unsigned long long max; /* max cycle / nanoseconds */ - unsigned int div; + u64 div, tmp; if (clk_rate == 0) return -EINVAL; - for (div = 0; div <= RCAR_PWM_MAX_DIVISION; div++) { - max = (unsigned long long)NSEC_PER_SEC * RCAR_PWM_MAX_CYCLE * - (1 << div); - do_div(max, clk_rate); - if (period_ns <= max) - break; - } + div = (u64)NSEC_PER_SEC * RCAR_PWM_MAX_CYCLE; + tmp = (u64)period_ns * clk_rate + div - 1; + tmp = div64_u64(tmp, div); + div = ilog2(tmp - 1) + 1; return (div <= RCAR_PWM_MAX_DIVISION) ? div : -ERANGE; } @@ -139,39 +137,8 @@ static void rcar_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) pm_runtime_put(chip->dev); } -static int rcar_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, - int duty_ns, int period_ns) +static int rcar_pwm_enable(struct rcar_pwm_chip *rp) { - struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip); - int div, ret; - - div = rcar_pwm_get_clock_division(rp, period_ns); - if (div < 0) - return div; - - /* - * Let the core driver set pwm->period if disabled and duty_ns == 0. - * But, this driver should prevent to set the new duty_ns if current - * duty_cycle is not set - */ - if (!pwm_is_enabled(pwm) && !duty_ns && !pwm->state.duty_cycle) - return 0; - - rcar_pwm_update(rp, RCAR_PWMCR_SYNC, RCAR_PWMCR_SYNC, RCAR_PWMCR); - - ret = rcar_pwm_set_counter(rp, div, duty_ns, period_ns); - if (!ret) - rcar_pwm_set_clock_control(rp, div); - - /* The SYNC should be set to 0 even if rcar_pwm_set_counter failed */ - rcar_pwm_update(rp, RCAR_PWMCR_SYNC, 0, RCAR_PWMCR); - - return ret; -} - -static int rcar_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip); u32 value; /* Don't enable the PWM device if CYC0 or PH0 is 0 */ @@ -185,19 +152,51 @@ static int rcar_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) return 0; } -static void rcar_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +static void rcar_pwm_disable(struct rcar_pwm_chip *rp) +{ + rcar_pwm_update(rp, RCAR_PWMCR_EN0, 0, RCAR_PWMCR); +} + +static int rcar_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) { struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip); + struct pwm_state cur_state; + int div, ret; - rcar_pwm_update(rp, RCAR_PWMCR_EN0, 0, RCAR_PWMCR); + /* This HW/driver only supports normal polarity */ + pwm_get_state(pwm, &cur_state); + if (state->polarity != PWM_POLARITY_NORMAL) + return -ENOTSUPP; + + if (!state->enabled) { + rcar_pwm_disable(rp); + return 0; + } + + div = rcar_pwm_get_clock_division(rp, state->period); + if (div < 0) + return div; + + rcar_pwm_update(rp, RCAR_PWMCR_SYNC, RCAR_PWMCR_SYNC, RCAR_PWMCR); + + ret = rcar_pwm_set_counter(rp, div, state->duty_cycle, state->period); + if (!ret) + rcar_pwm_set_clock_control(rp, div); + + /* The SYNC should be set to 0 even if rcar_pwm_set_counter failed */ + rcar_pwm_update(rp, RCAR_PWMCR_SYNC, 0, RCAR_PWMCR); + + if (!ret && state->enabled) + ret = rcar_pwm_enable(rp); + + return ret; } static const struct pwm_ops rcar_pwm_ops = { .request = rcar_pwm_request, .free = rcar_pwm_free, - .config = rcar_pwm_config, - .enable = rcar_pwm_enable, - .disable = rcar_pwm_disable, + .apply = rcar_pwm_apply, .owner = THIS_MODULE, }; @@ -279,18 +278,16 @@ static int rcar_pwm_suspend(struct device *dev) static int rcar_pwm_resume(struct device *dev) { struct pwm_device *pwm = rcar_pwm_dev_to_pwm_dev(dev); + struct pwm_state state; if (!test_bit(PWMF_REQUESTED, &pwm->flags)) return 0; pm_runtime_get_sync(dev); - rcar_pwm_config(pwm->chip, pwm, pwm->state.duty_cycle, - pwm->state.period); - if (pwm_is_enabled(pwm)) - rcar_pwm_enable(pwm->chip, pwm); + pwm_get_state(pwm, &state); - return 0; + return rcar_pwm_apply(pwm->chip, pwm, &state); } #endif /* CONFIG_PM_SLEEP */ static SIMPLE_DEV_PM_OPS(rcar_pwm_pm_ops, rcar_pwm_suspend, rcar_pwm_resume); diff --git a/include/linux/pwm.h b/include/linux/pwm.h @@ -242,11 +242,7 @@ pwm_set_relative_duty_cycle(struct pwm_state *state, unsigned int duty_cycle, * struct pwm_ops - PWM controller operations * @request: optional hook for requesting a PWM * @free: optional hook for freeing a PWM - * @config: configure duty cycles and period length for this PWM - * @set_polarity: configure the polarity of this PWM * @capture: capture and report PWM signal - * @enable: enable PWM output toggling - * @disable: disable PWM output toggling * @apply: atomically apply a new PWM config. The state argument * should be adjusted with the real hardware config (if the * approximate the period or duty_cycle value, state should @@ -254,53 +250,56 @@ pwm_set_relative_duty_cycle(struct pwm_state *state, unsigned int duty_cycle, * @get_state: get the current PWM state. This function is only * called once per PWM device when the PWM chip is * registered. - * @dbg_show: optional routine to show contents in debugfs * @owner: helps prevent removal of modules exporting active PWMs + * @config: configure duty cycles and period length for this PWM + * @set_polarity: configure the polarity of this PWM + * @enable: enable PWM output toggling + * @disable: disable PWM output toggling */ struct pwm_ops { int (*request)(struct pwm_chip *chip, struct pwm_device *pwm); void (*free)(struct pwm_chip *chip, struct pwm_device *pwm); - int (*config)(struct pwm_chip *chip, struct pwm_device *pwm, - int duty_ns, int period_ns); - int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm, - enum pwm_polarity polarity); int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_capture *result, unsigned long timeout); - int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm); - void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm); int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state); void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state); -#ifdef CONFIG_DEBUG_FS - void (*dbg_show)(struct pwm_chip *chip, struct seq_file *s); -#endif struct module *owner; + + /* Only used by legacy drivers */ + int (*config)(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns); + int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity); + int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm); + void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm); }; /** * struct pwm_chip - abstract a PWM controller * @dev: device providing the PWMs - * @list: list node for internal use * @ops: callbacks for this PWM controller * @base: number of first PWM controlled by this chip * @npwm: number of PWMs controlled by this chip - * @pwms: array of PWM devices allocated by the framework * @of_xlate: request a PWM device given a device tree PWM specifier * @of_pwm_n_cells: number of cells expected in the device tree PWM specifier + * @list: list node for internal use + * @pwms: array of PWM devices allocated by the framework */ struct pwm_chip { struct device *dev; - struct list_head list; const struct pwm_ops *ops; int base; unsigned int npwm; - struct pwm_device *pwms; - struct pwm_device * (*of_xlate)(struct pwm_chip *pc, const struct of_phandle_args *args); unsigned int of_pwm_n_cells; + + /* only used internally by the PWM framework */ + struct list_head list; + struct pwm_device *pwms; }; /**