1
0
Fork 0

pwm: Changes for v5.1-rc1

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.
 -----BEGIN PGP SIGNATURE-----
 
 iQJNBAABCAA3FiEEiOrDCAFJzPfAjcif3SOs138+s6EFAlyJFvUZHHRoaWVycnku
 cmVkaW5nQGdtYWlsLmNvbQAKCRDdI6zXfz6zobfREACTyAxr02GyEQFm4wLLV/OG
 ynJ45IY2g/9OEY6RGmHQXzcBKycmFtifV6uF5PhMr0KZzdfp/uIMnyHsxV7Gm+ia
 NNhG7gQPrb3yZev2AGmc6N/je2+5SL6AZH3+C5ocRqThNKKFknr3uZHbZpmTQlQf
 QV63UYNpwtUrFu7jvzNz1kCBBUispXoDY44W0YSv8RYiKOxwJabPott0oOJH8miF
 lVyCa2yyA8KuW1DTP13XSHMpmUwKGNkiqaFWGIMPFz6ViK5zj/rY92dUIwLegmh7
 l/A7CBMCilw9yd9o1bhSWp2yUJ1gf8QeC64awa0euk9abgX574Nxt+vJsF8EtpaF
 8aCcQrJpjlpPCQV4rP62G8Dcr7tWRNmKWKRNMaXQICpdhcLXuwg6tz4HH8z+I1um
 W+w+9wsiqrRqKyqxlmQEXBi3qSPepJkU/DUsrE7QoO2jbmi21xyVIi1AQHXqNZYo
 y2NzAADuh4i7L8AVFtJ1KcvdZZIBF/XrQXgXEgjX6fQpcs6aMyNOHTu9Xod7XW59
 8yBKKqYmvSmmxDcFFfajkBuUbIuUqIzFxr4U6JYHQsWNnVN1ey+MSlZK6bHA8gw0
 UngjRNv8fEAU0Hck2Z0iwDe1zHVW6g/ZNKZC2TUqzETPvZHg9QKt0sJYGkmxoR5O
 JW1eTekpW57XahV2oX7f3g==
 =TyJJ
 -----END PGP SIGNATURE-----

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()
  ...
hifive-unleashed-5.1
Linus Torvalds 2019-03-13 10:01:10 -07:00
commit add8462a60
13 changed files with 474 additions and 300 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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"

View File

@ -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

View File

@ -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;
}

View File

@ -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_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 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_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);

View File

@ -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;

View File

@ -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);

View File

@ -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>");

View File

@ -19,16 +19,6 @@
#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 */
@ -86,21 +76,18 @@
/* PWMPR register value of 0xffff has the same effect as 0xfffe */
#define MX3_PWMPR_MAX 0xfffe
struct imx_chip {
struct pwm_imx27_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)
#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;
ret = clk_prepare_enable(imx->clk_ipg);
@ -116,35 +103,32 @@ static int imx_pwm_clk_prepare_enable(struct pwm_chip *chip)
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_ipg);
}
static void imx_pwm_get_state(struct pwm_chip *chip,
struct pwm_device *pwm, struct pwm_state *state)
static void pwm_imx27_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;
struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
u32 period, prescaler, pwm_clk, val;
u64 tmp;
int ret;
ret = imx_pwm_clk_prepare_enable(chip);
ret = pwm_imx27_clk_prepare_enable(chip);
if (ret < 0)
return;
val = readl(imx->mmio_base + MX3_PWMCR);
if (val & MX3_PWMCR_EN) {
if (val & MX3_PWMCR_EN)
state->enabled = true;
ret = imx_pwm_clk_prepare_enable(chip);
if (ret)
return;
} else {
else
state->enabled = false;
}
switch (FIELD_GET(MX3_PWMCR_POUTC, val)) {
case MX3_PWMCR_POUTC_NORMAL:
@ -176,70 +160,13 @@ static void imx_pwm_get_state(struct pwm_chip *chip,
state->duty_cycle = 0;
}
imx_pwm_clk_disable_unprepare(chip);
if (!state->enabled)
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)
static void pwm_imx27_sw_reset(struct pwm_chip *chip)
{
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 pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
struct device *dev = chip->dev;
int wait_count = 0;
u32 cr;
@ -255,10 +182,10 @@ static void imx_pwm_sw_reset(struct pwm_chip *chip)
dev_warn(dev, "software reset timeout\n");
}
static void imx_pwm_wait_fifo_slot(struct pwm_chip *chip,
struct pwm_device *pwm)
static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip,
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;
unsigned int period_ms;
int fifoav;
@ -277,11 +204,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,
struct pwm_state *state)
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 imx_chip *imx = to_imx_chip(chip);
struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
struct pwm_state cstate;
unsigned long long c;
int ret;
@ -318,13 +245,13 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm,
* enabled.
*/
if (cstate.enabled) {
imx_pwm_wait_fifo_slot(chip, pwm);
pwm_imx27_wait_fifo_slot(chip, pwm);
} else {
ret = imx_pwm_clk_prepare_enable(chip);
ret = pwm_imx27_clk_prepare_enable(chip);
if (ret)
return ret;
imx_pwm_sw_reset(chip);
pwm_imx27_sw_reset(chip);
}
writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);
@ -343,64 +270,35 @@ static int imx_pwm_apply_v2(struct pwm_chip *chip, struct pwm_device *pwm,
} else if (cstate.enabled) {
writel(0, imx->mmio_base + MX3_PWMCR);
imx_pwm_clk_disable_unprepare(chip);
pwm_imx27_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,
static const struct pwm_ops pwm_imx27_ops = {
.apply = pwm_imx27_apply,
.get_state = pwm_imx27_get_state,
.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, },
static const struct of_device_id pwm_imx27_dt_ids[] = {
{ .compatible = "fsl,imx27-pwm", },
{ /* 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 =
of_match_device(imx_pwm_dt_ids, &pdev->dev);
const struct imx_pwm_data *data;
struct imx_chip *imx;
struct pwm_imx27_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;
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",
@ -410,57 +308,51 @@ static int imx_pwm_probe(struct platform_device *pdev)
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);
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 = data->ops;
imx->chip.ops = &pwm_imx27_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;
}
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;
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);
if (imx == NULL)
return -ENODEV;
imx_pwm_clk_disable_unprepare(&imx->chip);
pwm_imx27_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,
.driver = {
.name = "pwm-imx27",
.of_match_table = pwm_imx27_dt_ids,
},
.probe = imx_pwm_probe,
.remove = imx_pwm_remove,
.probe = pwm_imx27_probe,
.remove = pwm_imx27_remove,
};
module_platform_driver(imx_pwm_driver);
MODULE_LICENSE("GPL v2");

View File

@ -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);

View File

@ -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);

View File

@ -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;
};
/**