The changes for this release include a new driver for the PWM controller
 found on SoCs of the ZTX ZX family. Support for an old SH-Mobile SoC has
 been dropped and the Rockchip and MediaTek drivers gain support for more
 generations.
 
 Other than that there are a bunch of coding style fixes, minor bug fixes
 and cleanup as well as documentation patches.
 -----BEGIN PGP SIGNATURE-----
 
 iQJNBAABCAA3FiEEiOrDCAFJzPfAjcif3SOs138+s6EFAlm2fWoZHHRoaWVycnku
 cmVkaW5nQGdtYWlsLmNvbQAKCRDdI6zXfz6zofXuD/0WP6F9ZMx3FDpc02PJe7KA
 H634eZXRxjRBMyJ+fWqBf+VZ9uKyA8Qh+lKzve57vzBah8cnp2fRS6MtNxCqhq9T
 kiu9ocsbncF01HuAqBCVZM/TwbPIa8QtDGQ7HlmQ+brdWdXyntjilWmoW+DQuoDT
 hsC/AL+Ih0t9LH8zrtv5wtB92+920M9NUe35up3hrc1qQgHCs+1VwRQ0PUlhlkwF
 xQVop+t6rAtVVHx+mEw3tBQ913Ciy2sJY/BuGkXzTCj1P3i8HrPWE/laVXrn9Kbe
 taTeewCr1IZSnyFY4/0+JPz2/a/kFIa36N0uQop0WGcrKV9cp1Hdjuhqa4joKgdk
 rPrNJ3YQ4ji6U/vsy0FYuWPXfQI8sK7i56oJiRVAOIH0OaI6CTx1nDpRr/eXvqSd
 fVf/zwUIVdzutREqYHVU8WwfYJO//U1zWymsJsfD1hZu/2hSZryjemhIjrmi66gh
 RF+8Q9qrV1KGeLFIEAvRpnZ48yM/NKQxKqf9cBUfQmvELpu3b0ujx1eYmGefPqvu
 1jIQbQ96sZI39geF+qt+FE994v7VZnYiMyhWhtpR8inkw9tfcYs+oGr70Jz+SgFf
 0xV/M0aC7KWsOD7Avh3vqMH+j3tUf3zfK1/a0Jsr3PYLCDlw2WEnaWr5b3mv4YQA
 Ta4B6NVjFz41LD9rZV+68A==
 =GUps
 -----END PGP SIGNATURE-----

Merge tag 'pwm/for-4.14-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 release include a new driver for the PWM
  controller found on SoCs of the ZTX ZX family. Support for an old
  SH-Mobile SoC has been dropped and the Rockchip and MediaTek drivers
  gain support for more generations.

  Other than that there are a bunch of coding style fixes, minor bug
  fixes and cleanup as well as documentation patches"

* tag 'pwm/for-4.14-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (32 commits)
  pwm: pwm-samsung: fix suspend/resume support
  pwm: samsung: Remove redundant checks from pwm_samsung_config()
  pwm: mediatek: Disable clock on PWM configuration failure
  dt-bindings: pwm: Add MT2712/MT7622 information
  pwm: mediatek: Fix clock control issue
  pwm: mediatek: Fix PWM source clock selection
  pwm: mediatek: Fix Kconfig description
  pwm: tegra: Explicitly request exclusive reset control
  pwm: hibvt: Explicitly request exclusive reset control
  pwm: tiehrpwm: Set driver data before runtime PM enable
  pwm: tiehrpwm: Miscellaneous coding style fixups
  pwm: tiecap: Set driver data before runtime PM enable
  pwm: tiecap: Miscellaneous coding style fixups
  dt-bindings: pwm: tiecap: Add TI 66AK2G SoC specific compatible
  pwm: tiehrpwm: fix clock imbalance in probe error path
  pwm: tiehrpwm: Fix runtime PM imbalance at unbind
  pwm: Kconfig: Enable pwm-tiecap to be built for Keystone
  pwm: Add ZTE ZX PWM device driver
  dt-bindings: pwm: Add bindings doc for ZTE ZX PWM controller
  pwm: bcm2835: Support for polarity setting via DT
  ...
This commit is contained in:
Linus Torvalds 2017-09-11 13:04:32 -07:00
Родитель 669bf77a54 7755daf5e7
Коммит 66c9457df3
21 изменённых файлов: 699 добавлений и 307 удалений

Просмотреть файл

