Commit 070d9a93 authored by Thierry Reding's avatar Thierry Reding

Merge branch 'for-4.8/drivers' into for-next

parents bd268612 1f0d3bb0
Broadcom iProc PWM controller device tree bindings
This controller has 4 channels.
Required Properties :
- compatible: must be "brcm,iproc-pwm"
- reg: physical base address and length of the controller's registers
- clocks: phandle + clock specifier pair for the external clock
- #pwm-cells: Should be 3. See pwm.txt in this directory for a
description of the cells format.
Refer to clocks/clock-bindings.txt for generic clock consumer properties.
Example:
pwm: pwm@18031000 {
compatible = "brcm,iproc-pwm";
reg = <0x18031000 0x28>;
clocks = <&osc>;
#pwm-cells = <3>;
};
* PWM controlled by ChromeOS EC
Google's ChromeOS EC PWM is a simple PWM attached to the Embedded Controller
(EC) and controlled via a host-command interface.
An EC PWM node should be only found as a sub-node of the EC node (see
Documentation/devicetree/bindings/mfd/cros-ec.txt).
Required properties:
- compatible: Must contain "google,cros-ec-pwm"
- #pwm-cells: Should be 1. The cell specifies the PWM index.
Example:
cros-ec@0 {
compatible = "google,cros-ec-spi";
...
cros_ec_pwm: ec-pwm {
compatible = "google,cros-ec-pwm";
#pwm-cells = <1>;
};
};
Tegra SoC PWFM controller Tegra SoC PWFM controller
Required properties: Required properties:
- compatible: For Tegra20, must contain "nvidia,tegra20-pwm". For Tegra30, - compatible: Must be:
must contain "nvidia,tegra30-pwm". Otherwise, must contain - "nvidia,tegra20-pwm": for Tegra20
"nvidia,<chip>-pwm", plus one of the above, where <chip> is tegra114, - "nvidia,tegra30-pwm", "nvidia,tegra20-pwm": for Tegra30
tegra124, tegra132, or tegra210. - "nvidia,tegra114-pwm", "nvidia,tegra20-pwm": for Tegra114
- "nvidia,tegra124-pwm", "nvidia,tegra20-pwm": for Tegra124
- "nvidia,tegra132-pwm", "nvidia,tegra20-pwm": for Tegra132
- "nvidia,tegra210-pwm", "nvidia,tegra20-pwm": for Tegra210
- "nvidia,tegra186-pwm": for Tegra186
- reg: physical base address and length of the controller's registers - reg: physical base address and length of the controller's registers
- #pwm-cells: should be 2. See pwm.txt in this directory for a description of - #pwm-cells: should be 2. See pwm.txt in this directory for a description of
the cells format. the cells format.
......
...@@ -15,14 +15,14 @@ Optional properties: ...@@ -15,14 +15,14 @@ Optional properties:
Example: Example:
ehrpwm0: ehrpwm@0 { /* EHRPWM on am33xx */ ehrpwm0: pwm@48300200 { /* EHRPWM on am33xx */
compatible = "ti,am33xx-ehrpwm"; compatible = "ti,am33xx-ehrpwm";
#pwm-cells = <3>; #pwm-cells = <3>;
reg = <0x48300200 0x100>; reg = <0x48300200 0x100>;
ti,hwmods = "ehrpwm0"; ti,hwmods = "ehrpwm0";
}; };
ehrpwm0: ehrpwm@0 { /* EHRPWM on da850 */ ehrpwm0: pwm@300000 { /* EHRPWM on da850 */
compatible = "ti,da850-ehrpwm", "ti,am33xx-ehrpwm"; compatible = "ti,da850-ehrpwm", "ti,am33xx-ehrpwm";
#pwm-cells = <3>; #pwm-cells = <3>;
reg = <0x300000 0x2000>; reg = <0x300000 0x2000>;
......
...@@ -7,6 +7,7 @@ Required Properties: ...@@ -7,6 +7,7 @@ Required Properties:
- "renesas,pwm-r8a7790": for R-Car H2 - "renesas,pwm-r8a7790": for R-Car H2
- "renesas,pwm-r8a7791": for R-Car M2-W - "renesas,pwm-r8a7791": for R-Car M2-W
- "renesas,pwm-r8a7794": for R-Car E2 - "renesas,pwm-r8a7794": for R-Car E2
- "renesas,pwm-r8a7795": for R-Car H3
- reg: base address and length of the registers block for the PWM. - reg: base address and length of the registers block for the PWM.
- #pwm-cells: should be 2. See pwm.txt in this directory for a description of - #pwm-cells: should be 2. See pwm.txt in this directory for a description of
the cells format. the cells format.
......
== ST STMPE PWM controller ==
This is a PWM block embedded in the ST Microelectronics STMPE
(ST Multi-Purpose Expander) chips. The PWM is registered as a
subdevices of the STMPE MFD device.
Required properties:
- compatible: should be:
- "st,stmpe-pwm"
- #pwm-cells: should be 2. See pwm.txt in this directory for a description of
the cells format.
Example:
pwm0: pwm {
compatible = "st,stmpe-pwm";
#pwm-cells = <2>;
};
...@@ -74,6 +74,16 @@ config PWM_ATMEL_TCB ...@@ -74,6 +74,16 @@ config PWM_ATMEL_TCB
To compile this driver as a module, choose M here: the module To compile this driver as a module, choose M here: the module
will be called pwm-atmel-tcb. will be called pwm-atmel-tcb.
config PWM_BCM_IPROC
tristate "iProc PWM support"
depends on ARCH_BCM_IPROC
help
Generic PWM framework driver for Broadcom iProc PWM block. This
block is used in Broadcom iProc SoC's.
To compile this driver as a module, choose M here: the module
will be called pwm-bcm-iproc.
config PWM_BCM_KONA config PWM_BCM_KONA
tristate "Kona PWM support" tristate "Kona PWM support"
depends on ARCH_BCM_MOBILE depends on ARCH_BCM_MOBILE
...@@ -137,6 +147,13 @@ config PWM_CRC ...@@ -137,6 +147,13 @@ config PWM_CRC
Generic PWM framework driver for Crystalcove (CRC) PMIC based PWM Generic PWM framework driver for Crystalcove (CRC) PMIC based PWM
control. control.
config PWM_CROS_EC
tristate "ChromeOS EC PWM driver"
depends on MFD_CROS_EC
help
PWM driver for exposing a PWM attached to the ChromeOS Embedded
Controller.
config PWM_EP93XX config PWM_EP93XX
tristate "Cirrus Logic EP93xx PWM support" tristate "Cirrus Logic EP93xx PWM support"
depends on ARCH_EP93XX depends on ARCH_EP93XX
...@@ -305,7 +322,7 @@ config PWM_PXA ...@@ -305,7 +322,7 @@ config PWM_PXA
config PWM_RCAR config PWM_RCAR
tristate "Renesas R-Car PWM support" tristate "Renesas R-Car PWM support"
depends on ARCH_RCAR_GEN1 || ARCH_RCAR_GEN2 || COMPILE_TEST depends on ARCH_RENESAS || COMPILE_TEST
depends on HAS_IOMEM depends on HAS_IOMEM
help help
This driver exposes the PWM Timer controller found in Renesas This driver exposes the PWM Timer controller found in Renesas
...@@ -362,6 +379,13 @@ config PWM_STI ...@@ -362,6 +379,13 @@ config PWM_STI
To compile this driver as a module, choose M here: the module To compile this driver as a module, choose M here: the module
will be called pwm-sti. will be called pwm-sti.
config PWM_STMPE
bool "STMPE expander PWM export"
depends on MFD_STMPE
help
This enables support for the PWMs found in the STMPE I/O
expanders.
config PWM_SUN4I config PWM_SUN4I
tristate "Allwinner PWM support" tristate "Allwinner PWM support"
depends on ARCH_SUNXI || COMPILE_TEST depends on ARCH_SUNXI || COMPILE_TEST
......
...@@ -4,6 +4,7 @@ obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o ...@@ -4,6 +4,7 @@ obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o
obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
obj-$(CONFIG_PWM_BCM_IPROC) += pwm-bcm-iproc.o
obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o
obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o
obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o
...@@ -11,6 +12,7 @@ obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o ...@@ -11,6 +12,7 @@ obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o
obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o
obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
obj-$(CONFIG_PWM_CRC) += pwm-crc.o obj-$(CONFIG_PWM_CRC) += pwm-crc.o
obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o
obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o
obj-$(CONFIG_PWM_IMG) += pwm-img.o obj-$(CONFIG_PWM_IMG) += pwm-img.o
...@@ -34,6 +36,7 @@ obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o ...@@ -34,6 +36,7 @@ obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o
obj-$(CONFIG_PWM_STI) += pwm-sti.o obj-$(CONFIG_PWM_STI) += pwm-sti.o
obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o
obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o
obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o
......
...@@ -64,7 +64,8 @@ struct atmel_pwm_chip { ...@@ -64,7 +64,8 @@ struct atmel_pwm_chip {
void __iomem *base; void __iomem *base;
unsigned int updated_pwms; unsigned int updated_pwms;
struct mutex isr_lock; /* ISR is cleared when read, ensure only one thread does that */ /* ISR is cleared when read, ensure only one thread does that */
struct mutex isr_lock;
void (*config)(struct pwm_chip *chip, struct pwm_device *pwm, void (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
unsigned long dty, unsigned long prd); unsigned long dty, unsigned long prd);
...@@ -271,6 +272,16 @@ static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) ...@@ -271,6 +272,16 @@ static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
mutex_unlock(&atmel_pwm->isr_lock); mutex_unlock(&atmel_pwm->isr_lock);
atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm); atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm);
/*
* Wait for the PWM channel disable operation to be effective before
* stopping the clock.
*/
timeout = jiffies + 2 * HZ;
while ((atmel_pwm_readl(atmel_pwm, PWM_SR) & (1 << pwm->hwpwm)) &&
time_before(jiffies, timeout))
usleep_range(10, 100);
clk_disable(atmel_pwm->clk); clk_disable(atmel_pwm->clk);
} }
...@@ -324,21 +335,14 @@ MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids); ...@@ -324,21 +335,14 @@ MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids);
static inline const struct atmel_pwm_data * static inline const struct atmel_pwm_data *
atmel_pwm_get_driver_data(struct platform_device *pdev) atmel_pwm_get_driver_data(struct platform_device *pdev)
{ {
if (pdev->dev.of_node) { const struct platform_device_id *id;
const struct of_device_id *match;
match = of_match_device(atmel_pwm_dt_ids, &pdev->dev);
if (!match)
return NULL;
return match->data; if (pdev->dev.of_node)
} else { return of_device_get_match_data(&pdev->dev);
const struct platform_device_id *id;
id = platform_get_device_id(pdev); id = platform_get_device_id(pdev);
return (struct atmel_pwm_data *)id->driver_data; return (struct atmel_pwm_data *)id->driver_data;
}
} }
static int atmel_pwm_probe(struct platform_device *pdev) static int atmel_pwm_probe(struct platform_device *pdev)
......
/*
* Copyright (C) 2016 Broadcom
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#define IPROC_PWM_CTRL_OFFSET 0x00
#define IPROC_PWM_CTRL_TYPE_SHIFT(ch) (15 + (ch))
#define IPROC_PWM_CTRL_POLARITY_SHIFT(ch) (8 + (ch))
#define IPROC_PWM_CTRL_EN_SHIFT(ch) (ch)
#define IPROC_PWM_PERIOD_OFFSET(ch) (0x04 + ((ch) << 3))
#define IPROC_PWM_PERIOD_MIN 0x02
#define IPROC_PWM_PERIOD_MAX 0xffff
#define IPROC_PWM_DUTY_CYCLE_OFFSET(ch) (0x08 + ((ch) << 3))
#define IPROC_PWM_DUTY_CYCLE_MIN 0x00
#define IPROC_PWM_DUTY_CYCLE_MAX 0xffff
#define IPROC_PWM_PRESCALE_OFFSET 0x24
#define IPROC_PWM_PRESCALE_BITS 0x06
#define IPROC_PWM_PRESCALE_SHIFT(ch) ((3 - (ch)) * \
IPROC_PWM_PRESCALE_BITS)
#define IPROC_PWM_PRESCALE_MASK(ch) (IPROC_PWM_PRESCALE_MAX << \
IPROC_PWM_PRESCALE_SHIFT(ch))
#define IPROC_PWM_PRESCALE_MIN 0x00
#define IPROC_PWM_PRESCALE_MAX 0x3f
struct iproc_pwmc {
struct pwm_chip chip;
void __iomem *base;
struct clk *clk;
};
static inline struct iproc_pwmc *to_iproc_pwmc(struct pwm_chip *chip)
{
return container_of(chip, struct iproc_pwmc, chip);
}
static void iproc_pwmc_enable(struct iproc_pwmc *ip, unsigned int channel)
{
u32 value;
value = readl(ip->base + IPROC_PWM_CTRL_OFFSET);
value |= 1 << IPROC_PWM_CTRL_EN_SHIFT(channel);
writel(value, ip->base + IPROC_PWM_CTRL_OFFSET);
/* must be a 400 ns delay between clearing and setting enable bit */
ndelay(400);
}
static void iproc_pwmc_disable(struct iproc_pwmc *ip, unsigned int channel)
{
u32 value;
value = readl(ip->base + IPROC_PWM_CTRL_OFFSET);
value &= ~(1 << IPROC_PWM_CTRL_EN_SHIFT(channel));
writel(value, ip->base + IPROC_PWM_CTRL_OFFSET);
/* must be a 400 ns delay between clearing and setting enable bit */
ndelay(400);
}
static void iproc_pwmc_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct iproc_pwmc *ip = to_iproc_pwmc(chip);
u64 tmp, multi, rate;
u32 value, prescale;
rate = clk_get_rate(ip->clk);
value = readl(ip->base + IPROC_PWM_CTRL_OFFSET);
if (value & BIT(IPROC_PWM_CTRL_EN_SHIFT(pwm->hwpwm)))
state->enabled = true;
else
state->enabled = false;
if (value & BIT(IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm)))
state->polarity = PWM_POLARITY_NORMAL;
else
state->polarity = PWM_POLARITY_INVERSED;
value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET);
prescale = value >> IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm);
prescale &= IPROC_PWM_PRESCALE_MAX;
multi = NSEC_PER_SEC * (prescale + 1);
value = readl(ip->base + IPROC_PWM_PERIOD_OFFSET(pwm->hwpwm));
tmp = (value & IPROC_PWM_PERIOD_MAX) * multi;
state->period = div64_u64(tmp, rate);
value = readl(ip->base + IPROC_PWM_DUTY_CYCLE_OFFSET(pwm->hwpwm));
tmp = (value & IPROC_PWM_PERIOD_MAX) * multi;
state->duty_cycle = div64_u64(tmp, rate);
}
static int iproc_pwmc_apply(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
unsigned long prescale = IPROC_PWM_PRESCALE_MIN;
struct iproc_pwmc *ip = to_iproc_pwmc(chip);
u32 value, period, duty;
u64 rate;
rate = clk_get_rate(ip->clk);
/*
* Find period count, duty count and prescale to suit duty_cycle and
* period. This is done according to formulas described below:
*
* period_ns = 10^9 * (PRESCALE + 1) * PC / PWM_CLK_RATE
* duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
*
* PC = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1))
* DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1))
*/
while (1) {
u64 value, div;
div = NSEC_PER_SEC * (prescale + 1);
value = rate * state->period;
period = div64_u64(value, div);
value = rate * state->duty_cycle;
duty = div64_u64(value, div);
if (period < IPROC_PWM_PERIOD_MIN ||
duty < IPROC_PWM_DUTY_CYCLE_MIN)
return -EINVAL;
if (period <= IPROC_PWM_PERIOD_MAX &&
duty <= IPROC_PWM_DUTY_CYCLE_MAX)
break;
/* Otherwise, increase prescale and recalculate counts */
if (++prescale > IPROC_PWM_PRESCALE_MAX)
return -EINVAL;
}
iproc_pwmc_disable(ip, pwm->hwpwm);
/* Set prescale */
value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET);
value &= ~IPROC_PWM_PRESCALE_MASK(pwm->hwpwm);
value |= prescale << IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm);
writel(value, ip->base + IPROC_PWM_PRESCALE_OFFSET);
/* set period and duty cycle */
writel(period, ip->base + IPROC_PWM_PERIOD_OFFSET(pwm->hwpwm));
writel(duty, ip->base + IPROC_PWM_DUTY_CYCLE_OFFSET(pwm->hwpwm));
/* set polarity */
value = readl(ip->base + IPROC_PWM_CTRL_OFFSET);
if (state->polarity == PWM_POLARITY_NORMAL)
value |= 1 << IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm);
else
value &= ~(1 << IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm));
writel(value, ip->base + IPROC_PWM_CTRL_OFFSET);
if (state->enabled)
iproc_pwmc_enable(ip, pwm->hwpwm);
return 0;
}
static const struct pwm_ops iproc_pwm_ops = {
.apply = iproc_pwmc_apply,
.get_state = iproc_pwmc_get_state,
};
static int iproc_pwmc_probe(struct platform_device *pdev)
{
struct iproc_pwmc *ip;
struct resource *res;
unsigned int i;
u32 value;
int ret;
ip = devm_kzalloc(&pdev->dev, sizeof(*ip), GFP_KERNEL);
if (!ip)
return -ENOMEM;
platform_set_drvdata(pdev, ip);
ip->chip.dev = &pdev->dev;
ip->chip.ops = &iproc_pwm_ops;
ip->chip.base = -1;
ip->chip.npwm = 4;
ip->chip.of_xlate = of_pwm_xlate_with_flags;
ip->chip.of_pwm_n_cells = 3;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ip->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(ip->base))
return PTR_ERR(ip->base);
ip->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(ip->clk)) {
dev_err(&pdev->dev, "failed to get clock: %ld\n",
PTR_ERR(ip->clk));
return PTR_ERR(ip->clk);
}
ret = clk_prepare_enable(ip->clk);
if (ret < 0) {
dev_err(&pdev->dev, "failed to enable clock: %d\n", ret);
return ret;
}
/* Set full drive and normal polarity for all channels */
value = readl(ip->base + IPROC_PWM_CTRL_OFFSET);
for (i = 0; i < ip->chip.npwm; i++) {
value &= ~(1 << IPROC_PWM_CTRL_TYPE_SHIFT(i));
value |= 1 << IPROC_PWM_CTRL_POLARITY_SHIFT(i);
}
writel(value, ip->base + IPROC_PWM_CTRL_OFFSET);
ret = pwmchip_add(&ip->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
clk_disable_unprepare(ip->clk);
}
return ret;
}
static int iproc_pwmc_remove(struct platform_device *pdev)
{
struct iproc_pwmc *ip = platform_get_drvdata(pdev);
clk_disable_unprepare(ip->clk);
return pwmchip_remove(&ip->chip);
}
static const struct of_device_id bcm_iproc_pwmc_dt[] = {
{ .compatible = "brcm,iproc-pwm" },
{ },
};
MODULE_DEVICE_TABLE(of, bcm_iproc_pwmc_dt);
static struct platform_driver iproc_pwmc_driver = {
.driver = {
.name = "bcm-iproc-pwm",
.of_match_table = bcm_iproc_pwmc_dt,
},
.probe = iproc_pwmc_probe,
.remove = iproc_pwmc_remove,
};
module_platform_driver(iproc_pwmc_driver);
MODULE_AUTHOR("Yendapally Reddy Dhananjaya Reddy <yendapally.reddy@broadcom.com>");
MODULE_DESCRIPTION("Broadcom iProc PWM driver");
MODULE_LICENSE("GPL v2");
/*
* Copyright (C) 2016 Google, Inc
*
* 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.
*
* Expose a PWM controlled by the ChromeOS EC to the host processor.
*/
#include <linux/module.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
/**
* struct cros_ec_pwm_device - Driver data for EC PWM
*
* @dev: Device node
* @ec: Pointer to EC device
* @chip: PWM controller chip
*/
struct cros_ec_pwm_device {
struct device *dev;
struct cros_ec_device *ec;
struct pwm_chip chip;
};
static inline struct cros_ec_pwm_device *pwm_to_cros_ec_pwm(struct pwm_chip *c)
{
return container_of(c, struct cros_ec_pwm_device, chip);
}
static int cros_ec_pwm_set_duty(struct cros_ec_device *ec, u8 index, u16 duty)
{
struct {
struct cros_ec_command msg;
struct ec_params_pwm_set_duty params;
} buf;
struct ec_params_pwm_set_duty *params = &buf.params;
struct cros_ec_command *msg = &buf.msg;
memset(&buf, 0, sizeof(buf));
msg->version = 0;
msg->command = EC_CMD_PWM_SET_DUTY;
msg->insize = 0;
msg->outsize = sizeof(*params);
params->duty = duty;
params->pwm_type = EC_PWM_TYPE_GENERIC;
params->index = index;
return cros_ec_cmd_xfer_status(ec, msg);
}
static int __cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index,
u32 *result)
{
struct {
struct cros_ec_command msg;
union {
struct ec_params_pwm_get_duty params;
struct ec_response_pwm_get_duty resp;
};
} buf;
struct ec_params_pwm_get_duty *params = &buf.params;
struct ec_response_pwm_get_duty *resp = &buf.resp;
struct cros_ec_command *msg = &buf.msg;
int ret;
memset(&buf, 0, sizeof(buf));
msg->version = 0;
msg->command = EC_CMD_PWM_GET_DUTY;
msg->insize = sizeof(*params);
msg->outsize = sizeof(*resp);
params->pwm_type = EC_PWM_TYPE_GENERIC;
params->index = index;
ret = cros_ec_cmd_xfer_status(ec, msg);
if (result)
*result = msg->result;
if (ret < 0)
return ret;
return resp->duty;
}
static int cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index)
{
return __cros_ec_pwm_get_duty(ec, index, NULL);
}
static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip);
int duty_cycle;
/* The EC won't let us change the period */
if (state->period != EC_PWM_MAX_DUTY)
return -EINVAL;
/*
* EC doesn't separate the concept of duty cycle and enabled, but
* kernel does. Translate.
*/
duty_cycle = state->enabled ? state->duty_cycle : 0;
return cros_ec_pwm_set_duty(ec_pwm->ec, pwm->hwpwm, duty_cycle);
}
static void cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip);
int ret;
ret = cros_ec_pwm_get_duty(ec_pwm->ec, pwm->hwpwm);
if (ret < 0) {
dev_err(chip->dev, "error getting initial duty: %d\n", ret);
return;
}
state->enabled = (ret > 0);
state->period = EC_PWM_MAX_DUTY;
/* Note that "disabled" and "duty cycle == 0" are treated the same */
state->duty_cycle = ret;
}
static struct pwm_device *
cros_ec_pwm_xlate(struct pwm_chip *pc, const struct of_phandle_args *args)
{
struct pwm_device *pwm;
if (args->args[0] >= pc->npwm)
return ERR_PTR(-EINVAL);
pwm = pwm_request_from_chip(pc, args->args[0], NULL);
if (IS_ERR(pwm))
return pwm;
/* The EC won't let us change the period */
pwm->args.period = EC_PWM_MAX_DUTY;
return pwm;
}
static const struct pwm_ops cros_ec_pwm_ops = {
.get_state = cros_ec_pwm_get_state,
.apply = cros_ec_pwm_apply,
.owner = THIS_MODULE,
};
static int cros_ec_num_pwms(struct cros_ec_device *ec)
{
int i, ret;
/* The index field is only 8 bits */
for (i = 0; i <= U8_MAX; i++) {
u32 result = 0;
ret = __cros_ec_pwm_get_duty(ec, i, &result);
/* We want to parse EC protocol errors */
if (ret < 0 && !(ret == -EPROTO && result))
return ret;
/*
* We look for SUCCESS, INVALID_COMMAND, or INVALID_PARAM
* responses; everything else is treated as an error.
*/
if (result == EC_RES_INVALID_COMMAND)
return -ENODEV;
else if (result == EC_RES_INVALID_PARAM)
return i;
else if (result)
return -EPROTO;
}
return U8_MAX;
}
static int cros_ec_pwm_probe(struct platform_device *pdev)
{
struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct cros_ec_pwm_device *ec_pwm;
struct pwm_chip *chip;
int ret;
if (!ec) {
dev_err(dev, "no parent EC device\n");
return -EINVAL;
}
ec_pwm = devm_kzalloc(dev, sizeof(*ec_pwm), GFP_KERNEL);
if (!ec_pwm)
return -ENOMEM;
chip = &ec_pwm->chip;
ec_pwm->ec = ec;
/* PWM chip */
chip->dev = dev;
chip->ops = &cros_ec_pwm_ops;
chip->of_xlate = cros_ec_pwm_xlate;
chip->of_pwm_n_cells = 1;
chip->base = -1;
ret = cros_ec_num_pwms(ec);
if (ret < 0) {
dev_err(dev, "Couldn't find PWMs: %d\n", ret);
return ret;
}
chip->npwm = ret;
dev_dbg(dev, "Probed %u PWMs\n", chip->npwm);
ret = pwmchip_add(chip);
if (ret < 0) {
dev_err(dev, "cannot register PWM: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, ec_pwm);
return ret;
}
static int cros_ec_pwm_remove(struct platform_device *dev)
{
struct cros_ec_pwm_device *ec_pwm = platform_get_drvdata(dev);
struct pwm_chip *chip = &ec_pwm->chip;
return pwmchip_remove(chip);
}
#ifdef CONFIG_OF
static const struct of_device_id cros_ec_pwm_of_match[] = {
{ .compatible = "google,cros-ec-pwm" },
{},
};
MODULE_DEVICE_TABLE(of, cros_ec_pwm_of_match);
#endif
static struct platform_driver cros_ec_pwm_driver = {
.probe = cros_ec_pwm_probe,
.remove = cros_ec_pwm_remove,
.driver = {
.name = "cros-ec-pwm",
.of_match_table = of_match_ptr(cros_ec_pwm_of_match),
},
};
module_platform_driver(cros_ec_pwm_driver);
MODULE_ALIAS("platform:cros-ec-pwm");
MODULE_DESCRIPTION("ChromeOS EC PWM driver");
MODULE_LICENSE("GPL v2");
...@@ -25,6 +25,7 @@ struct lpc32xx_pwm_chip { ...@@ -25,6 +25,7 @@ struct lpc32xx_pwm_chip {
}; };
#define PWM_ENABLE BIT(31) #define PWM_ENABLE BIT(31)
#define PWM_PIN_LEVEL BIT(30)
#define to_lpc32xx_pwm_chip(_chip) \ #define to_lpc32xx_pwm_chip(_chip) \
container_of(_chip, struct lpc32xx_pwm_chip, chip) container_of(_chip, struct lpc32xx_pwm_chip, chip)
...@@ -103,6 +104,7 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev) ...@@ -103,6 +104,7 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev)
struct lpc32xx_pwm_chip *lpc32xx; struct lpc32xx_pwm_chip *lpc32xx;
struct resource *res; struct resource *res;
int ret; int ret;
u32 val;
lpc32xx = devm_kzalloc(&pdev->dev, sizeof(*lpc32xx), GFP_KERNEL); lpc32xx = devm_kzalloc(&pdev->dev, sizeof(*lpc32xx), GFP_KERNEL);
if (!lpc32xx) if (!lpc32xx)
...@@ -128,6 +130,11 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev) ...@@ -128,6 +130,11 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev)
return ret; return ret;
} }
/* When PWM is disable, configure the output to the default value */
val = readl(lpc32xx->base + (lpc32xx->chip.pwms[0].hwpwm << 2));
val &= ~PWM_PIN_LEVEL;
writel(val, lpc32xx->base + (lpc32xx->chip.pwms[0].hwpwm << 2));
platform_set_drvdata(pdev, lpc32xx); platform_set_drvdata(pdev, lpc32xx);
return 0; return 0;
......
...@@ -76,6 +76,7 @@ static const struct pci_device_id pwm_lpss_pci_ids[] = { ...@@ -76,6 +76,7 @@ static const struct pci_device_id pwm_lpss_pci_ids[] = {
{ PCI_VDEVICE(INTEL, 0x0ac8), (unsigned long)&pwm_lpss_bxt_info}, { PCI_VDEVICE(INTEL, 0x0ac8), (unsigned long)&pwm_lpss_bxt_info},
{ PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&pwm_lpss_byt_info}, { PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&pwm_lpss_byt_info},
{ PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&pwm_lpss_byt_info}, { PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&pwm_lpss_byt_info},
{ PCI_VDEVICE(INTEL, 0x11a5), (unsigned long)&pwm_lpss_bxt_info},
{ PCI_VDEVICE(INTEL, 0x1ac8), (unsigned long)&pwm_lpss_bxt_info}, { PCI_VDEVICE(INTEL, 0x1ac8), (unsigned long)&pwm_lpss_bxt_info},
{ PCI_VDEVICE(INTEL, 0x2288), (unsigned long)&pwm_lpss_bsw_info}, { PCI_VDEVICE(INTEL, 0x2288), (unsigned long)&pwm_lpss_bsw_info},
{ PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info}, { PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info},
......
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
#define PWM_SW_UPDATE BIT(30) #define PWM_SW_UPDATE BIT(30)
#define PWM_BASE_UNIT_SHIFT 8 #define PWM_BASE_UNIT_SHIFT 8
#define PWM_ON_TIME_DIV_MASK 0x000000ff #define PWM_ON_TIME_DIV_MASK 0x000000ff
#define PWM_DIVISION_CORRECTION 0x2
/* Size of each PWM register space if multiple */ /* Size of each PWM register space if multiple */
#define PWM_SIZE 0x400 #define PWM_SIZE 0x400
...@@ -92,8 +91,8 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, ...@@ -92,8 +91,8 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns) int duty_ns, int period_ns)
{ {
struct pwm_lpss_chip *lpwm = to_lpwm(chip); struct pwm_lpss_chip *lpwm = to_lpwm(chip);
u8 on_time_div; unsigned long long on_time_div;
unsigned long c, base_unit_range; unsigned long c = lpwm->info->clk_rate, base_unit_range;
unsigned long long base_unit, freq = NSEC_PER_SEC; unsigned long long base_unit, freq = NSEC_PER_SEC;
u32 ctrl; u32 ctrl;
...@@ -101,21 +100,18 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, ...@@ -101,21 +100,18 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm,
/* /*
* The equation is: * The equation is:
* base_unit = ((freq / c) * base_unit_range) + correction * base_unit = round(base_unit_range * freq / c)
*/ */
base_unit_range = BIT(lpwm->info->base_unit_bits); base_unit_range = BIT(lpwm->info->base_unit_bits);
base_unit = freq * base_unit_range; freq *= base_unit_range;
c = lpwm->info->clk_rate; base_unit = DIV_ROUND_CLOSEST_ULL(freq, c);
if (!c)
return -EINVAL;
do_div(base_unit, c);
base_unit += PWM_DIVISION_CORRECTION;
if (duty_ns <= 0) if (duty_ns <= 0)
duty_ns = 1; duty_ns = 1;
on_time_div = 255 - (255 * duty_ns / period_ns); on_time_div = 255ULL * duty_ns;
do_div(on_time_div, period_ns);
on_time_div = 255ULL - on_time_div;
pm_runtime_get_sync(chip->dev); pm_runtime_get_sync(chip->dev);
...@@ -169,6 +165,7 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, ...@@ -169,6 +165,7 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
const struct pwm_lpss_boardinfo *info) const struct pwm_lpss_boardinfo *info)
{ {
struct pwm_lpss_chip *lpwm; struct pwm_lpss_chip *lpwm;
unsigned long c;
int ret; int ret;
lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL); lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL);
...@@ -180,6 +177,11 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, ...@@ -180,6 +177,11 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
return ERR_CAST(lpwm->regs); return ERR_CAST(lpwm->regs);
lpwm->info = info; lpwm->info = info;
c = lpwm->info->clk_rate;
if (!c)
return ERR_PTR(-EINVAL);
lpwm->chip.dev = dev; lpwm->chip.dev = dev;
lpwm->chip.ops = &pwm_lpss_ops; lpwm->chip.ops = &pwm_lpss_ops;
lpwm->chip.base = -1; lpwm->chip.base = -1;
......
...@@ -47,10 +47,14 @@ struct rockchip_pwm_regs { ...@@ -47,10 +47,14 @@ struct rockchip_pwm_regs {
struct rockchip_pwm_data { struct rockchip_pwm_data {
struct rockchip_pwm_regs regs; struct rockchip_pwm_regs regs;
unsigned int prescaler; unsigned int prescaler;
bool supports_polarity;
const struct pwm_ops *ops; const struct pwm_ops *ops;
void (*set_enable)(struct pwm_chip *chip, void (*set_enable)(struct pwm_chip *chip,
struct pwm_device *pwm, bool enable); 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);
}; };
static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c) static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c)
...@@ -59,7 +63,8 @@ static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c) ...@@ -59,7 +63,8 @@ static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c)
} }
static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip, static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip,
struct pwm_device *pwm, bool enable) struct pwm_device *pwm, bool enable,
enum pwm_polarity polarity)
{ {
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN; u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN;
...@@ -75,15 +80,29 @@ static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip, ...@@ -75,15 +80,29 @@ static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip,
writel_relaxed(val, pc->base + pc->data->regs.ctrl); 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, static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip,
struct pwm_device *pwm, bool enable) struct pwm_device *pwm, bool enable,
enum pwm_polarity polarity)
{ {
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
PWM_CONTINUOUS; PWM_CONTINUOUS;
u32 val; u32 val;
if (pwm_get_polarity(pwm) == PWM_POLARITY_INVERSED) if (polarity == PWM_POLARITY_INVERSED)
enable_conf |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE; enable_conf |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE;
else else
enable_conf |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE; enable_conf |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE;
...@@ -98,13 +117,59 @@ static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip, ...@@ -98,13 +117,59 @@ static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip,
writel_relaxed(val, pc->base + pc->data->regs.ctrl); 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);
unsigned long clk_rate;
u64 tmp;
int ret;
ret = clk_enable(pc->clk);
if (ret)
return;
clk_rate = clk_get_rate(pc->clk);
tmp = readl_relaxed(pc->base + pc->data->regs.period);
tmp *= pc->data->prescaler * NSEC_PER_SEC;
state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
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);
pc->data->get_state(chip, pwm, state);
clk_disable(pc->clk);
}
static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns) int duty_ns, int period_ns)
{ {
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
unsigned long period, duty; unsigned long period, duty;
u64 clk_rate, div; u64 clk_rate, div;
int ret;
clk_rate = clk_get_rate(pc->clk); clk_rate = clk_get_rate(pc->clk);
...@@ -114,74 +179,72 @@ static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, ...@@ -114,74 +179,72 @@ static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* default prescaler value for all practical clock rate values. * default prescaler value for all practical clock rate values.
*/ */
div = clk_rate * period_ns; div = clk_rate * period_ns;
do_div(div, pc->data->prescaler * NSEC_PER_SEC); period = DIV_ROUND_CLOSEST_ULL(div,
period = div; pc->data->prescaler * NSEC_PER_SEC);
div = clk_rate * duty_ns; div = clk_rate * duty_ns;
do_div(div, pc->data->prescaler * NSEC_PER_SEC); duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC);
duty = div;
ret = clk_enable(pc->clk);
if (ret)
return ret;
writel(period, pc->base + pc->data->regs.period); writel(period, pc->base + pc->data->regs.period);
writel(duty, pc->base + pc->data->regs.duty); writel(duty, pc->base + pc->data->regs.duty);
writel(0, pc->base + pc->data->regs.cntr);
clk_disable(pc->clk);
return 0;
}
static int rockchip_pwm_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
/*
* No action needed here because pwm->polarity will be set by the core
* and the core will only change polarity when the PWM is not enabled.
* We'll handle things in set_enable().
*/
return 0; return 0;
} }
static int rockchip_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{ {
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
struct pwm_state curstate;
bool enabled;
int ret; int ret;
pwm_get_state(pwm, &curstate);
enabled = curstate.enabled;
ret = clk_enable(pc->clk); ret = clk_enable(pc->clk);
if (ret) if (ret)
return ret; return ret;
pc->data->set_enable(chip, pwm, true); if (state->polarity != curstate.polarity && enabled) {
pc->data->set_enable(chip, pwm, false, state->polarity);
enabled = false;
}
return 0; ret = rockchip_pwm_config(chip, pwm, state->duty_cycle, state->period);
} if (ret) {
if (enabled != curstate.enabled)
pc->data->set_enable(chip, pwm, !enabled,
state->polarity);
static void rockchip_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) goto out;
{ }
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
if (state->enabled != enabled)
pc->data->set_enable(chip, pwm, state->enabled,
state->polarity);
pc->data->set_enable(chip, pwm, false); /*
* Update the state with the real hardware, which can differ a bit
* because of period/duty_cycle approximation.
*/
rockchip_pwm_get_state(chip, pwm, state);
out:
clk_disable(pc->clk); clk_disable(pc->clk);
return ret;
} }
static const struct pwm_ops rockchip_pwm_ops_v1 = { static const struct pwm_ops rockchip_pwm_ops_v1 = {
.config = rockchip_pwm_config, .get_state = rockchip_pwm_get_state,
.enable = rockchip_pwm_enable, .apply = rockchip_pwm_apply,
.disable = rockchip_pwm_disable,
.owner = THIS_MODULE, .owner = THIS_MODULE,
}; };
static const struct pwm_ops rockchip_pwm_ops_v2 = { static const struct pwm_ops rockchip_pwm_ops_v2 = {
.config = rockchip_pwm_config, .get_state = rockchip_pwm_get_state,
.set_polarity = rockchip_pwm_set_polarity, .apply = rockchip_pwm_apply,
.enable = rockchip_pwm_enable,
.disable = rockchip_pwm_disable,
.owner = THIS_MODULE, .owner = THIS_MODULE,
}; };
...@@ -195,6 +258,7 @@ static const struct rockchip_pwm_data pwm_data_v1 = { ...@@ -195,6 +258,7 @@ static const struct rockchip_pwm_data pwm_data_v1 = {
.prescaler = 2, .prescaler = 2,
.ops = &rockchip_pwm_ops_v1, .ops = &rockchip_pwm_ops_v1,
.set_enable = rockchip_pwm_set_enable_v1, .set_enable = rockchip_pwm_set_enable_v1,
.get_state = rockchip_pwm_get_state_v1,
}; };
static const struct rockchip_pwm_data pwm_data_v2 = { static const struct rockchip_pwm_data pwm_data_v2 = {
...@@ -205,8 +269,10 @@ static const struct rockchip_pwm_data pwm_data_v2 = { ...@@ -205,8 +269,10 @@ static const struct rockchip_pwm_data pwm_data_v2 = {
.ctrl = 0x0c, .ctrl = 0x0c,
}, },
.prescaler = 1, .prescaler = 1,
.supports_polarity = true,
.ops = &rockchip_pwm_ops_v2, .ops = &rockchip_pwm_ops_v2,
.set_enable = rockchip_pwm_set_enable_v2, .set_enable = rockchip_pwm_set_enable_v2,
.get_state = rockchip_pwm_get_state_v2,
}; };
static const struct rockchip_pwm_data pwm_data_vop = { static const struct rockchip_pwm_data pwm_data_vop = {
...@@ -217,8 +283,10 @@ static const struct rockchip_pwm_data pwm_data_vop = { ...@@ -217,8 +283,10 @@ static const struct rockchip_pwm_data pwm_data_vop = {
.ctrl = 0x00, .ctrl = 0x00,
}, },
.prescaler = 1, .prescaler = 1,
.supports_polarity = true,
.ops = &rockchip_pwm_ops_v2, .ops = &rockchip_pwm_ops_v2,
.set_enable = rockchip_pwm_set_enable_v2, .set_enable = rockchip_pwm_set_enable_v2,
.get_state = rockchip_pwm_get_state_v2,
}; };
static const struct of_device_id rockchip_pwm_dt_ids[] = { static const struct of_device_id rockchip_pwm_dt_ids[] = {
...@@ -253,7 +321,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev) ...@@ -253,7 +321,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
if (IS_ERR(pc->clk)) if (IS_ERR(pc->clk))
return PTR_ERR(pc->clk); return PTR_ERR(pc->clk);
ret = clk_prepare(pc->clk); ret = clk_prepare_enable(pc->clk);
if (ret) if (ret)
return ret; return ret;
...@@ -265,7 +333,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev) ...@@ -265,7 +333,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
pc->chip.base = -1; pc->chip.base = -1;
pc->chip.npwm = 1; pc->chip.npwm = 1;
if (pc->data->ops->set_polarity) { if (pc->data->supports_polarity) {
pc->chip.of_xlate = of_pwm_xlate_with_flags; pc->chip.of_xlate = of_pwm_xlate_with_flags;
pc->chip.of_pwm_n_cells = 3; pc->chip.of_pwm_n_cells = 3;
} }
...@@ -276,6 +344,10 @@ static int rockchip_pwm_probe(struct platform_device *pdev) ...@@ -276,6 +344,10 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
} }
/* 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 ret; return ret;
} }
...@@ -283,6 +355,20 @@ static int rockchip_pwm_remove(struct platform_device *pdev) ...@@ -283,6 +355,20 @@ static int rockchip_pwm_remove(struct platform_device *pdev)
{ {
struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev); struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev);
/*
* Disable the PWM clk before unpreparing it if the PWM device is still
* running. This should only happen when the last PWM user left it
* enabled, or when nobody requested a PWM that was previously enabled
* by the bootloader.
*
* FIXME: Maybe the core should disable all PWM devices in
* pwmchip_remove(). In this case we'd only have to call
* clk_unprepare() after pwmchip_remove().
*
*/
if (pwm_is_enabled(pc->chip.pwms))
clk_disable(pc->clk);
clk_unprepare(pc->clk); clk_unprepare(pc->clk);
return pwmchip_remove(&pc->chip); return pwmchip_remove(&pc->chip);
......
/*
* Copyright (C) 2016 Linaro Ltd.
*
* Author: Linus Walleij <linus.walleij@linaro.org>
*
* 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/bitops.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/mfd/stmpe.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#define STMPE24XX_PWMCS 0x30
#define PWMCS_EN_PWM0 BIT(0)
#define PWMCS_EN_PWM1 BIT(1)
#define PWMCS_EN_PWM2 BIT(2)
#define STMPE24XX_PWMIC0 0x38
#define STMPE24XX_PWMIC1 0x39
#define STMPE24XX_PWMIC2 0x3a
#define STMPE_PWM_24XX_PINBASE 21
struct stmpe_pwm {
struct stmpe *stmpe;
struct pwm_chip chip;
u8 last_duty;
};
static inline struct stmpe_pwm *to_stmpe_pwm(struct pwm_chip *chip)
{
return container_of(chip, struct stmpe_pwm, chip);
}
static int stmpe_24xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip);
u8 value;
int ret;
ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS);
if (ret < 0) {
dev_err(chip->dev, "error reading PWM#%u control\n",
pwm->hwpwm);
return ret;
}
value = ret | BIT(pwm->hwpwm);
ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value);
if (ret) {
dev_err(chip->dev, "error writing PWM#%u control\n",
pwm->hwpwm);
return ret;
}
return 0;
}
static void stmpe_24xx_pwm_disable(struct pwm_chip *chip,
struct pwm_device *pwm)
{
struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip);
u8 value;
int ret;
ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS);
if (ret < 0) {
dev_err(chip->dev, "error reading PWM#%u control\n",
pwm->hwpwm);
return;
}
value = ret & ~BIT(pwm->hwpwm);
ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value);
if (ret) {
dev_err(chip->dev, "error writing PWM#%u control\n",
pwm->hwpwm);
return;
}
}
/* STMPE 24xx PWM instructions */
#define SMAX 0x007f
#define SMIN 0x00ff
#define GTS 0x0000
#define LOAD BIT(14) /* Only available on 2403 */
#define RAMPUP 0x0000
#define RAMPDOWN BIT(7)
#define PRESCALE_512 BIT(14)
#define STEPTIME_1 BIT(8)
#define BRANCH (BIT(15) | BIT(13))
static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip);
unsigned int i, pin;
u16 program[3] = {
SMAX,
GTS,
GTS,
};
u8 offset;
int ret;
/* Make sure we are disabled */
if (pwm_is_enabled(pwm)) {
stmpe_24xx_pwm_disable(chip, pwm);
} else {
/* Connect the PWM to the pin */
pin = pwm->hwpwm;
/* On STMPE2401 and 2403 pins 21,22,23 are used */
if (stmpe_pwm->stmpe->partnum == STMPE2401 ||
stmpe_pwm->stmpe->partnum == STMPE2403)
pin += STMPE_PWM_24XX_PINBASE;
ret = stmpe_set_altfunc(stmpe_pwm->stmpe, BIT(pin),
STMPE_BLOCK_PWM);
if (ret) {
dev_err(chip->dev, "unable to connect PWM#%u to pin\n",
pwm->hwpwm);
return ret;
}
}
/* STMPE24XX */
switch (pwm->hwpwm) {
case 0:
offset = STMPE24XX_PWMIC0;
break;
case 1:
offset = STMPE24XX_PWMIC1;
break;
case 2:
offset = STMPE24XX_PWMIC1;
break;
default:
/* Should not happen as npwm is 3 */
return -ENODEV;
}
dev_dbg(chip->dev, "PWM#%u: config duty %d ns, period %d ns\n",
pwm->hwpwm, duty_ns, period_ns);
if (duty_ns == 0) {
if (stmpe_pwm->stmpe->partnum == STMPE2401)
program[0] = SMAX; /* off all the time */
if (stmpe_pwm->stmpe->partnum == STMPE2403)
program[0] = LOAD | 0xff; /* LOAD 0xff */
stmpe_pwm->last_duty = 0x00;
} else if (duty_ns == period_ns) {
if (stmpe_pwm->stmpe->partnum == STMPE2401)
program[0] = SMIN; /* on all the time */
if (stmpe_pwm->stmpe->partnum == STMPE2403)
program[0] = LOAD | 0x00; /* LOAD 0x00 */
stmpe_pwm->last_duty = 0xff;
} else {
u8 value, last = stmpe_pwm->last_duty;
unsigned long duty;
/*
* Counter goes from 0x00 to 0xff repeatedly at 32768 Hz,
* (means a period of 30517 ns) then this is compared to the
* counter from the ramp, if this is >= PWM counter the output
* is high. With LOAD we can define how much of the cycle it
* is on.
*
* Prescale = 0 -> 2 kHz -> T = 1/f = 488281.25 ns
*/
/* Scale to 0..0xff */
duty = duty_ns * 256;
duty = DIV_ROUND_CLOSEST(duty, period_ns);
value = duty;
if (value == last) {
/* Run the old program */
if (pwm_is_enabled(pwm))
stmpe_24xx_pwm_enable(chip, pwm);
return 0;
} else if (stmpe_pwm->stmpe->partnum == STMPE2403) {
/* STMPE2403 can simply set the right PWM value */
program[0] = LOAD | value;
program[1] = 0x0000;
} else if (stmpe_pwm->stmpe->partnum == STMPE2401) {
/* STMPE2401 need a complex program */
u16 incdec = 0x0000;
if (last < value)
/* Count up */
incdec = RAMPUP | (value - last);
else
/* Count down */
incdec = RAMPDOWN | (last - value);
/* Step to desired value, smoothly */
program[0] = PRESCALE_512 | STEPTIME_1 | incdec;
/* Loop eternally to 0x00 */
program[1] = BRANCH;
}
dev_dbg(chip->dev,
"PWM#%u: value = %02x, last_duty = %02x, program=%04x,%04x,%04x\n",
pwm->hwpwm, value, last, program[0], program[1],
program[2]);
stmpe_pwm->last_duty = value;
}
/*
* We can write programs of up to 64 16-bit words into this channel.
*/
for (i = 0; i < ARRAY_SIZE(program); i++) {
u8 value;
value = (program[i] >> 8) & 0xff;
ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value);
if (ret) {
dev_err(chip->dev, "error writing register %02x: %d\n",
offset, ret);
return ret;
}
value = program[i] & 0xff;
ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value);
if (ret) {
dev_err(chip->dev, "error writing register %02x: %d\n",
offset, ret);
return ret;
}
}
/* If we were enabled, re-enable this PWM */
if (pwm_is_enabled(pwm))
stmpe_24xx_pwm_enable(chip, pwm);
/* Sleep for 200ms so we're sure it will take effect */
msleep(200);
dev_dbg(chip->dev, "programmed PWM#%u, %u bytes\n", pwm->hwpwm, i);
return 0;
}
static const struct pwm_ops stmpe_24xx_pwm_ops = {
.config = stmpe_24xx_pwm_config,
.enable = stmpe_24xx_pwm_enable,
.disable = stmpe_24xx_pwm_disable,
.owner = THIS_MODULE,
};
static int __init stmpe_pwm_probe(struct platform_device *pdev)
{
struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
struct stmpe_pwm *pwm;
int ret;
pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
if (!pwm)
return -ENOMEM;
pwm->stmpe = stmpe;
pwm->chip.dev = &pdev->dev;
pwm->chip.base = -1;
if (stmpe->partnum == STMPE2401 || stmpe->partnum == STMPE2403) {
pwm->chip.ops = &stmpe_24xx_pwm_ops;
pwm->chip.npwm = 3;
} else {
if (stmpe->partnum == STMPE1601)
dev_err(&pdev->dev, "STMPE1601 not yet supported\n");
else
dev_err(&pdev->dev, "Unknown STMPE PWM\n");
return -ENODEV;
}
ret = stmpe_enable(stmpe, STMPE_BLOCK_PWM);
if (ret)
return ret;
ret = pwmchip_add(&pwm->chip);
if (ret) {
stmpe_disable(stmpe, STMPE_BLOCK_PWM);
return ret;
}
platform_set_drvdata(pdev, pwm);
return 0;
}
static struct platform_driver stmpe_pwm_driver = {
.driver = {
.name = "stmpe-pwm",
},
};
builtin_platform_driver_probe(stmpe_pwm_driver, stmpe_pwm_probe);
...@@ -26,9 +26,11 @@ ...@@ -26,9 +26,11 @@
#include <linux/io.h> #include <linux/io.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_device.h>
#include <linux/pwm.h> #include <linux/pwm.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/reset.h>
#define PWM_ENABLE (1 << 31) #define PWM_ENABLE (1 << 31)
#define PWM_DUTY_WIDTH 8 #define PWM_DUTY_WIDTH 8
...@@ -36,15 +38,20 @@ ...@@ -36,15 +38,20 @@
#define PWM_SCALE_WIDTH 13 #define PWM_SCALE_WIDTH 13
#define PWM_SCALE_SHIFT 0 #define PWM_SCALE_SHIFT 0
#define NUM_PWM 4 struct tegra_pwm_soc {
unsigned int num_channels;
};
struct tegra_pwm_chip { struct tegra_pwm_chip {
struct pwm_chip chip; struct pwm_chip chip;
struct device *dev; struct device *dev;
struct clk *clk;
struct reset_control*rst;
struct clk *clk; void __iomem *regs;
void __iomem *mmio_base; const struct tegra_pwm_soc *soc;
}; };
static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip) static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip)
...@@ -54,20 +61,20 @@ static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip) ...@@ -54,20 +61,20 @@ static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip)
static inline u32 pwm_readl(struct tegra_pwm_chip *chip, unsigned int num) static inline u32 pwm_readl(struct tegra_pwm_chip *chip, unsigned int num)
{ {
return readl(chip->mmio_base + (num << 4)); return readl(chip->regs + (num << 4));
} }
static inline void pwm_writel(struct tegra_pwm_chip *chip, unsigned int num, static inline void pwm_writel(struct tegra_pwm_chip *chip, unsigned int num,
unsigned long val) unsigned long val)
{ {
writel(val, chip->mmio_base + (num << 4)); writel(val, chip->regs + (num << 4));
} }
static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns) int duty_ns, int period_ns)
{ {
struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
unsigned long long c; unsigned long long c = duty_ns;
unsigned long rate, hz; unsigned long rate, hz;
u32 val = 0; u32 val = 0;
int err; int err;
...@@ -77,7 +84,8 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, ...@@ -77,7 +84,8 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* per (1 << PWM_DUTY_WIDTH) cycles and make sure to round to the * per (1 << PWM_DUTY_WIDTH) cycles and make sure to round to the
* nearest integer during division. * nearest integer during division.
*/ */
c = duty_ns * ((1 << PWM_DUTY_WIDTH) - 1) + period_ns / 2; c *= (1 << PWM_DUTY_WIDTH);
c += period_ns / 2;
do_div(c, period_ns); do_div(c, period_ns);
val = (u32)c << PWM_DUTY_SHIFT; val = (u32)c << PWM_DUTY_SHIFT;
...@@ -176,12 +184,13 @@ static int tegra_pwm_probe(struct platform_device *pdev) ...@@ -176,12 +184,13 @@ static int tegra_pwm_probe(struct platform_device *pdev)
if (!pwm) if (!pwm)
return -ENOMEM; return -ENOMEM;
pwm->soc = of_device_get_match_data(&pdev->dev);
pwm->dev = &pdev->dev; pwm->dev = &pdev->dev;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r); pwm->regs = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(pwm->mmio_base)) if (IS_ERR(pwm->regs))
return PTR_ERR(pwm->mmio_base); return PTR_ERR(pwm->regs);
platform_set_drvdata(pdev, pwm); platform_set_drvdata(pdev, pwm);
...@@ -189,14 +198,24 @@ static int tegra_pwm_probe(struct platform_device *pdev) ...@@ -189,14 +198,24 @@ static int tegra_pwm_probe(struct platform_device *pdev)
if (IS_ERR(pwm->clk)) if (IS_ERR(pwm->clk))
return PTR_ERR(pwm->clk); return PTR_ERR(pwm->clk);
pwm->rst = devm_reset_control_get(&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);
return ret;
}
reset_control_deassert(pwm->rst);
pwm->chip.dev = &pdev->dev; pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &tegra_pwm_ops; pwm->chip.ops = &tegra_pwm_ops;
pwm->chip.base = -1; pwm->chip.base = -1;
pwm->chip.npwm = NUM_PWM; pwm->chip.npwm = pwm->soc->num_channels;
ret = pwmchip_add(&pwm->chip); ret = pwmchip_add(&pwm->chip);
if (ret < 0) { if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
reset_control_assert(pwm->rst);
return ret; return ret;
} }
...@@ -206,12 +225,17 @@ static int tegra_pwm_probe(struct platform_device *pdev) ...@@ -206,12 +225,17 @@ static int tegra_pwm_probe(struct platform_device *pdev)
static int tegra_pwm_remove(struct platform_device *pdev) static int tegra_pwm_remove(struct platform_device *pdev)
{ {
struct tegra_pwm_chip *pc = platform_get_drvdata(pdev); struct tegra_pwm_chip *pc = platform_get_drvdata(pdev);
int i; unsigned int i;
int err;
if (WARN_ON(!pc)) if (WARN_ON(!pc))
return -ENODEV; return -ENODEV;
for (i = 0; i < NUM_PWM; i++) { err = clk_prepare_enable(pc->clk);
if (err < 0)
return err;
for (i = 0; i < pc->chip.npwm; i++) {
struct pwm_device *pwm = &pc->chip.pwms[i]; struct pwm_device *pwm = &pc->chip.pwms[i];
if (!pwm_is_enabled(pwm)) if (!pwm_is_enabled(pwm))
...@@ -223,12 +247,23 @@ static int tegra_pwm_remove(struct platform_device *pdev) ...@@ -223,12 +247,23 @@ static int tegra_pwm_remove(struct platform_device *pdev)
clk_disable_unprepare(pc->clk); clk_disable_unprepare(pc->clk);
} }
reset_control_assert(pc->rst);
clk_disable_unprepare(pc->clk);
return pwmchip_remove(&pc->chip); return pwmchip_remove(&pc->chip);
} }
static const struct tegra_pwm_soc tegra20_pwm_soc = {
.num_channels = 4,
};
static const struct tegra_pwm_soc tegra186_pwm_soc = {
.num_channels = 1,
};
static const struct of_device_id tegra_pwm_of_match[] = { static const struct of_device_id tegra_pwm_of_match[] = {
{ .compatible = "nvidia,tegra20-pwm" }, { .compatible = "nvidia,tegra20-pwm", .data = &tegra20_pwm_soc },
{ .compatible = "nvidia,tegra30-pwm" }, { .compatible = "nvidia,tegra186-pwm", .data = &tegra186_pwm_soc },
{ } { }
}; };
......
...@@ -27,8 +27,6 @@ ...@@ -27,8 +27,6 @@
#include <linux/pwm.h> #include <linux/pwm.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include "pwm-tipwmss.h"
/* ECAP registers and bits definitions */ /* ECAP registers and bits definitions */
#define CAP1 0x08 #define CAP1 0x08
#define CAP2 0x0C #define CAP2 0x0C
...@@ -195,6 +193,7 @@ static const struct pwm_ops ecap_pwm_ops = { ...@@ -195,6 +193,7 @@ static const struct pwm_ops ecap_pwm_ops = {
}; };
static const struct of_device_id ecap_of_match[] = { static const struct of_device_id ecap_of_match[] = {
{ .compatible = "ti,am3352-ecap" },
{ .compatible = "ti,am33xx-ecap" }, { .compatible = "ti,am33xx-ecap" },
{}, {},
}; };
...@@ -202,17 +201,24 @@ MODULE_DEVICE_TABLE(of, ecap_of_match); ...@@ -202,17 +201,24 @@ MODULE_DEVICE_TABLE(of, ecap_of_match);
static int ecap_pwm_probe(struct platform_device *pdev) static int ecap_pwm_probe(struct platform_device *pdev)
{ {
struct device_node *np = pdev->dev.of_node;
int ret; int ret;
struct resource *r; struct resource *r;
struct clk *clk; struct clk *clk;
struct ecap_pwm_chip *pc; struct ecap_pwm_chip *pc;
u16 status;
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
if (!pc) if (!pc)
return -ENOMEM; return -ENOMEM;
clk = devm_clk_get(&pdev->dev, "fck"); clk = devm_clk_get(&pdev->dev, "fck");
if (IS_ERR(clk)) {
if (of_device_is_compatible(np, "ti,am33xx-ecap")) {
dev_warn(&pdev->dev, "Binding is obsolete.\n");
clk = devm_clk_get(pdev->dev.parent, "fck");
}
}
if (IS_ERR(clk)) { if (IS_ERR(clk)) {
dev_err(&pdev->dev, "failed to get clock\n"); dev_err(&pdev->dev, "failed to get clock\n");
return PTR_ERR(clk); return PTR_ERR(clk);
...@@ -243,40 +249,15 @@ static int ecap_pwm_probe(struct platform_device *pdev) ...@@ -243,40 +249,15 @@ static int ecap_pwm_probe(struct platform_device *pdev)
} }
pm_runtime_enable(&pdev->dev); pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
status = pwmss_submodule_state_change(pdev->dev.parent,
PWMSS_ECAPCLK_EN);
if (!(status & PWMSS_ECAPCLK_EN_ACK)) {
dev_err(&pdev->dev, "PWMSS config space clock enable failed\n");
ret = -EINVAL;
goto pwmss_clk_failure;
}
pm_runtime_put_sync(&pdev->dev);
platform_set_drvdata(pdev, pc); platform_set_drvdata(pdev, pc);
return 0; return 0;
pwmss_clk_failure:
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
pwmchip_remove(&pc->chip);
return ret;
} }
static int ecap_pwm_remove(struct platform_device *pdev) static int ecap_pwm_remove(struct platform_device *pdev)
{ {
struct ecap_pwm_chip *pc = platform_get_drvdata(pdev); struct ecap_pwm_chip *pc = platform_get_drvdata(pdev);
pm_runtime_get_sync(&pdev->dev);
/*
* Due to hardware misbehaviour, acknowledge of the stop_req
* is missing. Hence checking of the status bit skipped.
*/
pwmss_submodule_state_change(pdev->dev.parent, PWMSS_ECAPCLK_STOP_REQ);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev); pm_runtime_disable(&pdev->dev);
return pwmchip_remove(&pc->chip); return pwmchip_remove(&pc->chip);
} }
......
...@@ -27,8 +27,6 @@ ...@@ -27,8 +27,6 @@
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include "pwm-tipwmss.h"
/* EHRPWM registers and bits definitions */ /* EHRPWM registers and bits definitions */
/* Time base module registers */ /* Time base module registers */
...@@ -426,6 +424,7 @@ static const struct pwm_ops ehrpwm_pwm_ops = { ...@@ -426,6 +424,7 @@ static const struct pwm_ops ehrpwm_pwm_ops = {
}; };
static const struct of_device_id ehrpwm_of_match[] = { static const struct of_device_id ehrpwm_of_match[] = {
{ .compatible = "ti,am3352-ehrpwm" },
{ .compatible = "ti,am33xx-ehrpwm" }, { .compatible = "ti,am33xx-ehrpwm" },
{}, {},
}; };
...@@ -433,17 +432,24 @@ MODULE_DEVICE_TABLE(of, ehrpwm_of_match); ...@@ -433,17 +432,24 @@ MODULE_DEVICE_TABLE(of, ehrpwm_of_match);
static int ehrpwm_pwm_probe(struct platform_device *pdev) static int ehrpwm_pwm_probe(struct platform_device *pdev)
{ {
struct device_node *np = pdev->dev.of_node;
int ret; int ret;
struct resource *r; struct resource *r;
struct clk *clk; struct clk *clk;
struct ehrpwm_pwm_chip *pc; struct ehrpwm_pwm_chip *pc;
u16 status;
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
if (!pc) if (!pc)
return -ENOMEM; return -ENOMEM;
clk = devm_clk_get(&pdev->dev, "fck"); clk = devm_clk_get(&pdev->dev, "fck");
if (IS_ERR(clk)) {
if (of_device_is_compatible(np, "ti,am33xx-ecap")) {
dev_warn(&pdev->dev, "Binding is obsolete.\n");
clk = devm_clk_get(pdev->dev.parent, "fck");
}
}
if (IS_ERR(clk)) { if (IS_ERR(clk)) {
dev_err(&pdev->dev, "failed to get clock\n"); dev_err(&pdev->dev, "failed to get clock\n");
return PTR_ERR(clk); return PTR_ERR(clk);
...@@ -487,27 +493,9 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev) ...@@ -487,27 +493,9 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
} }
pm_runtime_enable(&pdev->dev); pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
status = pwmss_submodule_state_change(pdev->dev.parent,
PWMSS_EPWMCLK_EN);
if (!(status & PWMSS_EPWMCLK_EN_ACK)) {
dev_err(&pdev->dev, "PWMSS config space clock enable failed\n");
ret = -EINVAL;
goto pwmss_clk_failure;
}
pm_runtime_put_sync(&pdev->dev);
platform_set_drvdata(pdev, pc); platform_set_drvdata(pdev, pc);
return 0; return 0;
pwmss_clk_failure:
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
pwmchip_remove(&pc->chip);
clk_unprepare(pc->tbclk);
return ret;
} }
static int ehrpwm_pwm_remove(struct platform_device *pdev) static int ehrpwm_pwm_remove(struct platform_device *pdev)
...@@ -516,14 +504,6 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev) ...@@ -516,14 +504,6 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev)
clk_unprepare(pc->tbclk); clk_unprepare(pc->tbclk);
pm_runtime_get_sync(&pdev->dev);
/*
* Due to hardware misbehaviour, acknowledge of the stop_req
* is missing. Hence checking of the status bit skipped.
*/
pwmss_submodule_state_change(pdev->dev.parent, PWMSS_EPWMCLK_STOP_REQ);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_put_sync(&pdev->dev); pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev); pm_runtime_disable(&pdev->dev);
return pwmchip_remove(&pc->chip); return pwmchip_remove(&pc->chip);
......
...@@ -22,32 +22,6 @@ ...@@ -22,32 +22,6 @@
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include "pwm-tipwmss.h"
#define PWMSS_CLKCONFIG 0x8 /* Clock gating reg */
#define PWMSS_CLKSTATUS 0xc /* Clock gating status reg */
struct pwmss_info {
void __iomem *mmio_base;
struct mutex pwmss_lock;
u16 pwmss_clkconfig;
};
u16 pwmss_submodule_state_change(struct device *dev, int set)
{
struct pwmss_info *info = dev_get_drvdata(dev);
u16 val;
mutex_lock(&info->pwmss_lock);
val = readw(info->mmio_base + PWMSS_CLKCONFIG);
val |= set;
writew(val , info->mmio_base + PWMSS_CLKCONFIG);
mutex_unlock(&info->pwmss_lock);
return readw(info->mmio_base + PWMSS_CLKSTATUS);
}
EXPORT_SYMBOL(pwmss_submodule_state_change);
static const struct of_device_id pwmss_of_match[] = { static const struct of_device_id pwmss_of_match[] = {
{ .compatible = "ti,am33xx-pwmss" }, { .compatible = "ti,am33xx-pwmss" },
{}, {},
...@@ -57,24 +31,10 @@ MODULE_DEVICE_TABLE(of, pwmss_of_match); ...@@ -57,24 +31,10 @@ MODULE_DEVICE_TABLE(of, pwmss_of_match);
static int pwmss_probe(struct platform_device *pdev) static int pwmss_probe(struct platform_device *pdev)
{ {
int ret; int ret;
struct resource *r;
struct pwmss_info *info;
struct device_node *node = pdev->dev.of_node; struct device_node *node = pdev->dev.of_node;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
mutex_init(&info->pwmss_lock);
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
info->mmio_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(info->mmio_base))
return PTR_ERR(info->mmio_base);
pm_runtime_enable(&pdev->dev); pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev); pm_runtime_get_sync(&pdev->dev);
platform_set_drvdata(pdev, info);
/* Populate all the child nodes here... */ /* Populate all the child nodes here... */
ret = of_platform_populate(node, NULL, NULL, &pdev->dev); ret = of_platform_populate(node, NULL, NULL, &pdev->dev);
...@@ -86,30 +46,21 @@ static int pwmss_probe(struct platform_device *pdev) ...@@ -86,30 +46,21 @@ static int pwmss_probe(struct platform_device *pdev)
static int pwmss_remove(struct platform_device *pdev) static int pwmss_remove(struct platform_device *pdev)
{ {
struct pwmss_info *info = platform_get_drvdata(pdev);
pm_runtime_put_sync(&pdev->dev); pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev); pm_runtime_disable(&pdev->dev);
mutex_destroy(&info->pwmss_lock);
return 0; return 0;
} }
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
static int pwmss_suspend(struct device *dev) static int pwmss_suspend(struct device *dev)
{ {
struct pwmss_info *info = dev_get_drvdata(dev);
info->pwmss_clkconfig = readw(info->mmio_base + PWMSS_CLKCONFIG);
pm_runtime_put_sync(dev); pm_runtime_put_sync(dev);
return 0; return 0;
} }
static int pwmss_resume(struct device *dev) static int pwmss_resume(struct device *dev)
{ {
struct pwmss_info *info = dev_get_drvdata(dev);
pm_runtime_get_sync(dev); pm_runtime_get_sync(dev);
writew(info->pwmss_clkconfig, info->mmio_base + PWMSS_CLKCONFIG);
return 0; return 0;
} }
#endif #endif
......
/*
* TI PWM Subsystem driver
*
* Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef __TIPWMSS_H
#define __TIPWMSS_H
/* PWM substem clock gating */
#define PWMSS_ECAPCLK_EN BIT(0)
#define PWMSS_ECAPCLK_STOP_REQ BIT(1)
#define PWMSS_EPWMCLK_EN BIT(8)
#define PWMSS_EPWMCLK_STOP_REQ BIT(9)
#define PWMSS_ECAPCLK_EN_ACK BIT(0)
#define PWMSS_EPWMCLK_EN_ACK BIT(8)
#ifdef CONFIG_PWM_TIPWMSS
extern u16 pwmss_submodule_state_change(struct device *dev, int set);
#else
static inline u16 pwmss_submodule_state_change(struct device *dev, int set)
{
/* return success status value */
return 0xFFFF;
}
#endif
#endif /* __TIPWMSS_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment