forked from Minki/linux
Merge branch 'for-4.8/drivers' into for-next
This commit is contained in:
commit
070d9a9306
21
Documentation/devicetree/bindings/pwm/brcm,iproc-pwm.txt
Normal file
21
Documentation/devicetree/bindings/pwm/brcm,iproc-pwm.txt
Normal file
@ -0,0 +1,21 @@
|
||||
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>;
|
||||
};
|
23
Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt
Normal file
23
Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt
Normal file
@ -0,0 +1,23 @@
|
||||
* 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>;
|
||||
};
|
||||
};
|
@ -1,10 +1,14 @@
|
||||
Tegra SoC PWFM controller
|
||||
|
||||
Required properties:
|
||||
- compatible: For Tegra20, must contain "nvidia,tegra20-pwm". For Tegra30,
|
||||
must contain "nvidia,tegra30-pwm". Otherwise, must contain
|
||||
"nvidia,<chip>-pwm", plus one of the above, where <chip> is tegra114,
|
||||
tegra124, tegra132, or tegra210.
|
||||
- compatible: Must be:
|
||||
- "nvidia,tegra20-pwm": for Tegra20
|
||||
- "nvidia,tegra30-pwm", "nvidia,tegra20-pwm": for Tegra30
|
||||
- "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
|
||||
- #pwm-cells: should be 2. See pwm.txt in this directory for a description of
|
||||
the cells format.
|
||||
|
@ -15,14 +15,14 @@ Optional properties:
|
||||
|
||||
Example:
|
||||
|
||||
ehrpwm0: ehrpwm@0 { /* EHRPWM on am33xx */
|
||||
ehrpwm0: pwm@48300200 { /* EHRPWM on am33xx */
|
||||
compatible = "ti,am33xx-ehrpwm";
|
||||
#pwm-cells = <3>;
|
||||
reg = <0x48300200 0x100>;
|
||||
ti,hwmods = "ehrpwm0";
|
||||
};
|
||||
|
||||
ehrpwm0: ehrpwm@0 { /* EHRPWM on da850 */
|
||||
ehrpwm0: pwm@300000 { /* EHRPWM on da850 */
|
||||
compatible = "ti,da850-ehrpwm", "ti,am33xx-ehrpwm";
|
||||
#pwm-cells = <3>;
|
||||
reg = <0x300000 0x2000>;
|
||||
|
@ -7,6 +7,7 @@ Required Properties:
|
||||
- "renesas,pwm-r8a7790": for R-Car H2
|
||||
- "renesas,pwm-r8a7791": for R-Car M2-W
|
||||
- "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.
|
||||
- #pwm-cells: should be 2. See pwm.txt in this directory for a description of
|
||||
the cells format.
|
||||
|
18
Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt
Normal file
18
Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt
Normal file
@ -0,0 +1,18 @@
|
||||
== 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
|
||||
To compile this driver as a module, choose M here: the module
|
||||
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
|
||||
tristate "Kona PWM support"
|
||||
depends on ARCH_BCM_MOBILE
|
||||
@ -137,6 +147,13 @@ config PWM_CRC
|
||||
Generic PWM framework driver for Crystalcove (CRC) PMIC based PWM
|
||||
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
|
||||
tristate "Cirrus Logic EP93xx PWM support"
|
||||
depends on ARCH_EP93XX
|
||||
@ -305,7 +322,7 @@ config PWM_PXA
|
||||
|
||||
config PWM_RCAR
|
||||
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
|
||||
help
|
||||
This driver exposes the PWM Timer controller found in Renesas
|
||||
@ -362,6 +379,13 @@ config PWM_STI
|
||||
To compile this driver as a module, choose M here: the module
|
||||
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
|
||||
tristate "Allwinner PWM support"
|
||||
depends on ARCH_SUNXI || COMPILE_TEST
|
||||
|
@ -4,6 +4,7 @@ obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
|
||||
obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o
|
||||
obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.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_BCM2835) += pwm-bcm2835.o
|
||||
obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o
|
||||
@ -11,6 +12,7 @@ obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o
|
||||
obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o
|
||||
obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.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_FSL_FTM) += pwm-fsl-ftm.o
|
||||
obj-$(CONFIG_PWM_IMG) += pwm-img.o
|
||||
@ -34,6 +36,7 @@ obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
|
||||
obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
|
||||
obj-$(CONFIG_PWM_SPEAR) += pwm-spear.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_TEGRA) += pwm-tegra.o
|
||||
obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o
|
||||
|
@ -64,7 +64,8 @@ struct atmel_pwm_chip {
|
||||
void __iomem *base;
|
||||
|
||||
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,
|
||||
unsigned long dty, unsigned long prd);
|
||||
@ -271,6 +272,16 @@ static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
mutex_unlock(&atmel_pwm->isr_lock);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -324,21 +335,14 @@ MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids);
|
||||
static inline const struct atmel_pwm_data *
|
||||
atmel_pwm_get_driver_data(struct platform_device *pdev)
|
||||
{
|
||||
if (pdev->dev.of_node) {
|
||||
const struct of_device_id *match;
|
||||
const struct platform_device_id *id;
|
||||
|
||||
match = of_match_device(atmel_pwm_dt_ids, &pdev->dev);
|
||||
if (!match)
|
||||
return NULL;
|
||||
if (pdev->dev.of_node)
|
||||
return of_device_get_match_data(&pdev->dev);
|
||||
|
||||
return match->data;
|
||||
} else {
|
||||
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)
|
||||
|
277
drivers/pwm/pwm-bcm-iproc.c
Normal file
277
drivers/pwm/pwm-bcm-iproc.c
Normal file
@ -0,0 +1,277 @@
|
||||
/*
|
||||
* 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");
|
260
drivers/pwm/pwm-cros-ec.c
Normal file
260
drivers/pwm/pwm-cros-ec.c
Normal file
@ -0,0 +1,260 @@
|
||||
/*
|
||||
* 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 {
|
||||
};
|
||||
|
||||
#define PWM_ENABLE BIT(31)
|
||||
#define PWM_PIN_LEVEL BIT(30)
|
||||
|
||||
#define to_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)
|
||||
struct lpc32xx_pwm_chip *lpc32xx;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
lpc32xx = devm_kzalloc(&pdev->dev, sizeof(*lpc32xx), GFP_KERNEL);
|
||||
if (!lpc32xx)
|
||||
@ -128,6 +130,11 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev)
|
||||
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);
|
||||
|
||||
return 0;
|
||||
|
@ -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, 0x0f08), (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, 0x2288), (unsigned long)&pwm_lpss_bsw_info},
|
||||
{ PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info},
|
||||
|
@ -27,7 +27,6 @@
|
||||
#define PWM_SW_UPDATE BIT(30)
|
||||
#define PWM_BASE_UNIT_SHIFT 8
|
||||
#define PWM_ON_TIME_DIV_MASK 0x000000ff
|
||||
#define PWM_DIVISION_CORRECTION 0x2
|
||||
|
||||
/* Size of each PWM register space if multiple */
|
||||
#define PWM_SIZE 0x400
|
||||
@ -92,8 +91,8 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
int duty_ns, int period_ns)
|
||||
{
|
||||
struct pwm_lpss_chip *lpwm = to_lpwm(chip);
|
||||
u8 on_time_div;
|
||||
unsigned long c, base_unit_range;
|
||||
unsigned long long on_time_div;
|
||||
unsigned long c = lpwm->info->clk_rate, base_unit_range;
|
||||
unsigned long long base_unit, freq = NSEC_PER_SEC;
|
||||
u32 ctrl;
|
||||
|
||||
@ -101,21 +100,18 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
|
||||
/*
|
||||
* 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 = freq * base_unit_range;
|
||||
freq *= base_unit_range;
|
||||
|
||||
c = lpwm->info->clk_rate;
|
||||
if (!c)
|
||||
return -EINVAL;
|
||||
|
||||
do_div(base_unit, c);
|
||||
base_unit += PWM_DIVISION_CORRECTION;
|
||||
base_unit = DIV_ROUND_CLOSEST_ULL(freq, c);
|
||||
|
||||
if (duty_ns <= 0)
|
||||
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);
|
||||
|
||||
@ -169,6 +165,7 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
|
||||
const struct pwm_lpss_boardinfo *info)
|
||||
{
|
||||
struct pwm_lpss_chip *lpwm;
|
||||
unsigned long c;
|
||||
int ret;
|
||||
|
||||
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,
|
||||
return ERR_CAST(lpwm->regs);
|
||||
|
||||
lpwm->info = info;
|
||||
|
||||
c = lpwm->info->clk_rate;
|
||||
if (!c)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
lpwm->chip.dev = dev;
|
||||
lpwm->chip.ops = &pwm_lpss_ops;
|
||||
lpwm->chip.base = -1;
|
||||
|
@ -47,10 +47,14 @@ struct rockchip_pwm_regs {
|
||||
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);
|
||||
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)
|
||||
@ -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,
|
||||
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);
|
||||
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,
|
||||
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)
|
||||
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 (pwm_get_polarity(pwm) == PWM_POLARITY_INVERSED)
|
||||
if (polarity == PWM_POLARITY_INVERSED)
|
||||
enable_conf |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE;
|
||||
else
|
||||
enable_conf |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE;
|
||||
@ -98,13 +117,59 @@ static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip,
|
||||
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,
|
||||
int duty_ns, int period_ns)
|
||||
{
|
||||
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
|
||||
unsigned long period, duty;
|
||||
u64 clk_rate, div;
|
||||
int ret;
|
||||
|
||||
clk_rate = clk_get_rate(pc->clk);
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
div = clk_rate * period_ns;
|
||||
do_div(div, pc->data->prescaler * NSEC_PER_SEC);
|
||||
period = div;
|
||||
period = DIV_ROUND_CLOSEST_ULL(div,
|
||||
pc->data->prescaler * NSEC_PER_SEC);
|
||||
|
||||
div = clk_rate * duty_ns;
|
||||
do_div(div, pc->data->prescaler * NSEC_PER_SEC);
|
||||
duty = div;
|
||||
|
||||
ret = clk_enable(pc->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC);
|
||||
|
||||
writel(period, pc->base + pc->data->regs.period);
|
||||
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;
|
||||
}
|
||||
|
||||
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 pwm_state curstate;
|
||||
bool enabled;
|
||||
int ret;
|
||||
|
||||
pwm_get_state(pwm, &curstate);
|
||||
enabled = curstate.enabled;
|
||||
|
||||
ret = clk_enable(pc->clk);
|
||||
if (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)
|
||||
{
|
||||
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
|
||||
goto out;
|
||||
}
|
||||
|
||||
pc->data->set_enable(chip, pwm, false);
|
||||
if (state->enabled != enabled)
|
||||
pc->data->set_enable(chip, pwm, state->enabled,
|
||||
state->polarity);
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct pwm_ops rockchip_pwm_ops_v1 = {
|
||||
.config = rockchip_pwm_config,
|
||||
.enable = rockchip_pwm_enable,
|
||||
.disable = rockchip_pwm_disable,
|
||||
.get_state = rockchip_pwm_get_state,
|
||||
.apply = rockchip_pwm_apply,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct pwm_ops rockchip_pwm_ops_v2 = {
|
||||
.config = rockchip_pwm_config,
|
||||
.set_polarity = rockchip_pwm_set_polarity,
|
||||
.enable = rockchip_pwm_enable,
|
||||
.disable = rockchip_pwm_disable,
|
||||
.get_state = rockchip_pwm_get_state,
|
||||
.apply = rockchip_pwm_apply,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
@ -195,6 +258,7 @@ static const struct rockchip_pwm_data pwm_data_v1 = {
|
||||
.prescaler = 2,
|
||||
.ops = &rockchip_pwm_ops_v1,
|
||||
.set_enable = rockchip_pwm_set_enable_v1,
|
||||
.get_state = rockchip_pwm_get_state_v1,
|
||||
};
|
||||
|
||||
static const struct rockchip_pwm_data pwm_data_v2 = {
|
||||
@ -205,8 +269,10 @@ static const struct rockchip_pwm_data pwm_data_v2 = {
|
||||
.ctrl = 0x0c,
|
||||
},
|
||||
.prescaler = 1,
|
||||
.supports_polarity = true,
|
||||
.ops = &rockchip_pwm_ops_v2,
|
||||
.set_enable = rockchip_pwm_set_enable_v2,
|
||||
.get_state = rockchip_pwm_get_state_v2,
|
||||
};
|
||||
|
||||
static const struct rockchip_pwm_data pwm_data_vop = {
|
||||
@ -217,8 +283,10 @@ static const struct rockchip_pwm_data pwm_data_vop = {
|
||||
.ctrl = 0x00,
|
||||
},
|
||||
.prescaler = 1,
|
||||
.supports_polarity = true,
|
||||
.ops = &rockchip_pwm_ops_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[] = {
|
||||
@ -253,7 +321,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
|
||||
if (IS_ERR(pc->clk))
|
||||
return PTR_ERR(pc->clk);
|
||||
|
||||
ret = clk_prepare(pc->clk);
|
||||
ret = clk_prepare_enable(pc->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -265,7 +333,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
|
||||
pc->chip.base = -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_pwm_n_cells = 3;
|
||||
}
|
||||
@ -276,6 +344,10 @@ static int rockchip_pwm_probe(struct platform_device *pdev)
|
||||
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;
|
||||
}
|
||||
|
||||
@ -283,6 +355,20 @@ static int rockchip_pwm_remove(struct platform_device *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);
|
||||
|
||||
return pwmchip_remove(&pc->chip);
|
||||
|
319
drivers/pwm/pwm-stmpe.c
Normal file
319
drivers/pwm/pwm-stmpe.c
Normal file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* 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 @@
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#define PWM_ENABLE (1 << 31)
|
||||
#define PWM_DUTY_WIDTH 8
|
||||
@ -36,15 +38,20 @@
|
||||
#define PWM_SCALE_WIDTH 13
|
||||
#define PWM_SCALE_SHIFT 0
|
||||
|
||||
#define NUM_PWM 4
|
||||
struct tegra_pwm_soc {
|
||||
unsigned int num_channels;
|
||||
};
|
||||
|
||||
struct tegra_pwm_chip {
|
||||
struct pwm_chip chip;
|
||||
struct device *dev;
|
||||
struct pwm_chip chip;
|
||||
struct device *dev;
|
||||
|
||||
struct clk *clk;
|
||||
struct clk *clk;
|
||||
struct reset_control*rst;
|
||||
|
||||
void __iomem *mmio_base;
|
||||
void __iomem *regs;
|
||||
|
||||
const struct tegra_pwm_soc *soc;
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
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,
|
||||
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,
|
||||
int duty_ns, int period_ns)
|
||||
{
|
||||
struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
|
||||
unsigned long long c;
|
||||
unsigned long long c = duty_ns;
|
||||
unsigned long rate, hz;
|
||||
u32 val = 0;
|
||||
int err;
|
||||
@ -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
|
||||
* 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);
|
||||
|
||||
val = (u32)c << PWM_DUTY_SHIFT;
|
||||
@ -176,12 +184,13 @@ static int tegra_pwm_probe(struct platform_device *pdev)
|
||||
if (!pwm)
|
||||
return -ENOMEM;
|
||||
|
||||
pwm->soc = of_device_get_match_data(&pdev->dev);
|
||||
pwm->dev = &pdev->dev;
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r);
|
||||
if (IS_ERR(pwm->mmio_base))
|
||||
return PTR_ERR(pwm->mmio_base);
|
||||
pwm->regs = devm_ioremap_resource(&pdev->dev, r);
|
||||
if (IS_ERR(pwm->regs))
|
||||
return PTR_ERR(pwm->regs);
|
||||
|
||||
platform_set_drvdata(pdev, pwm);
|
||||
|
||||
@ -189,14 +198,24 @@ static int tegra_pwm_probe(struct platform_device *pdev)
|
||||
if (IS_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.ops = &tegra_pwm_ops;
|
||||
pwm->chip.base = -1;
|
||||
pwm->chip.npwm = NUM_PWM;
|
||||
pwm->chip.npwm = pwm->soc->num_channels;
|
||||
|
||||
ret = pwmchip_add(&pwm->chip);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
|
||||
reset_control_assert(pwm->rst);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -206,12 +225,17 @@ static int tegra_pwm_probe(struct platform_device *pdev)
|
||||
static int tegra_pwm_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra_pwm_chip *pc = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
if (WARN_ON(!pc))
|
||||
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];
|
||||
|
||||
if (!pwm_is_enabled(pwm))
|
||||
@ -223,12 +247,23 @@ static int tegra_pwm_remove(struct platform_device *pdev)
|
||||
clk_disable_unprepare(pc->clk);
|
||||
}
|
||||
|
||||
reset_control_assert(pc->rst);
|
||||
clk_disable_unprepare(pc->clk);
|
||||
|
||||
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[] = {
|
||||
{ .compatible = "nvidia,tegra20-pwm" },
|
||||
{ .compatible = "nvidia,tegra30-pwm" },
|
||||
{ .compatible = "nvidia,tegra20-pwm", .data = &tegra20_pwm_soc },
|
||||
{ .compatible = "nvidia,tegra186-pwm", .data = &tegra186_pwm_soc },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
@ -27,8 +27,6 @@
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
#include "pwm-tipwmss.h"
|
||||
|
||||
/* ECAP registers and bits definitions */
|
||||
#define CAP1 0x08
|
||||
#define CAP2 0x0C
|
||||
@ -195,6 +193,7 @@ static const struct pwm_ops ecap_pwm_ops = {
|
||||
};
|
||||
|
||||
static const struct of_device_id ecap_of_match[] = {
|
||||
{ .compatible = "ti,am3352-ecap" },
|
||||
{ .compatible = "ti,am33xx-ecap" },
|
||||
{},
|
||||
};
|
||||
@ -202,17 +201,24 @@ 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 resource *r;
|
||||
struct clk *clk;
|
||||
struct ecap_pwm_chip *pc;
|
||||
u16 status;
|
||||
|
||||
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
|
||||
if (!pc)
|
||||
return -ENOMEM;
|
||||
|
||||
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)) {
|
||||
dev_err(&pdev->dev, "failed to get clock\n");
|
||||
return PTR_ERR(clk);
|
||||
@ -243,40 +249,15 @@ static int ecap_pwm_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
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);
|
||||
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)
|
||||
{
|
||||
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);
|
||||
return pwmchip_remove(&pc->chip);
|
||||
}
|
||||
|
@ -27,8 +27,6 @@
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
#include "pwm-tipwmss.h"
|
||||
|
||||
/* EHRPWM registers and bits definitions */
|
||||
|
||||
/* Time base module registers */
|
||||
@ -426,6 +424,7 @@ static const struct pwm_ops ehrpwm_pwm_ops = {
|
||||
};
|
||||
|
||||
static const struct of_device_id ehrpwm_of_match[] = {
|
||||
{ .compatible = "ti,am3352-ehrpwm" },
|
||||
{ .compatible = "ti,am33xx-ehrpwm" },
|
||||
{},
|
||||
};
|
||||
@ -433,17 +432,24 @@ 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 resource *r;
|
||||
struct clk *clk;
|
||||
struct ehrpwm_pwm_chip *pc;
|
||||
u16 status;
|
||||
|
||||
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
|
||||
if (!pc)
|
||||
return -ENOMEM;
|
||||
|
||||
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)) {
|
||||
dev_err(&pdev->dev, "failed to get clock\n");
|
||||
return PTR_ERR(clk);
|
||||
@ -487,27 +493,9 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
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);
|
||||
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)
|
||||
@ -516,14 +504,6 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev)
|
||||
|
||||
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_disable(&pdev->dev);
|
||||
return pwmchip_remove(&pc->chip);
|
||||
|
@ -22,32 +22,6 @@
|
||||
#include <linux/pm_runtime.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[] = {
|
||||
{ .compatible = "ti,am33xx-pwmss" },
|
||||
{},
|
||||
@ -57,24 +31,10 @@ MODULE_DEVICE_TABLE(of, pwmss_of_match);
|
||||
static int pwmss_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct resource *r;
|
||||
struct pwmss_info *info;
|
||||
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_get_sync(&pdev->dev);
|
||||
platform_set_drvdata(pdev, info);
|
||||
|
||||
/* Populate all the child nodes here... */
|
||||
ret = of_platform_populate(node, NULL, NULL, &pdev->dev);
|
||||
@ -86,30 +46,21 @@ static int pwmss_probe(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_disable(&pdev->dev);
|
||||
mutex_destroy(&info->pwmss_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwmss_resume(struct device *dev)
|
||||
{
|
||||
struct pwmss_info *info = dev_get_drvdata(dev);
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
writew(info->pwmss_clkconfig, info->mmio_base + PWMSS_CLKCONFIG);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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 */
|
Loading…
Reference in New Issue
Block a user