@ -6,7 +6,7 @@ Required properties:
- clocks: This clock defines the base clock frequency of the PWM hardware
system, the period and the duty_cycle of the PWM signal is a multiple of
the base period.
- #pwm-cells: Should be 2. See pwm.txt in this directory for a description of
- #pwm-cells: Should be 3. See pwm.txt in this directory for a description of
the cells format.
Examples:
@ -15,7 +15,7 @@ pwm@2020c000 {
compatible = "brcm,bcm2835-pwm";
reg = <0x2020c000 0x28>;
clocks = <&clk_pwm>;
#pwm-cells = <2>;
#pwm-cells = <3>;
};
clocks {

Просмотреть файл

@ -2,6 +2,8 @@ MediaTek PWM controller
Required properties:
- compatible: should be "mediatek,<name>-pwm":
- "mediatek,mt2712-pwm": found on mt2712 SoC.
- "mediatek,mt7622-pwm": found on mt7622 SoC.
- "mediatek,mt7623-pwm": found on mt7623 SoC.
- reg: physical base address and length of the controller's registers.
- #pwm-cells: must be 2. See pwm.txt in this directory for a description of
@ -10,7 +12,9 @@ Required properties:
- clock-names: must contain the following:
- "top": the top clock generator
- "main": clock used by the PWM core
- "pwm1-5": the five per PWM clocks
- "pwm1-8": the eight per PWM clocks for mt2712
- "pwm1-6": the six per PWM clocks for mt7622
- "pwm1-5": the five per PWM clocks for mt7623
- pinctrl-names: Must contain a "default" entry.
- pinctrl-0: One property must exist for each entry in pinctrl-names.
See pinctrl/pinctrl-bindings.txt for details of the property values.

Просмотреть файл

@ -3,10 +3,17 @@ Rockchip PWM controller
Required properties:
- compatible: should be "rockchip,<name>-pwm"
"rockchip,rk2928-pwm": found on RK29XX,RK3066 and RK3188 SoCs
"rockchip,rk3288-pwm": found on RK3288 SoC
"rockchip,rk3288-pwm": found on RK3288 SOC
"rockchip,rv1108-pwm", "rockchip,rk3288-pwm": found on RV1108 SoC
"rockchip,vop-pwm": found integrated in VOP on RK3288 SoC
- reg: physical base address and length of the controller's registers
- clocks: phandle and clock specifier of the PWM reference clock
- clocks: See ../clock/clock-bindings.txt
- For older hardware (rk2928, rk3066, rk3188, rk3228, rk3288, rk3399):
- There is one clock that's used both to derive the functional clock
for the device and as the bus clock.
- For newer hardware (rk3328 and future socs): specified by name
- "pwm": This is used to derive the functional clock.
- "pclk": This is the APB bus clock.
- #pwm-cells: must be 2 (rk2928) or 3 (rk3288). See pwm.txt in this directory
for a description of the cell format.

Просмотреть файл

@ -6,6 +6,7 @@ Required properties:
for am4372 - compatible = "ti,am4372-ecap", "ti,am3352-ecap", "ti,am33xx-ecap";
for da850 - compatible = "ti,da850-ecap", "ti,am3352-ecap", "ti,am33xx-ecap";
for dra746 - compatible = "ti,dra746-ecap", "ti,am3352-ecap";
for 66ak2g - compatible = "ti,k2g-ecap", "ti,am3352-ecap";
- #pwm-cells: should be 3. See pwm.txt in this directory for a description of
the cells format. The PWM channel index ranges from 0 to 4. The only third
cell flag supported by this binding is PWM_POLARITY_INVERTED.

Просмотреть файл

@ -0,0 +1,22 @@
ZTE ZX PWM controller
Required properties:
- compatible: Should be "zte,zx296718-pwm".
- reg: Physical base address and length of the controller's registers.
- clocks : The phandle and specifier referencing the controller's clocks.
- clock-names: "pclk" for PCLK, "wclk" for WCLK to the PWM controller. The
PCLK is for register access, while WCLK is the reference clock for
calculating period and duty cycles.
- #pwm-cells: Should be 3. See pwm.txt in this directory for a description of
the cells format.
Example:
pwm: pwm@1439000 {
compatible = "zte,zx296718-pwm";
reg = <0x1439000 0x1000>;
clocks = <&lsp1crm LSP1_PWM_PCLK>,
<&lsp1crm LSP1_PWM_WCLK>;
clock-names = "pclk", "wclk";
#pwm-cells = <3>;
};

Просмотреть файл

@ -6,7 +6,6 @@ Required Properties:
- "renesas,tpu-r8a73a4": for R8A77A4 (R-Mobile APE6) compatible PWM controller.
- "renesas,tpu-r8a7740": for R8A7740 (R-Mobile A1) compatible PWM controller.
- "renesas,tpu-r8a7790": for R8A7790 (R-Car H2) compatible PWM controller.
- "renesas,tpu-sh7372": for SH7372 (SH-Mobile AP4) compatible PWM controller.
- "renesas,tpu": for generic R-Car TPU PWM controller.
- reg: Base address and length of each memory resource used by the PWM

Просмотреть файл

@ -300,7 +300,7 @@ config PWM_MEDIATEK
Generic PWM framework driver for Mediatek ARM SoC.
To compile this driver as a module, choose M here: the module
will be called pwm-mxs.
will be called pwm-mediatek.
config PWM_MXS
tristate "Freescale MXS PWM support"
@ -456,7 +456,7 @@ config PWM_TEGRA
config PWM_TIECAP
tristate "ECAP PWM support"
depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX
depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_KEYSTONE
help
PWM driver support for the ECAP APWM controller found on AM33XX
TI SOC
@ -510,4 +510,13 @@ config PWM_VT8500
To compile this driver as a module, choose M here: the module
will be called pwm-vt8500.
config PWM_ZX
tristate "ZTE ZX PWM support"
depends on ARCH_ZX
help
Generic PWM framework driver for ZTE ZX family SoCs.
To compile this driver as a module, choose M here: the module
will be called pwm-zx.
endif

Просмотреть файл

@ -50,3 +50,4 @@ obj-$(CONFIG_PWM_TIPWMSS) += pwm-tipwmss.o
obj-$(CONFIG_PWM_TWL) += pwm-twl.o
obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
obj-$(CONFIG_PWM_ZX) += pwm-zx.o

Просмотреть файл

@ -167,6 +167,8 @@ static int bcm2835_pwm_probe(struct platform_device *pdev)
pc->chip.dev = &pdev->dev;
pc->chip.ops = &bcm2835_pwm_ops;
pc->chip.npwm = 2;
pc->chip.of_xlate = of_pwm_xlate_with_flags;
pc->chip.of_pwm_n_cells = 3;
platform_set_drvdata(pdev, pc);

Просмотреть файл

@ -208,7 +208,7 @@ static int hibvt_pwm_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
pwm_chip->rstc = devm_reset_control_get(&pdev->dev, NULL);
pwm_chip->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
if (IS_ERR(pwm_chip->rstc)) {
clk_disable_unprepare(pwm_chip->clk);
return PTR_ERR(pwm_chip->rstc);

Просмотреть файл

@ -2,6 +2,7 @@
* Mediatek Pulse Width Modulator driver
*
* Copyright (C) 2015 John Crispin <blogic@openwrt.org>
* Copyright (C) 2017 Zhi Mao <zhi.mao@mediatek.com>
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
@ -29,6 +30,8 @@
#define PWMDWIDTH 0x2c
#define PWMTHRES 0x30
#define PWM_CLK_DIV_MAX 7
enum {
MTK_CLK_MAIN = 0,
MTK_CLK_TOP,
@ -61,6 +64,42 @@ static inline struct mtk_pwm_chip *to_mtk_pwm_chip(struct pwm_chip *chip)
return container_of(chip, struct mtk_pwm_chip, chip);
}
static int mtk_pwm_clk_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip);
int ret;
ret = clk_prepare_enable(pc->clks[MTK_CLK_TOP]);
if (ret < 0)
return ret;
ret = clk_prepare_enable(pc->clks[MTK_CLK_MAIN]);
if (ret < 0)
goto disable_clk_top;
ret = clk_prepare_enable(pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]);
if (ret < 0)
goto disable_clk_main;
return 0;
disable_clk_main:
clk_disable_unprepare(pc->clks[MTK_CLK_MAIN]);
disable_clk_top:
clk_disable_unprepare(pc->clks[MTK_CLK_TOP]);
return ret;
}
static void mtk_pwm_clk_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip);
clk_disable_unprepare(pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]);
clk_disable_unprepare(pc->clks[MTK_CLK_MAIN]);
clk_disable_unprepare(pc->clks[MTK_CLK_TOP]);
}
static inline u32 mtk_pwm_readl(struct mtk_pwm_chip *chip, unsigned int num,
unsigned int offset)
{
@ -80,6 +119,11 @@ static int mtk_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip);
struct clk *clk = pc->clks[MTK_CLK_PWM1 + pwm->hwpwm];
u32 resolution, clkdiv = 0;
int ret;
ret = mtk_pwm_clk_enable(chip, pwm);
if (ret < 0)
return ret;
resolution = NSEC_PER_SEC / clk_get_rate(clk);
@ -88,13 +132,18 @@ static int mtk_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
clkdiv++;
}
if (clkdiv > 7)
if (clkdiv > PWM_CLK_DIV_MAX) {
mtk_pwm_clk_disable(chip, pwm);
dev_err(chip->dev, "period %d not supported\n", period_ns);
return -EINVAL;
}
mtk_pwm_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | BIT(3) | clkdiv);
mtk_pwm_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | clkdiv);
mtk_pwm_writel(pc, pwm->hwpwm, PWMDWIDTH, period_ns / resolution);
mtk_pwm_writel(pc, pwm->hwpwm, PWMTHRES, duty_ns / resolution);
mtk_pwm_clk_disable(chip, pwm);
return 0;
}
@ -104,7 +153,7 @@ static int mtk_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
u32 value;
int ret;
ret = clk_prepare(pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]);
ret = mtk_pwm_clk_enable(chip, pwm);
if (ret < 0)
return ret;
@ -124,7 +173,7 @@ static void mtk_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
value &= ~BIT(pwm->hwpwm);
writel(value, pc->regs);
clk_unprepare(pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]);
mtk_pwm_clk_disable(chip, pwm);
}
static const struct pwm_ops mtk_pwm_ops = {
@ -156,14 +205,6 @@ static int mtk_pwm_probe(struct platform_device *pdev)
return PTR_ERR(pc->clks[i]);
}
ret = clk_prepare(pc->clks[MTK_CLK_TOP]);
if (ret < 0)
return ret;
ret = clk_prepare(pc->clks[MTK_CLK_MAIN]);
if (ret < 0)
goto disable_clk_top;
platform_set_drvdata(pdev, pc);
pc->chip.dev = &pdev->dev;
@ -174,26 +215,15 @@ static int mtk_pwm_probe(struct platform_device *pdev)
ret = pwmchip_add(&pc->chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
goto disable_clk_main;
return ret;
}
return 0;
disable_clk_main:
clk_unprepare(pc->clks[MTK_CLK_MAIN]);
disable_clk_top:
clk_unprepare(pc->clks[MTK_CLK_TOP]);
return ret;
}
static int mtk_pwm_remove(struct platform_device *pdev)
{
struct mtk_pwm_chip *pc = platform_get_drvdata(pdev);
unsigned int i;
for (i = 0; i < pc->chip.npwm; i++)
pwm_disable(&pc->chip.pwms[i]);
return pwmchip_remove(&pc->chip);
}

Просмотреть файл

@ -441,7 +441,7 @@ static int meson_pwm_init_channels(struct meson_pwm *meson,
for (i = 0; i < meson->chip.npwm; i++) {
struct meson_pwm_channel *channel = &channels[i];
snprintf(name, sizeof(name), "%s#mux%u", np->full_name, i);
snprintf(name, sizeof(name), "%pOF#mux%u", np, i);
init.name = name;
init.ops = &clk_mux_ops;

Просмотреть файл

@ -241,11 +241,11 @@ static inline int pca9685_pwm_gpio_probe(struct pca9685 *pca)
}
#endif
static void pca9685_set_sleep_mode(struct pca9685 *pca, int sleep)
static void pca9685_set_sleep_mode(struct pca9685 *pca, bool enable)
{
regmap_update_bits(pca->regmap, PCA9685_MODE1,
MODE1_SLEEP, sleep ? MODE1_SLEEP : 0);
if (!sleep) {
MODE1_SLEEP, enable ? MODE1_SLEEP : 0);
if (!enable) {
/* Wait 500us for the oscillator to be back up */
udelay(500);
}
@ -272,13 +272,13 @@ static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* state is guaranteed active here.
*/
/* Put chip into sleep mode */
pca9685_set_sleep_mode(pca, 1);
pca9685_set_sleep_mode(pca, true);
/* Change the chip-wide output frequency */
regmap_write(pca->regmap, PCA9685_PRESCALE, prescale);
/* Wake the chip up */
pca9685_set_sleep_mode(pca, 0);
pca9685_set_sleep_mode(pca, false);
pca->period_ns = period_ns;
} else {
@ -534,7 +534,7 @@ static int pca9685_pwm_runtime_suspend(struct device *dev)
struct i2c_client *client = to_i2c_client(dev);
struct pca9685 *pca = i2c_get_clientdata(client);
pca9685_set_sleep_mode(pca, 1);
pca9685_set_sleep_mode(pca, true);
return 0;
}
@ -543,7 +543,7 @@ static int pca9685_pwm_runtime_resume(struct device *dev)
struct i2c_client *client = to_i2c_client(dev);
struct pca9685 *pca = i2c_get_clientdata(client);
pca9685_set_sleep_mode(pca, 0);
pca9685_set_sleep_mode(pca, false);
return 0;
}
#endif

Просмотреть файл

@ -455,7 +455,6 @@ static const struct of_device_id tpu_of_table[] = {
{ .compatible = "renesas,tpu-r8a73a4", },
{ .compatible = "renesas,tpu-r8a7740", },
{ .compatible = "renesas,tpu-r8a7790", },
{ .compatible = "renesas,tpu-sh7372", },
{ .compatible = "renesas,tpu", },
{ },
};

Просмотреть файл

@ -27,12 +27,15 @@
#define PWM_DUTY_NEGATIVE (0 << 3)
#define PWM_INACTIVE_NEGATIVE (0 << 4)
#define PWM_INACTIVE_POSITIVE (1 << 4)
#define PWM_POLARITY_MASK (PWM_DUTY_POSITIVE | PWM_INACTIVE_POSITIVE)
#define PWM_OUTPUT_LEFT (0 << 5)
#define PWM_LOCK_EN (1 << 6)
#define PWM_LP_DISABLE (0 << 8)
struct rockchip_pwm_chip {
struct pwm_chip chip;
struct clk *clk;
struct clk *pclk;
const struct rockchip_pwm_data *data;
void __iomem *base;
};
@ -48,13 +51,8 @@ struct rockchip_pwm_data {
struct rockchip_pwm_regs regs;
unsigned int prescaler;
bool supports_polarity;
const struct pwm_ops *ops;
void (*set_enable)(struct pwm_chip *chip,
struct pwm_device *pwm, bool enable,
enum pwm_polarity polarity);
void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state);
bool supports_lock;
u32 enable_conf;
};
static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c)
@ -62,90 +60,18 @@ static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c)
return container_of(c, struct rockchip_pwm_chip, chip);
}
static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip,
struct pwm_device *pwm, bool enable,
enum pwm_polarity polarity)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN;
u32 val;
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
if (enable)
val |= enable_conf;
else
val &= ~enable_conf;
writel_relaxed(val, pc->base + pc->data->regs.ctrl);
}
static void rockchip_pwm_get_state_v1(struct pwm_chip *chip,
struct pwm_device *pwm,
struct pwm_state *state)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN;
u32 val;
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
if ((val & enable_conf) == enable_conf)
state->enabled = true;
}
static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip,
struct pwm_device *pwm, bool enable,
enum pwm_polarity polarity)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
PWM_CONTINUOUS;
u32 val;
if (polarity == PWM_POLARITY_INVERSED)
enable_conf |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE;
else
enable_conf |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE;
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
if (enable)
val |= enable_conf;
else
val &= ~enable_conf;
writel_relaxed(val, pc->base + pc->data->regs.ctrl);
}
static void rockchip_pwm_get_state_v2(struct pwm_chip *chip,
struct pwm_device *pwm,
struct pwm_state *state)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
PWM_CONTINUOUS;
u32 val;
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
if ((val & enable_conf) != enable_conf)
return;
state->enabled = true;
if (!(val & PWM_DUTY_POSITIVE))
state->polarity = PWM_POLARITY_INVERSED;
}
static void rockchip_pwm_get_state(struct pwm_chip *chip,
struct pwm_device *pwm,
struct pwm_state *state)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
u32 enable_conf = pc->data->enable_conf;
unsigned long clk_rate;
u64 tmp;
u32 val;
int ret;
ret = clk_enable(pc->clk);
ret = clk_enable(pc->pclk);
if (ret)
return;
@ -157,19 +83,31 @@ static void rockchip_pwm_get_state(struct pwm_chip *chip,
tmp = readl_relaxed(pc->base + pc->data->regs.duty);
tmp *= pc->data->prescaler * NSEC_PER_SEC;
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
pc->data->get_state(chip, pwm, state);
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
if (pc->data->supports_polarity)
state->enabled = ((val & enable_conf) != enable_conf) ?
false : true;
else
state->enabled = ((val & enable_conf) == enable_conf) ?
true : false;
clk_disable(pc->clk);
if (pc->data->supports_polarity) {
if (!(val & PWM_DUTY_POSITIVE))
state->polarity = PWM_POLARITY_INVERSED;
}
clk_disable(pc->pclk);
}
static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
unsigned long period, duty;
u64 clk_rate, div;
u32 ctrl;
clk_rate = clk_get_rate(pc->clk);
@ -178,26 +116,53 @@ static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* bits, every possible input period can be obtained using the
* default prescaler value for all practical clock rate values.
*/
div = clk_rate * period_ns;
div = clk_rate * state->period;
period = DIV_ROUND_CLOSEST_ULL(div,
pc->data->prescaler * NSEC_PER_SEC);
div = clk_rate * duty_ns;
div = clk_rate * state->duty_cycle;
duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC);
/*
* Lock the period and duty of previous configuration, then
* change the duty and period, that would not be effective.
*/
ctrl = readl_relaxed(pc->base + pc->data->regs.ctrl);
if (pc->data->supports_lock) {
ctrl |= PWM_LOCK_EN;
writel_relaxed(ctrl, pc->base + pc->data->regs.ctrl);
}
writel(period, pc->base + pc->data->regs.period);
writel(duty, pc->base + pc->data->regs.duty);
return 0;
if (pc->data->supports_polarity) {
ctrl &= ~PWM_POLARITY_MASK;
if (state->polarity == PWM_POLARITY_INVERSED)
ctrl |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE;
else
ctrl |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE;
}
/*
* Unlock and set polarity at the same time,
* the configuration of duty, period and polarity
* would be effective together at next period.
*/
if (pc->data->supports_lock)
ctrl &= ~PWM_LOCK_EN;
writel(ctrl, pc->base + pc->data->regs.ctrl);
}
static int rockchip_pwm_enable(struct pwm_chip *chip,
struct pwm_device *pwm,
bool enable,
enum pwm_polarity polarity)
struct pwm_device *pwm,
bool enable)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
u32 enable_conf = pc->data->enable_conf;
int ret;
u32 val;
if (enable) {
ret = clk_enable(pc->clk);
@ -205,7 +170,14 @@ static int rockchip_pwm_enable(struct pwm_chip *chip,
return ret;
}
pc->data->set_enable(chip, pwm, enable, polarity);
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
if (enable)
val |= enable_conf;
else
val &= ~enable_conf;
writel_relaxed(val, pc->base + pc->data->regs.ctrl);
if (!enable)
clk_disable(pc->clk);
@ -219,33 +191,26 @@ static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
struct pwm_state curstate;
bool enabled;
int ret;
int ret = 0;
ret = clk_enable(pc->pclk);
if (ret)
return ret;
pwm_get_state(pwm, &curstate);
enabled = curstate.enabled;
ret = clk_enable(pc->clk);
if (ret)
return ret;
if (state->polarity != curstate.polarity && enabled) {
ret = rockchip_pwm_enable(chip, pwm, false, state->polarity);
if (state->polarity != curstate.polarity && enabled &&
!pc->data->supports_lock) {
ret = rockchip_pwm_enable(chip, pwm, false);
if (ret)
goto out;
enabled = false;
}
ret = rockchip_pwm_config(chip, pwm, state->duty_cycle, state->period);
if (ret) {
if (enabled != curstate.enabled)
rockchip_pwm_enable(chip, pwm, !enabled,
state->polarity);
goto out;
}
rockchip_pwm_config(chip, pwm, state);
if (state->enabled != enabled) {
ret = rockchip_pwm_enable(chip, pwm, state->enabled,
state->polarity);
ret = rockchip_pwm_enable(chip, pwm, state->enabled);
if (ret)
goto out;
}
@ -257,18 +222,12 @@ static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
rockchip_pwm_get_state(chip, pwm, state);
out:
clk_disable(pc->clk);
clk_disable(pc->pclk);
return ret;
}
static const struct pwm_ops rockchip_pwm_ops_v1 = {
.get_state = rockchip_pwm_get_state,
.apply = rockchip_pwm_apply,
.owner = THIS_MODULE,
};
static const struct pwm_ops rockchip_pwm_ops_v2 = {
static const struct pwm_ops rockchip_pwm_ops = {
.get_state = rockchip_pwm_get_state,
.apply = rockchip_pwm_apply,
.owner = THIS_MODULE,
@ -282,9 +241,9 @@ static const struct rockchip_pwm_data pwm_data_v1 = {
.ctrl = 0x0c,
},
.prescaler = 2,
.ops = &rockchip_pwm_ops_v1,
.set_enable = rockchip_pwm_set_enable_v1,
.get_state = rockchip_pwm_get_state_v1,
.supports_polarity = false,
.supports_lock = false,
.enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN,
};
static const struct rockchip_pwm_data pwm_data_v2 = {
@ -296,9 +255,9 @@ static const struct rockchip_pwm_data pwm_data_v2 = {
},
.prescaler = 1,
.supports_polarity = true,
.ops = &rockchip_pwm_ops_v2,
.set_enable = rockchip_pwm_set_enable_v2,
.get_state = rockchip_pwm_get_state_v2,
.supports_lock = false,
.enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
PWM_CONTINUOUS,
};
static const struct rockchip_pwm_data pwm_data_vop = {
@ -310,15 +269,30 @@ static const struct rockchip_pwm_data pwm_data_vop = {
},
.prescaler = 1,
.supports_polarity = true,
.ops = &rockchip_pwm_ops_v2,
.set_enable = rockchip_pwm_set_enable_v2,
.get_state = rockchip_pwm_get_state_v2,
.supports_lock = false,
.enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
PWM_CONTINUOUS,
};
static const struct rockchip_pwm_data pwm_data_v3 = {
.regs = {
.duty = 0x08,
.period = 0x04,
.cntr = 0x00,
.ctrl = 0x0c,
},
.prescaler = 1,
.supports_polarity = true,
.supports_lock = true,
.enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
PWM_CONTINUOUS,
};
static const struct of_device_id rockchip_pwm_dt_ids[] = {
{ .compatible = "rockchip,rk2928-pwm", .data = &pwm_data_v1},
{ .compatible = "rockchip,rk3288-pwm", .data = &pwm_data_v2},
{ .compatible = "rockchip,vop-pwm", .data = &pwm_data_vop},
{ .compatible = "rockchip,rk3328-pwm", .data = &pwm_data_v3},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_pwm_dt_ids);
@ -328,7 +302,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
const struct of_device_id *id;
struct rockchip_pwm_chip *pc;
struct resource *r;
int ret;
int ret, count;
id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev);
if (!id)
@ -343,19 +317,49 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
if (IS_ERR(pc->base))
return PTR_ERR(pc->base);
pc->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(pc->clk))
return PTR_ERR(pc->clk);
pc->clk = devm_clk_get(&pdev->dev, "pwm");
if (IS_ERR(pc->clk)) {
pc->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(pc->clk)) {
ret = PTR_ERR(pc->clk);
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev, "Can't get bus clk: %d\n",
ret);
return ret;
}
}
count = of_count_phandle_with_args(pdev->dev.of_node,
"clocks", "#clock-cells");
if (count == 2)
pc->pclk = devm_clk_get(&pdev->dev, "pclk");
else
pc->pclk = pc->clk;
if (IS_ERR(pc->pclk)) {
ret = PTR_ERR(pc->pclk);
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev, "Can't get APB clk: %d\n", ret);
return ret;
}
ret = clk_prepare_enable(pc->clk);
if (ret)
if (ret) {
dev_err(&pdev->dev, "Can't prepare enable bus clk: %d\n", ret);
return ret;
}
ret = clk_prepare(pc->pclk);
if (ret) {
dev_err(&pdev->dev, "Can't prepare APB clk: %d\n", ret);
goto err_clk;
}
platform_set_drvdata(pdev, pc);
pc->data = id->data;
pc->chip.dev = &pdev->dev;
pc->chip.ops = pc->data->ops;
pc->chip.ops = &rockchip_pwm_ops;
pc->chip.base = -1;
pc->chip.npwm = 1;
@ -368,12 +372,20 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
if (ret < 0) {
clk_unprepare(pc->clk);
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
goto err_pclk;
}
/* Keep the PWM clk enabled if the PWM appears to be up and running. */
if (!pwm_is_enabled(pc->chip.pwms))
clk_disable(pc->clk);
return 0;
err_pclk:
clk_unprepare(pc->pclk);
err_clk:
clk_disable_unprepare(pc->clk);
return ret;
}
@ -395,6 +407,7 @@ static int rockchip_pwm_remove(struct platform_device *pdev)
if (pwm_is_enabled(pc->chip.pwms))
clk_disable(pc->clk);
clk_unprepare(pc->pclk);
clk_unprepare(pc->clk);
return pwmchip_remove(&pc->chip);

Просмотреть файл

@ -3,6 +3,7 @@
* Copyright (c) 2008 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org>
* Copyright (c) 2013 Tomasz Figa <tomasz.figa@gmail.com>
* Copyright (c) 2017 Samsung Electronics Co., Ltd.
*
* PWM driver for Samsung SoCs
*
@ -74,6 +75,7 @@ struct samsung_pwm_channel {
* @chip: generic PWM chip
* @variant: local copy of hardware variant data
* @inverter_mask: inverter status for all channels - one bit per channel
* @disabled_mask: disabled status for all channels - one bit per channel
* @base: base address of mapped PWM registers
* @base_clk: base clock used to drive the timers
* @tclk0: external clock 0 (can be ERR_PTR if not present)
@ -83,6 +85,7 @@ struct samsung_pwm_chip {
struct pwm_chip chip;
struct samsung_pwm_variant variant;
u8 inverter_mask;
u8 disabled_mask;
void __iomem *base;
struct clk *base_clk;
@ -257,6 +260,8 @@ static int pwm_samsung_enable(struct pwm_chip *chip, struct pwm_device *pwm)
tcon |= TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan);
writel(tcon, our_chip->base + REG_TCON);
our_chip->disabled_mask &= ~BIT(pwm->hwpwm);
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
return 0;
@ -275,6 +280,8 @@ static void pwm_samsung_disable(struct pwm_chip *chip, struct pwm_device *pwm)
tcon &= ~TCON_AUTORELOAD(tcon_chan);
writel(tcon, our_chip->base + REG_TCON);
our_chip->disabled_mask |= BIT(pwm->hwpwm);
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
}
@ -297,8 +304,8 @@ static void pwm_samsung_manual_update(struct samsung_pwm_chip *chip,
spin_unlock_irqrestore(&samsung_pwm_lock, flags);
}
static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
static int __pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns, bool force_period)
{
struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip);
struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm);
@ -312,9 +319,6 @@ static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
if (period_ns > NSEC_PER_SEC)
return -ERANGE;
if (period_ns == chan->period_ns && duty_ns == chan->duty_ns)
return 0;
tcnt = readl(our_chip->base + REG_TCNTB(pwm->hwpwm));
oldtcmp = readl(our_chip->base + REG_TCMPB(pwm->hwpwm));
@ -322,7 +326,7 @@ static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
++tcnt;
/* Check to see if we are changing the clock rate of the PWM. */
if (chan->period_ns != period_ns) {
if (chan->period_ns != period_ns || force_period) {
unsigned long tin_rate;
u32 period;
@ -381,6 +385,12 @@ static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
return 0;
}
static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
return __pwm_samsung_config(chip, pwm, duty_ns, period_ns, false);
}
static void pwm_samsung_set_invert(struct samsung_pwm_chip *chip,
unsigned int channel, bool invert)
{
@ -592,51 +602,41 @@ static int pwm_samsung_remove(struct platform_device *pdev)
}
#ifdef CONFIG_PM_SLEEP
static int pwm_samsung_suspend(struct device *dev)
static int pwm_samsung_resume(struct device *dev)
{
struct samsung_pwm_chip *chip = dev_get_drvdata(dev);
struct samsung_pwm_chip *our_chip = dev_get_drvdata(dev);
struct pwm_chip *chip = &our_chip->chip;
unsigned int i;
/*
* No one preserves these values during suspend so reset them.
* Otherwise driver leaves PWM unconfigured if same values are
* passed to pwm_config() next time.
*/
for (i = 0; i < SAMSUNG_PWM_NUM; ++i) {
struct pwm_device *pwm = &chip->chip.pwms[i];
for (i = 0; i < SAMSUNG_PWM_NUM; i++) {
struct pwm_device *pwm = &chip->pwms[i];
struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm);
if (!chan)
continue;
chan->period_ns = 0;
chan->duty_ns = 0;
}
if (our_chip->variant.output_mask & BIT(i))
pwm_samsung_set_invert(our_chip, i,
our_chip->inverter_mask & BIT(i));
return 0;
}
if (chan->period_ns) {
__pwm_samsung_config(chip, pwm, chan->duty_ns,
chan->period_ns, true);
/* needed to make PWM disable work on Odroid-XU3 */
pwm_samsung_manual_update(our_chip, pwm);
}
static int pwm_samsung_resume(struct device *dev)
{
struct samsung_pwm_chip *chip = dev_get_drvdata(dev);
unsigned int chan;
/*
* Inverter setting must be preserved across suspend/resume
* as nobody really seems to configure it more than once.
*/
for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan) {
if (chip->variant.output_mask & BIT(chan))
pwm_samsung_set_invert(chip, chan,
chip->inverter_mask & BIT(chan));
if (our_chip->disabled_mask & BIT(i))
pwm_samsung_disable(chip, pwm);
else
pwm_samsung_enable(chip, pwm);
}
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(pwm_samsung_pm_ops, pwm_samsung_suspend,
pwm_samsung_resume);
static SIMPLE_DEV_PM_OPS(pwm_samsung_pm_ops, NULL, pwm_samsung_resume);
static struct platform_driver pwm_samsung_driver = {
.driver = {

Просмотреть файл

@ -218,7 +218,7 @@ static int tegra_pwm_probe(struct platform_device *pdev)
*/
pwm->clk_rate = clk_get_rate(pwm->clk);
pwm->rst = devm_reset_control_get(&pdev->dev, "pwm");
pwm->rst = devm_reset_control_get_exclusive(&pdev->dev, "pwm");
if (IS_ERR(pwm->rst)) {
ret = PTR_ERR(pwm->rst);
dev_err(&pdev->dev, "Reset control is not found: %d\n", ret);

Просмотреть файл

@ -39,15 +39,15 @@
#define ECCTL2_TSCTR_FREERUN BIT(4)
struct ecap_context {
u32 cap3;
u32 cap4;
u16 ecctl2;
u32 cap3;
u32 cap4;
u16 ecctl2;
};
struct ecap_pwm_chip {
struct pwm_chip chip;
unsigned int clk_rate;
void __iomem *mmio_base;
struct pwm_chip chip;
unsigned int clk_rate;
void __iomem *mmio_base;
struct ecap_context ctx;
};
@ -64,9 +64,9 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
u32 period_cycles, duty_cycles;
unsigned long long c;
unsigned long period_cycles, duty_cycles;
unsigned int reg_val;
u16 value;
if (period_ns > NSEC_PER_SEC)
return -ERANGE;
@ -74,7 +74,7 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
c = pc->clk_rate;
c = c * period_ns;
do_div(c, NSEC_PER_SEC);
period_cycles = (unsigned long)c;
period_cycles = (u32)c;
if (period_cycles < 1) {
period_cycles = 1;
@ -83,17 +83,17 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
c = pc->clk_rate;
c = c * duty_ns;
do_div(c, NSEC_PER_SEC);
duty_cycles = (unsigned long)c;
duty_cycles = (u32)c;
}
pm_runtime_get_sync(pc->chip.dev);
reg_val = readw(pc->mmio_base + ECCTL2);
value = readw(pc->mmio_base + ECCTL2);
/* Configure APWM mode & disable sync option */
reg_val |= ECCTL2_APWM_MODE | ECCTL2_SYNC_SEL_DISA;
value |= ECCTL2_APWM_MODE | ECCTL2_SYNC_SEL_DISA;
writew(reg_val, pc->mmio_base + ECCTL2);
writew(value, pc->mmio_base + ECCTL2);
if (!pwm_is_enabled(pwm)) {
/* Update active registers if not running */
@ -110,40 +110,45 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
}
if (!pwm_is_enabled(pwm)) {
reg_val = readw(pc->mmio_base + ECCTL2);
value = readw(pc->mmio_base + ECCTL2);
/* Disable APWM mode to put APWM output Low */
reg_val &= ~ECCTL2_APWM_MODE;
writew(reg_val, pc->mmio_base + ECCTL2);
value &= ~ECCTL2_APWM_MODE;
writew(value, pc->mmio_base + ECCTL2);
}
pm_runtime_put_sync(pc->chip.dev);
return 0;
}
static int ecap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
enum pwm_polarity polarity)
enum pwm_polarity polarity)
{
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
unsigned short reg_val;
u16 value;
pm_runtime_get_sync(pc->chip.dev);
reg_val = readw(pc->mmio_base + ECCTL2);
value = readw(pc->mmio_base + ECCTL2);
if (polarity == PWM_POLARITY_INVERSED)
/* Duty cycle defines LOW period of PWM */
reg_val |= ECCTL2_APWM_POL_LOW;
value |= ECCTL2_APWM_POL_LOW;
else
/* Duty cycle defines HIGH period of PWM */
reg_val &= ~ECCTL2_APWM_POL_LOW;
value &= ~ECCTL2_APWM_POL_LOW;
writew(value, pc->mmio_base + ECCTL2);
writew(reg_val, pc->mmio_base + ECCTL2);
pm_runtime_put_sync(pc->chip.dev);
return 0;
}
static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
unsigned int reg_val;
u16 value;
/* Leave clock enabled on enabling PWM */
pm_runtime_get_sync(pc->chip.dev);
@ -152,24 +157,25 @@ static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
* Enable 'Free run Time stamp counter mode' to start counter
* and 'APWM mode' to enable APWM output
*/
reg_val = readw(pc->mmio_base + ECCTL2);
reg_val |= ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE;
writew(reg_val, pc->mmio_base + ECCTL2);
value = readw(pc->mmio_base + ECCTL2);
value |= ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE;
writew(value, pc->mmio_base + ECCTL2);
return 0;
}
static void ecap_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip);
unsigned int reg_val;
u16 value;
/*
* Disable 'Free run Time stamp counter mode' to stop counter
* and 'APWM mode' to put APWM output to low
*/
reg_val = readw(pc->mmio_base + ECCTL2);
reg_val &= ~(ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE);
writew(reg_val, pc->mmio_base + ECCTL2);
value = readw(pc->mmio_base + ECCTL2);
value &= ~(ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE);
writew(value, pc->mmio_base + ECCTL2);
/* Disable clock on PWM disable */
pm_runtime_put_sync(pc->chip.dev);
@ -184,12 +190,12 @@ static void ecap_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
}
static const struct pwm_ops ecap_pwm_ops = {
.free = ecap_pwm_free,
.config = ecap_pwm_config,
.set_polarity = ecap_pwm_set_polarity,
.enable = ecap_pwm_enable,
.disable = ecap_pwm_disable,
.owner = THIS_MODULE,
.free = ecap_pwm_free,
.config = ecap_pwm_config,
.set_polarity = ecap_pwm_set_polarity,
.enable = ecap_pwm_enable,
.disable = ecap_pwm_disable,
.owner = THIS_MODULE,
};
static const struct of_device_id ecap_of_match[] = {
@ -202,10 +208,10 @@ MODULE_DEVICE_TABLE(of, ecap_of_match);
static int ecap_pwm_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int ret;
struct ecap_pwm_chip *pc;
struct resource *r;
struct clk *clk;
struct ecap_pwm_chip *pc;
int ret;
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
if (!pc)
@ -248,9 +254,9 @@ static int ecap_pwm_probe(struct platform_device *pdev)
return ret;
}
platform_set_drvdata(pdev, pc);
pm_runtime_enable(&pdev->dev);
platform_set_drvdata(pdev, pc);
return 0;
}
@ -259,6 +265,7 @@ static int ecap_pwm_remove(struct platform_device *pdev)
struct ecap_pwm_chip *pc = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
return pwmchip_remove(&pc->chip);
}
@ -311,14 +318,13 @@ static SIMPLE_DEV_PM_OPS(ecap_pwm_pm_ops, ecap_pwm_suspend, ecap_pwm_resume);
static struct platform_driver ecap_pwm_driver = {
.driver = {
.name = "ecap",
.name = "ecap",
.of_match_table = ecap_of_match,
.pm = &ecap_pwm_pm_ops,
.pm = &ecap_pwm_pm_ops,
},
.probe = ecap_pwm_probe,
.remove = ecap_pwm_remove,
};
module_platform_driver(ecap_pwm_driver);
MODULE_DESCRIPTION("ECAP PWM driver");

Просмотреть файл

@ -122,12 +122,12 @@ struct ehrpwm_context {
};
struct ehrpwm_pwm_chip {
struct pwm_chip chip;
unsigned int clk_rate;
void __iomem *mmio_base;
struct pwm_chip chip;
unsigned long clk_rate;
void __iomem *mmio_base;
unsigned long period_cycles[NUM_PWM_CHANNEL];
enum pwm_polarity polarity[NUM_PWM_CHANNEL];
struct clk *tbclk;
struct clk *tbclk;
struct ehrpwm_context ctx;
};
@ -136,25 +136,26 @@ static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip)
return container_of(chip, struct ehrpwm_pwm_chip, chip);
}
static inline u16 ehrpwm_read(void __iomem *base, int offset)
static inline u16 ehrpwm_read(void __iomem *base, unsigned int offset)
{
return readw(base + offset);
}
static inline void ehrpwm_write(void __iomem *base, int offset, unsigned int val)
static inline void ehrpwm_write(void __iomem *base, unsigned int offset,
u16 value)
{
writew(val & 0xFFFF, base + offset);
writew(value, base + offset);
}
static void ehrpwm_modify(void __iomem *base, int offset,
unsigned short mask, unsigned short val)
static void ehrpwm_modify(void __iomem *base, unsigned int offset, u16 mask,
u16 value)
{
unsigned short regval;
unsigned short val;
regval = readw(base + offset);
regval &= ~mask;
regval |= val & mask;
writew(regval, base + offset);
val = readw(base + offset);
val &= ~mask;
val |= value & mask;
writew(val, base + offset);
}
/**
@ -163,14 +164,13 @@ static void ehrpwm_modify(void __iomem *base, int offset,
* @prescale_div: prescaler value set
* @tb_clk_div: Time Base Control prescaler bits
*/
static int set_prescale_div(unsigned long rqst_prescaler,
unsigned short *prescale_div, unsigned short *tb_clk_div)
static int set_prescale_div(unsigned long rqst_prescaler, u16 *prescale_div,
u16 *tb_clk_div)
{
unsigned int clkdiv, hspclkdiv;
for (clkdiv = 0; clkdiv <= CLKDIV_MAX; clkdiv++) {
for (hspclkdiv = 0; hspclkdiv <= HSPCLKDIV_MAX; hspclkdiv++) {
/*
* calculations for prescaler value :
* prescale_div = HSPCLKDIVIDER * CLKDIVIDER.
@ -191,13 +191,14 @@ static int set_prescale_div(unsigned long rqst_prescaler,
}
}
}
return 1;
}
static void configure_polarity(struct ehrpwm_pwm_chip *pc, int chan)
{
int aqctl_reg;
unsigned short aqctl_val, aqctl_mask;
u16 aqctl_val, aqctl_mask;
unsigned int aqctl_reg;
/*
* Configure PWM output to HIGH/LOW level on counter
@ -232,13 +233,13 @@ static void configure_polarity(struct ehrpwm_pwm_chip *pc, int chan)
* duty_ns = 10^9 * (ps_divval * duty_cycles) / PWM_CLK_RATE
*/
static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
int duty_ns, int period_ns)
{
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
u32 period_cycles, duty_cycles;
u16 ps_divval, tb_divval;
unsigned int i, cmp_reg;
unsigned long long c;
unsigned long period_cycles, duty_cycles;
unsigned short ps_divval, tb_divval;
int i, cmp_reg;
if (period_ns > NSEC_PER_SEC)
return -ERANGE;
@ -272,8 +273,9 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
if (i == pwm->hwpwm)
continue;
dev_err(chip->dev, "Period value conflicts with channel %d\n",
i);
dev_err(chip->dev,
"period value conflicts with channel %u\n",
i);
return -EINVAL;
}
}
@ -282,7 +284,7 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
/* Configure clock prescaler to support Low frequency PWM wave */
if (set_prescale_div(period_cycles/PERIOD_MAX, &ps_divval,
&tb_divval)) {
&tb_divval)) {
dev_err(chip->dev, "Unsupported values\n");
return -EINVAL;
}
@ -303,7 +305,7 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
/* Configure ehrpwm counter for up-count mode */
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CTRMODE_MASK,
TBCTL_CTRMODE_UP);
TBCTL_CTRMODE_UP);
if (pwm->hwpwm == 1)
/* Channel 1 configured with compare B register */
@ -315,23 +317,26 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles);
pm_runtime_put_sync(chip->dev);
return 0;
}
static int ehrpwm_pwm_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm, enum pwm_polarity polarity)
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
/* Configuration of polarity in hardware delayed, do at enable */
pc->polarity[pwm->hwpwm] = polarity;
return 0;
}
static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
unsigned short aqcsfrc_val, aqcsfrc_mask;
u16 aqcsfrc_val, aqcsfrc_mask;
int ret;
/* Leave clock enabled on enabling PWM */
@ -348,7 +353,7 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
/* Changes to shadow mode */
ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK,
AQSFRC_RLDCSF_ZRO);
AQSFRC_RLDCSF_ZRO);
ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val);
@ -358,20 +363,21 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
/* Enable TBCLK before enabling PWM device */
ret = clk_enable(pc->tbclk);
if (ret) {
dev_err(chip->dev, "Failed to enable TBCLK for %s\n",
dev_name(pc->chip.dev));
dev_err(chip->dev, "Failed to enable TBCLK for %s: %d\n",
dev_name(pc->chip.dev), ret);
return ret;
}
/* Enable time counter for free_run */
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_FREE_RUN);
return 0;
}
static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
unsigned short aqcsfrc_val, aqcsfrc_mask;
u16 aqcsfrc_val, aqcsfrc_mask;
/* Action Qualifier puts PWM output low forcefully */
if (pwm->hwpwm) {
@ -387,7 +393,7 @@ static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
* Action Qualifier control on PWM output from next TBCLK
*/
ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK,
AQSFRC_RLDCSF_IMDT);
AQSFRC_RLDCSF_IMDT);
ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val);
@ -415,17 +421,17 @@ static void ehrpwm_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
}
static const struct pwm_ops ehrpwm_pwm_ops = {
.free = ehrpwm_pwm_free,
.config = ehrpwm_pwm_config,
.set_polarity = ehrpwm_pwm_set_polarity,
.enable = ehrpwm_pwm_enable,
.disable = ehrpwm_pwm_disable,
.owner = THIS_MODULE,
.free = ehrpwm_pwm_free,
.config = ehrpwm_pwm_config,
.set_polarity = ehrpwm_pwm_set_polarity,
.enable = ehrpwm_pwm_enable,
.disable = ehrpwm_pwm_disable,
.owner = THIS_MODULE,
};
static const struct of_device_id ehrpwm_of_match[] = {
{ .compatible = "ti,am3352-ehrpwm" },
{ .compatible = "ti,am33xx-ehrpwm" },
{ .compatible = "ti,am3352-ehrpwm" },
{ .compatible = "ti,am33xx-ehrpwm" },
{},
};
MODULE_DEVICE_TABLE(of, ehrpwm_of_match);
@ -433,10 +439,10 @@ MODULE_DEVICE_TABLE(of, ehrpwm_of_match);
static int ehrpwm_pwm_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int ret;
struct ehrpwm_pwm_chip *pc;
struct resource *r;
struct clk *clk;
struct ehrpwm_pwm_chip *pc;
int ret;
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
if (!pc)
@ -489,13 +495,18 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
ret = pwmchip_add(&pc->chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
return ret;
goto err_clk_unprepare;
}
platform_set_drvdata(pdev, pc);
pm_runtime_enable(&pdev->dev);
platform_set_drvdata(pdev, pc);
return 0;
err_clk_unprepare:
clk_unprepare(pc->tbclk);
return ret;
}
static int ehrpwm_pwm_remove(struct platform_device *pdev)
@ -504,8 +515,8 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev)
clk_unprepare(pc->tbclk);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return pwmchip_remove(&pc->chip);
}
@ -513,6 +524,7 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev)
static void ehrpwm_pwm_save_context(struct ehrpwm_pwm_chip *pc)
{
pm_runtime_get_sync(pc->chip.dev);
pc->ctx.tbctl = ehrpwm_read(pc->mmio_base, TBCTL);
pc->ctx.tbprd = ehrpwm_read(pc->mmio_base, TBPRD);
pc->ctx.cmpa = ehrpwm_read(pc->mmio_base, CMPA);
@ -521,6 +533,7 @@ static void ehrpwm_pwm_save_context(struct ehrpwm_pwm_chip *pc)
pc->ctx.aqctlb = ehrpwm_read(pc->mmio_base, AQCTLB);
pc->ctx.aqsfrc = ehrpwm_read(pc->mmio_base, AQSFRC);
pc->ctx.aqcsfrc = ehrpwm_read(pc->mmio_base, AQCSFRC);
pm_runtime_put_sync(pc->chip.dev);
}
@ -539,9 +552,10 @@ static void ehrpwm_pwm_restore_context(struct ehrpwm_pwm_chip *pc)
static int ehrpwm_pwm_suspend(struct device *dev)
{
struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev);
int i;
unsigned int i;
ehrpwm_pwm_save_context(pc);
for (i = 0; i < pc->chip.npwm; i++) {
struct pwm_device *pwm = &pc->chip.pwms[i];
@ -551,13 +565,14 @@ static int ehrpwm_pwm_suspend(struct device *dev)
/* Disable explicitly if PWM is running */
pm_runtime_put_sync(dev);
}
return 0;
}
static int ehrpwm_pwm_resume(struct device *dev)
{
struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev);
int i;
unsigned int i;
for (i = 0; i < pc->chip.npwm; i++) {
struct pwm_device *pwm = &pc->chip.pwms[i];
@ -568,24 +583,25 @@ static int ehrpwm_pwm_resume(struct device *dev)
/* Enable explicitly if PWM was running */
pm_runtime_get_sync(dev);
}
ehrpwm_pwm_restore_context(pc);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(ehrpwm_pwm_pm_ops, ehrpwm_pwm_suspend,
ehrpwm_pwm_resume);
ehrpwm_pwm_resume);
static struct platform_driver ehrpwm_pwm_driver = {
.driver = {
.name = "ehrpwm",
.name = "ehrpwm",
.of_match_table = ehrpwm_of_match,
.pm = &ehrpwm_pwm_pm_ops,
.pm = &ehrpwm_pwm_pm_ops,
},
.probe = ehrpwm_pwm_probe,
.remove = ehrpwm_pwm_remove,
};
module_platform_driver(ehrpwm_pwm_driver);
MODULE_DESCRIPTION("EHRPWM PWM driver");

Просмотреть файл

@ -241,6 +241,7 @@ static int vt8500_pwm_probe(struct platform_device *pdev)
ret = pwmchip_add(&chip->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip\n");
clk_unprepare(chip->clk);
return ret;
}

282
drivers/pwm/pwm-zx.c Normal file
Просмотреть файл

@ -0,0 +1,282 @@
/*
* Copyright (C) 2017 Sanechips Technology Co., Ltd.
* Copyright 2017 Linaro Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#define ZX_PWM_MODE 0x0
#define ZX_PWM_CLKDIV_SHIFT 2
#define ZX_PWM_CLKDIV_MASK GENMASK(11, 2)
#define ZX_PWM_CLKDIV(x) (((x) << ZX_PWM_CLKDIV_SHIFT) & \
ZX_PWM_CLKDIV_MASK)
#define ZX_PWM_POLAR BIT(1)
#define ZX_PWM_EN BIT(0)
#define ZX_PWM_PERIOD 0x4
#define ZX_PWM_DUTY 0x8
#define ZX_PWM_CLKDIV_MAX 1023
#define ZX_PWM_PERIOD_MAX 65535
struct zx_pwm_chip {
struct pwm_chip chip;
struct clk *pclk;
struct clk *wclk;
void __iomem *base;
};
static inline struct zx_pwm_chip *to_zx_pwm_chip(struct pwm_chip *chip)
{
return container_of(chip, struct zx_pwm_chip, chip);
}
static inline u32 zx_pwm_readl(struct zx_pwm_chip *zpc, unsigned int hwpwm,
unsigned int offset)
{
return readl(zpc->base + (hwpwm + 1) * 0x10 + offset);
}
static inline void zx_pwm_writel(struct zx_pwm_chip *zpc, unsigned int hwpwm,
unsigned int offset, u32 value)
{
writel(value, zpc->base + (hwpwm + 1) * 0x10 + offset);
}
static void zx_pwm_set_mask(struct zx_pwm_chip *zpc, unsigned int hwpwm,
unsigned int offset, u32 mask, u32 value)
{
u32 data;
data = zx_pwm_readl(zpc, hwpwm, offset);
data &= ~mask;
data |= value & mask;
zx_pwm_writel(zpc, hwpwm, offset, data);
}
static void zx_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct zx_pwm_chip *zpc = to_zx_pwm_chip(chip);
unsigned long rate;
unsigned int div;
u32 value;
u64 tmp;
value = zx_pwm_readl(zpc, pwm->hwpwm, ZX_PWM_MODE);
if (value & ZX_PWM_POLAR)
state->polarity = PWM_POLARITY_NORMAL;
else
state->polarity = PWM_POLARITY_INVERSED;
if (value & ZX_PWM_EN)
state->enabled = true;
else
state->enabled = false;
div = (value & ZX_PWM_CLKDIV_MASK) >> ZX_PWM_CLKDIV_SHIFT;
rate = clk_get_rate(zpc->wclk);
tmp = zx_pwm_readl(zpc, pwm->hwpwm, ZX_PWM_PERIOD);
tmp *= div * NSEC_PER_SEC;
state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
tmp = zx_pwm_readl(zpc, pwm->hwpwm, ZX_PWM_DUTY);
tmp *= div * NSEC_PER_SEC;
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
}
static int zx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
unsigned int duty_ns, unsigned int period_ns)
{
struct zx_pwm_chip *zpc = to_zx_pwm_chip(chip);
unsigned int period_cycles, duty_cycles;
unsigned long long c;
unsigned int div = 1;
unsigned long rate;
/* Find out the best divider */
rate = clk_get_rate(zpc->wclk);
while (1) {
c = rate / div;
c = c * period_ns;
do_div(c, NSEC_PER_SEC);
if (c < ZX_PWM_PERIOD_MAX)
break;
div++;
if (div > ZX_PWM_CLKDIV_MAX)
return -ERANGE;
}
/* Calculate duty cycles */
period_cycles = c;
c *= duty_ns;
do_div(c, period_ns);
duty_cycles = c;
/*
* If the PWM is being enabled, we have to temporarily disable it
* before configuring the registers.
*/
if (pwm_is_enabled(pwm))
zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE, ZX_PWM_EN, 0);
/* Set up registers */
zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE, ZX_PWM_CLKDIV_MASK,
ZX_PWM_CLKDIV(div));
zx_pwm_writel(zpc, pwm->hwpwm, ZX_PWM_PERIOD, period_cycles);
zx_pwm_writel(zpc, pwm->hwpwm, ZX_PWM_DUTY, duty_cycles);
/* Re-enable the PWM if needed */
if (pwm_is_enabled(pwm))
zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE,
ZX_PWM_EN, ZX_PWM_EN);
return 0;
}
static int zx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct zx_pwm_chip *zpc = to_zx_pwm_chip(chip);
struct pwm_state cstate;
int ret;
pwm_get_state(pwm, &cstate);
if (state->polarity != cstate.polarity)
zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE, ZX_PWM_POLAR,
(state->polarity == PWM_POLARITY_INVERSED) ?
0 : ZX_PWM_POLAR);
if (state->period != cstate.period ||
state->duty_cycle != cstate.duty_cycle) {
ret = zx_pwm_config(chip, pwm, state->duty_cycle,
state->period);
if (ret)
return ret;
}
if (state->enabled != cstate.enabled) {
if (state->enabled) {
ret = clk_prepare_enable(zpc->wclk);
if (ret)
return ret;
zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE,
ZX_PWM_EN, ZX_PWM_EN);
} else {
zx_pwm_set_mask(zpc, pwm->hwpwm, ZX_PWM_MODE,
ZX_PWM_EN, 0);
clk_disable_unprepare(zpc->wclk);
}
}
return 0;
}
static const struct pwm_ops zx_pwm_ops = {
.apply = zx_pwm_apply,
.get_state = zx_pwm_get_state,
.owner = THIS_MODULE,
};
static int zx_pwm_probe(struct platform_device *pdev)
{
struct zx_pwm_chip *zpc;
struct resource *res;
unsigned int i;
int ret;
zpc = devm_kzalloc(&pdev->dev, sizeof(*zpc), GFP_KERNEL);
if (!zpc)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
zpc->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(zpc->base))
return PTR_ERR(zpc->base);
zpc->pclk = devm_clk_get(&pdev->dev, "pclk");
if (IS_ERR(zpc->pclk))
return PTR_ERR(zpc->pclk);
zpc->wclk = devm_clk_get(&pdev->dev, "wclk");
if (IS_ERR(zpc->wclk))
return PTR_ERR(zpc->wclk);
ret = clk_prepare_enable(zpc->pclk);
if (ret)
return ret;
zpc->chip.dev = &pdev->dev;
zpc->chip.ops = &zx_pwm_ops;
zpc->chip.base = -1;
zpc->chip.npwm = 4;
zpc->chip.of_xlate = of_pwm_xlate_with_flags;
zpc->chip.of_pwm_n_cells = 3;
/*
* PWM devices may be enabled by firmware, and let's disable all of
* them initially to save power.
*/
for (i = 0; i < zpc->chip.npwm; i++)
zx_pwm_set_mask(zpc, i, ZX_PWM_MODE, ZX_PWM_EN, 0);
ret = pwmchip_add(&zpc->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, zpc);
return 0;
}
static int zx_pwm_remove(struct platform_device *pdev)
{
struct zx_pwm_chip *zpc = platform_get_drvdata(pdev);
int ret;
ret = pwmchip_remove(&zpc->chip);
clk_disable_unprepare(zpc->pclk);
return ret;
}
static const struct of_device_id zx_pwm_dt_ids[] = {
{ .compatible = "zte,zx296718-pwm", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, zx_pwm_dt_ids);
static struct platform_driver zx_pwm_driver = {
.driver = {
.name = "zx-pwm",
.of_match_table = zx_pwm_dt_ids,
},
.probe = zx_pwm_probe,
.remove = zx_pwm_remove,
};
module_platform_driver(zx_pwm_driver);
MODULE_ALIAS("platform:zx-pwm");
MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
MODULE_DESCRIPTION("ZTE ZX PWM Driver");
MODULE_LICENSE("GPL v2");