pwm: Changes for v3.9-rc1
A new driver has been added to support the PWM mode of the timer counter blocks found on Atmel AT91 SoCs. The VT8500 driver now supports changing the PWM signal polarity and the TI drivers (EHRPWM and ECAP) gained suspend and resume functionality. User drivers can now query the core for whether access to a PWM device will sleep (if the PWM chip is on a slow bus such as I2C or SPI). The pwm-backlight driver now handles the backlight BL_CORE_FBBLANK state in addition to the FB layer's blanking states. To round things off, a few fixes and cleanups are also included. -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.19 (GNU/Linux) iQIcBAABAgAGBQJRKxfbAAoJEN0jrNd/PrOhcwEP/0plVf30QDMhcan1GR3l+jmK 3Q/cCNIWEZ75mRu+FtVYKUOduxkCOHR6LM4C2ScGnnpsS/X5oitc45FrblIwQaEI UeWVxDqI+pEp+rCdYVCND4lih60FJU0Cct0zZL2db4D4idF+8FRChhzwzSXhajnj ZZqz782Y2ig3YTLHtVGT1pp61DKxdu1Gz4U+fxo4G1/fnkquCYDzbCQovcmPSnnF gYB8hfxgT9JIt2cHrK6eKCHBF/4GToU+x5zDV2Ub15C2K/UQ+qgw82yl8MNd/ZS5 rFA06iW3icnJRRc8u6hu9WVjNVNTBrUqUlFhctNB01d5jF4R6uR6aEvovT9xe8NW Wa5v5WrjnL6SWpZVwb/3kwwJmkeU9zKESQt5KUw3VKnT8yr2BfxcH1gJIiuW6wK7 uYqknAvt2rwMEGzaKZJmQH682wNMxpJirs8hG9VjJc/v7AOnk6586HbRupAq8wWZ qK4n1Fto9RCg+b6e/hDId7+mN1GEcA5B8BOotPQ6ud+cinDZBKSbCSHhQG2LQ++F m81BbL82H2R2ICRoYNA74/bKq5OVOFFqHLHg5bUrG7ikdNbYOTK/4dF9qSS7DnT3 Bi6WnKpOtfpWbHBnDunP2vBFo3f+gXmbLJdgarxk7mneV8wp0GY6TYI1awWn0hI9 82JP3yiK5kW2JZv92o0H =Fcxi -----END PGP SIGNATURE----- Merge tag 'for-3.9-rc1' of git://gitorious.org/linux-pwm/linux-pwm Pull PWM changes from Thierry Reding: "A new driver has been added to support the PWM mode of the timer counter blocks found on Atmel AT91 SoCs. The VT8500 driver now supports changing the PWM signal polarity and the TI drivers (EHRPWM and ECAP) gained suspend and resume functionality. User drivers can now query the core for whether access to a PWM device will sleep (if the PWM chip is on a slow bus such as I2C or SPI). The pwm-backlight driver now handles the backlight BL_CORE_FBBLANK state in addition to the FB layer's blanking states. To round things off, a few fixes and cleanups are also included" * tag 'for-3.9-rc1' of git://gitorious.org/linux-pwm/linux-pwm: pwm: twl: Use to_twl() instead of container_of() pwm: tegra: assume CONFIG_OF pwm_backlight: Validate dft_brightness in main probe function pwm: Export pwm_{set,get}_chip_data() pwm: Make Kconfig entries more consistent pwm: Add can_sleep property to drivers pwm: Add pwm_can_sleep() as exported API to users pwm-backlight: handle BL_CORE_FBBLANK state pwm: pwm-tiecap: Low power sleep support pwm: pwm-tiehrpwm: Low power sleep support pwm: pwm-tiehrpwm: Update the clock handling of pwm-tiehrpwm driver pwm: vt8500: Add polarity support pwm: vt8500: Register write busy test performed incorrectly pwm: atmel: add Timer Counter Block PWM driver
This commit is contained in:
commit
3eb05225ee
|
@ -0,0 +1,18 @@
|
||||||
|
Atmel TCB PWM controller
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: should be "atmel,tcb-pwm"
|
||||||
|
- #pwm-cells: Should be 3. The first cell specifies the per-chip index
|
||||||
|
of the PWM to use, the second cell is the period in nanoseconds and
|
||||||
|
bit 0 in the third cell is used to encode the polarity of PWM output.
|
||||||
|
Set bit 0 of the third cell in PWM specifier to 1 for inverse polarity &
|
||||||
|
set to 0 for normal polarity.
|
||||||
|
- tc-block: The Timer Counter block to use as a PWM chip.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
pwm {
|
||||||
|
compatible = "atmel,tcb-pwm";
|
||||||
|
#pwm-cells = <3>;
|
||||||
|
tc-block = <1>;
|
||||||
|
};
|
|
@ -3,14 +3,17 @@ VIA/Wondermedia VT8500/WM8xxx series SoC PWM controller
|
||||||
Required properties:
|
Required properties:
|
||||||
- compatible: should be "via,vt8500-pwm"
|
- compatible: should be "via,vt8500-pwm"
|
||||||
- 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. The first cell specifies the per-chip index
|
- #pwm-cells: Should be 3. Number of cells being used to specify PWM property.
|
||||||
of the PWM to use and the second cell is the period in nanoseconds.
|
First cell specifies the per-chip index of the PWM to use, the second
|
||||||
|
cell is the period in nanoseconds and bit 0 in the third cell is used to
|
||||||
|
encode the polarity of PWM output. Set bit 0 of the third in PWM specifier
|
||||||
|
to 1 for inverse polarity & set to 0 for normal polarity.
|
||||||
- clocks: phandle to the PWM source clock
|
- clocks: phandle to the PWM source clock
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
pwm1: pwm@d8220000 {
|
pwm1: pwm@d8220000 {
|
||||||
#pwm-cells = <2>;
|
#pwm-cells = <3>;
|
||||||
compatible = "via,vt8500-pwm";
|
compatible = "via,vt8500-pwm";
|
||||||
reg = <0xd8220000 0x1000>;
|
reg = <0xd8220000 0x1000>;
|
||||||
clocks = <&clkpwm>;
|
clocks = <&clkpwm>;
|
||||||
|
|
|
@ -37,6 +37,18 @@ config PWM_AB8500
|
||||||
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-ab8500.
|
will be called pwm-ab8500.
|
||||||
|
|
||||||
|
config PWM_ATMEL_TCB
|
||||||
|
tristate "Atmel TC Block PWM support"
|
||||||
|
depends on ATMEL_TCLIB && OF
|
||||||
|
help
|
||||||
|
Generic PWM framework driver for Atmel Timer Counter Block.
|
||||||
|
|
||||||
|
A Timer Counter Block provides 6 PWM devices grouped by 2.
|
||||||
|
Devices in a given group must have the same period.
|
||||||
|
|
||||||
|
To compile this driver as a module, choose M here: the module
|
||||||
|
will be called pwm-atmel-tcb.
|
||||||
|
|
||||||
config PWM_BFIN
|
config PWM_BFIN
|
||||||
tristate "Blackfin PWM support"
|
tristate "Blackfin PWM support"
|
||||||
depends on BFIN_GPTIMERS
|
depends on BFIN_GPTIMERS
|
||||||
|
@ -47,7 +59,7 @@ config PWM_BFIN
|
||||||
will be called pwm-bfin.
|
will be called pwm-bfin.
|
||||||
|
|
||||||
config PWM_IMX
|
config PWM_IMX
|
||||||
tristate "i.MX pwm support"
|
tristate "i.MX PWM support"
|
||||||
depends on ARCH_MXC
|
depends on ARCH_MXC
|
||||||
help
|
help
|
||||||
Generic PWM framework driver for i.MX.
|
Generic PWM framework driver for i.MX.
|
||||||
|
@ -104,7 +116,7 @@ config PWM_PXA
|
||||||
will be called pwm-pxa.
|
will be called pwm-pxa.
|
||||||
|
|
||||||
config PWM_SAMSUNG
|
config PWM_SAMSUNG
|
||||||
tristate "Samsung pwm support"
|
tristate "Samsung PWM support"
|
||||||
depends on PLAT_SAMSUNG
|
depends on PLAT_SAMSUNG
|
||||||
help
|
help
|
||||||
Generic PWM framework driver for Samsung.
|
Generic PWM framework driver for Samsung.
|
||||||
|
@ -183,7 +195,7 @@ config PWM_TWL_LED
|
||||||
will be called pwm-twl-led.
|
will be called pwm-twl-led.
|
||||||
|
|
||||||
config PWM_VT8500
|
config PWM_VT8500
|
||||||
tristate "vt8500 pwm support"
|
tristate "vt8500 PWM support"
|
||||||
depends on ARCH_VT8500
|
depends on ARCH_VT8500
|
||||||
help
|
help
|
||||||
Generic PWM framework driver for vt8500.
|
Generic PWM framework driver for vt8500.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
obj-$(CONFIG_PWM) += core.o
|
obj-$(CONFIG_PWM) += core.o
|
||||||
obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
|
obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
|
||||||
|
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
|
||||||
obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o
|
obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o
|
||||||
obj-$(CONFIG_PWM_IMX) += pwm-imx.o
|
obj-$(CONFIG_PWM_IMX) += pwm-imx.o
|
||||||
obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o
|
obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o
|
||||||
|
|
|
@ -211,6 +211,7 @@ int pwm_set_chip_data(struct pwm_device *pwm, void *data)
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(pwm_set_chip_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pwm_get_chip_data() - get private chip data for a PWM
|
* pwm_get_chip_data() - get private chip data for a PWM
|
||||||
|
@ -220,6 +221,7 @@ void *pwm_get_chip_data(struct pwm_device *pwm)
|
||||||
{
|
{
|
||||||
return pwm ? pwm->chip_data : NULL;
|
return pwm ? pwm->chip_data : NULL;
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(pwm_get_chip_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pwmchip_add() - register a new PWM chip
|
* pwmchip_add() - register a new PWM chip
|
||||||
|
@ -763,6 +765,18 @@ void devm_pwm_put(struct device *dev, struct pwm_device *pwm)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(devm_pwm_put);
|
EXPORT_SYMBOL_GPL(devm_pwm_put);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pwm_can_sleep() - report whether PWM access will sleep
|
||||||
|
* @pwm: PWM device
|
||||||
|
*
|
||||||
|
* It returns true if accessing the PWM can sleep, false otherwise.
|
||||||
|
*/
|
||||||
|
bool pwm_can_sleep(struct pwm_device *pwm)
|
||||||
|
{
|
||||||
|
return pwm->chip->can_sleep;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(pwm_can_sleep);
|
||||||
|
|
||||||
#ifdef CONFIG_DEBUG_FS
|
#ifdef CONFIG_DEBUG_FS
|
||||||
static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s)
|
static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,445 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) Overkiz SAS 2012
|
||||||
|
*
|
||||||
|
* Author: Boris BREZILLON <b.brezillon@overkiz.com>
|
||||||
|
* License terms: GNU General Public License (GPL) version 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/clocksource.h>
|
||||||
|
#include <linux/clockchips.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/irq.h>
|
||||||
|
|
||||||
|
#include <linux/clk.h>
|
||||||
|
#include <linux/err.h>
|
||||||
|
#include <linux/ioport.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/atmel_tc.h>
|
||||||
|
#include <linux/pwm.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
|
||||||
|
#define NPWM 6
|
||||||
|
|
||||||
|
#define ATMEL_TC_ACMR_MASK (ATMEL_TC_ACPA | ATMEL_TC_ACPC | \
|
||||||
|
ATMEL_TC_AEEVT | ATMEL_TC_ASWTRG)
|
||||||
|
|
||||||
|
#define ATMEL_TC_BCMR_MASK (ATMEL_TC_BCPB | ATMEL_TC_BCPC | \
|
||||||
|
ATMEL_TC_BEEVT | ATMEL_TC_BSWTRG)
|
||||||
|
|
||||||
|
struct atmel_tcb_pwm_device {
|
||||||
|
enum pwm_polarity polarity; /* PWM polarity */
|
||||||
|
unsigned div; /* PWM clock divider */
|
||||||
|
unsigned duty; /* PWM duty expressed in clk cycles */
|
||||||
|
unsigned period; /* PWM period expressed in clk cycles */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct atmel_tcb_pwm_chip {
|
||||||
|
struct pwm_chip chip;
|
||||||
|
spinlock_t lock;
|
||||||
|
struct atmel_tc *tc;
|
||||||
|
struct atmel_tcb_pwm_device *pwms[NPWM];
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline struct atmel_tcb_pwm_chip *to_tcb_chip(struct pwm_chip *chip)
|
||||||
|
{
|
||||||
|
return container_of(chip, struct atmel_tcb_pwm_chip, chip);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int atmel_tcb_pwm_set_polarity(struct pwm_chip *chip,
|
||||||
|
struct pwm_device *pwm,
|
||||||
|
enum pwm_polarity polarity)
|
||||||
|
{
|
||||||
|
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
|
||||||
|
|
||||||
|
tcbpwm->polarity = polarity;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int atmel_tcb_pwm_request(struct pwm_chip *chip,
|
||||||
|
struct pwm_device *pwm)
|
||||||
|
{
|
||||||
|
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
|
||||||
|
struct atmel_tcb_pwm_device *tcbpwm;
|
||||||
|
struct atmel_tc *tc = tcbpwmc->tc;
|
||||||
|
void __iomem *regs = tc->regs;
|
||||||
|
unsigned group = pwm->hwpwm / 2;
|
||||||
|
unsigned index = pwm->hwpwm % 2;
|
||||||
|
unsigned cmr;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
tcbpwm = devm_kzalloc(chip->dev, sizeof(*tcbpwm), GFP_KERNEL);
|
||||||
|
if (!tcbpwm)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = clk_enable(tc->clk[group]);
|
||||||
|
if (ret) {
|
||||||
|
devm_kfree(chip->dev, tcbpwm);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
pwm_set_chip_data(pwm, tcbpwm);
|
||||||
|
tcbpwm->polarity = PWM_POLARITY_NORMAL;
|
||||||
|
tcbpwm->duty = 0;
|
||||||
|
tcbpwm->period = 0;
|
||||||
|
tcbpwm->div = 0;
|
||||||
|
|
||||||
|
spin_lock(&tcbpwmc->lock);
|
||||||
|
cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR));
|
||||||
|
/*
|
||||||
|
* Get init config from Timer Counter registers if
|
||||||
|
* Timer Counter is already configured as a PWM generator.
|
||||||
|
*/
|
||||||
|
if (cmr & ATMEL_TC_WAVE) {
|
||||||
|
if (index == 0)
|
||||||
|
tcbpwm->duty =
|
||||||
|
__raw_readl(regs + ATMEL_TC_REG(group, RA));
|
||||||
|
else
|
||||||
|
tcbpwm->duty =
|
||||||
|
__raw_readl(regs + ATMEL_TC_REG(group, RB));
|
||||||
|
|
||||||
|
tcbpwm->div = cmr & ATMEL_TC_TCCLKS;
|
||||||
|
tcbpwm->period = __raw_readl(regs + ATMEL_TC_REG(group, RC));
|
||||||
|
cmr &= (ATMEL_TC_TCCLKS | ATMEL_TC_ACMR_MASK |
|
||||||
|
ATMEL_TC_BCMR_MASK);
|
||||||
|
} else
|
||||||
|
cmr = 0;
|
||||||
|
|
||||||
|
cmr |= ATMEL_TC_WAVE | ATMEL_TC_WAVESEL_UP_AUTO | ATMEL_TC_EEVT_XC0;
|
||||||
|
__raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR));
|
||||||
|
spin_unlock(&tcbpwmc->lock);
|
||||||
|
|
||||||
|
tcbpwmc->pwms[pwm->hwpwm] = tcbpwm;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void atmel_tcb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
|
{
|
||||||
|
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
|
||||||
|
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
|
||||||
|
struct atmel_tc *tc = tcbpwmc->tc;
|
||||||
|
|
||||||
|
clk_disable(tc->clk[pwm->hwpwm / 2]);
|
||||||
|
tcbpwmc->pwms[pwm->hwpwm] = NULL;
|
||||||
|
devm_kfree(chip->dev, tcbpwm);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
|
{
|
||||||
|
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
|
||||||
|
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
|
||||||
|
struct atmel_tc *tc = tcbpwmc->tc;
|
||||||
|
void __iomem *regs = tc->regs;
|
||||||
|
unsigned group = pwm->hwpwm / 2;
|
||||||
|
unsigned index = pwm->hwpwm % 2;
|
||||||
|
unsigned cmr;
|
||||||
|
enum pwm_polarity polarity = tcbpwm->polarity;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If duty is 0 the timer will be stopped and we have to
|
||||||
|
* configure the output correctly on software trigger:
|
||||||
|
* - set output to high if PWM_POLARITY_INVERSED
|
||||||
|
* - set output to low if PWM_POLARITY_NORMAL
|
||||||
|
*
|
||||||
|
* This is why we're reverting polarity in this case.
|
||||||
|
*/
|
||||||
|
if (tcbpwm->duty == 0)
|
||||||
|
polarity = !polarity;
|
||||||
|
|
||||||
|
spin_lock(&tcbpwmc->lock);
|
||||||
|
cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR));
|
||||||
|
|
||||||
|
/* flush old setting and set the new one */
|
||||||
|
if (index == 0) {
|
||||||
|
cmr &= ~ATMEL_TC_ACMR_MASK;
|
||||||
|
if (polarity == PWM_POLARITY_INVERSED)
|
||||||
|
cmr |= ATMEL_TC_ASWTRG_CLEAR;
|
||||||
|
else
|
||||||
|
cmr |= ATMEL_TC_ASWTRG_SET;
|
||||||
|
} else {
|
||||||
|
cmr &= ~ATMEL_TC_BCMR_MASK;
|
||||||
|
if (polarity == PWM_POLARITY_INVERSED)
|
||||||
|
cmr |= ATMEL_TC_BSWTRG_CLEAR;
|
||||||
|
else
|
||||||
|
cmr |= ATMEL_TC_BSWTRG_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
__raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use software trigger to apply the new setting.
|
||||||
|
* If both PWM devices in this group are disabled we stop the clock.
|
||||||
|
*/
|
||||||
|
if (!(cmr & (ATMEL_TC_ACPC | ATMEL_TC_BCPC)))
|
||||||
|
__raw_writel(ATMEL_TC_SWTRG | ATMEL_TC_CLKDIS,
|
||||||
|
regs + ATMEL_TC_REG(group, CCR));
|
||||||
|
else
|
||||||
|
__raw_writel(ATMEL_TC_SWTRG, regs +
|
||||||
|
ATMEL_TC_REG(group, CCR));
|
||||||
|
|
||||||
|
spin_unlock(&tcbpwmc->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
|
{
|
||||||
|
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
|
||||||
|
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
|
||||||
|
struct atmel_tc *tc = tcbpwmc->tc;
|
||||||
|
void __iomem *regs = tc->regs;
|
||||||
|
unsigned group = pwm->hwpwm / 2;
|
||||||
|
unsigned index = pwm->hwpwm % 2;
|
||||||
|
u32 cmr;
|
||||||
|
enum pwm_polarity polarity = tcbpwm->polarity;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If duty is 0 the timer will be stopped and we have to
|
||||||
|
* configure the output correctly on software trigger:
|
||||||
|
* - set output to high if PWM_POLARITY_INVERSED
|
||||||
|
* - set output to low if PWM_POLARITY_NORMAL
|
||||||
|
*
|
||||||
|
* This is why we're reverting polarity in this case.
|
||||||
|
*/
|
||||||
|
if (tcbpwm->duty == 0)
|
||||||
|
polarity = !polarity;
|
||||||
|
|
||||||
|
spin_lock(&tcbpwmc->lock);
|
||||||
|
cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR));
|
||||||
|
|
||||||
|
/* flush old setting and set the new one */
|
||||||
|
cmr &= ~ATMEL_TC_TCCLKS;
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
cmr &= ~ATMEL_TC_ACMR_MASK;
|
||||||
|
|
||||||
|
/* Set CMR flags according to given polarity */
|
||||||
|
if (polarity == PWM_POLARITY_INVERSED)
|
||||||
|
cmr |= ATMEL_TC_ASWTRG_CLEAR;
|
||||||
|
else
|
||||||
|
cmr |= ATMEL_TC_ASWTRG_SET;
|
||||||
|
} else {
|
||||||
|
cmr &= ~ATMEL_TC_BCMR_MASK;
|
||||||
|
if (polarity == PWM_POLARITY_INVERSED)
|
||||||
|
cmr |= ATMEL_TC_BSWTRG_CLEAR;
|
||||||
|
else
|
||||||
|
cmr |= ATMEL_TC_BSWTRG_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If duty is 0 or equal to period there's no need to register
|
||||||
|
* a specific action on RA/RB and RC compare.
|
||||||
|
* The output will be configured on software trigger and keep
|
||||||
|
* this config till next config call.
|
||||||
|
*/
|
||||||
|
if (tcbpwm->duty != tcbpwm->period && tcbpwm->duty > 0) {
|
||||||
|
if (index == 0) {
|
||||||
|
if (polarity == PWM_POLARITY_INVERSED)
|
||||||
|
cmr |= ATMEL_TC_ACPA_SET | ATMEL_TC_ACPC_CLEAR;
|
||||||
|
else
|
||||||
|
cmr |= ATMEL_TC_ACPA_CLEAR | ATMEL_TC_ACPC_SET;
|
||||||
|
} else {
|
||||||
|
if (polarity == PWM_POLARITY_INVERSED)
|
||||||
|
cmr |= ATMEL_TC_BCPB_SET | ATMEL_TC_BCPC_CLEAR;
|
||||||
|
else
|
||||||
|
cmr |= ATMEL_TC_BCPB_CLEAR | ATMEL_TC_BCPC_SET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR));
|
||||||
|
|
||||||
|
if (index == 0)
|
||||||
|
__raw_writel(tcbpwm->duty, regs + ATMEL_TC_REG(group, RA));
|
||||||
|
else
|
||||||
|
__raw_writel(tcbpwm->duty, regs + ATMEL_TC_REG(group, RB));
|
||||||
|
|
||||||
|
__raw_writel(tcbpwm->period, regs + ATMEL_TC_REG(group, RC));
|
||||||
|
|
||||||
|
/* Use software trigger to apply the new setting */
|
||||||
|
__raw_writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG,
|
||||||
|
regs + ATMEL_TC_REG(group, CCR));
|
||||||
|
spin_unlock(&tcbpwmc->lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
|
int duty_ns, int period_ns)
|
||||||
|
{
|
||||||
|
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
|
||||||
|
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
|
||||||
|
unsigned group = pwm->hwpwm / 2;
|
||||||
|
unsigned index = pwm->hwpwm % 2;
|
||||||
|
struct atmel_tcb_pwm_device *atcbpwm = NULL;
|
||||||
|
struct atmel_tc *tc = tcbpwmc->tc;
|
||||||
|
int i;
|
||||||
|
int slowclk = 0;
|
||||||
|
unsigned period;
|
||||||
|
unsigned duty;
|
||||||
|
unsigned rate = clk_get_rate(tc->clk[group]);
|
||||||
|
unsigned long long min;
|
||||||
|
unsigned long long max;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find best clk divisor:
|
||||||
|
* the smallest divisor which can fulfill the period_ns requirements.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < 5; ++i) {
|
||||||
|
if (atmel_tc_divisors[i] == 0) {
|
||||||
|
slowclk = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
min = div_u64((u64)NSEC_PER_SEC * atmel_tc_divisors[i], rate);
|
||||||
|
max = min << tc->tcb_config->counter_width;
|
||||||
|
if (max >= period_ns)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If none of the divisor are small enough to represent period_ns
|
||||||
|
* take slow clock (32KHz).
|
||||||
|
*/
|
||||||
|
if (i == 5) {
|
||||||
|
i = slowclk;
|
||||||
|
rate = 32768;
|
||||||
|
min = div_u64(NSEC_PER_SEC, rate);
|
||||||
|
max = min << 16;
|
||||||
|
|
||||||
|
/* If period is too big return ERANGE error */
|
||||||
|
if (max < period_ns)
|
||||||
|
return -ERANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
duty = div_u64(duty_ns, min);
|
||||||
|
period = div_u64(period_ns, min);
|
||||||
|
|
||||||
|
if (index == 0)
|
||||||
|
atcbpwm = tcbpwmc->pwms[pwm->hwpwm + 1];
|
||||||
|
else
|
||||||
|
atcbpwm = tcbpwmc->pwms[pwm->hwpwm - 1];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PWM devices provided by TCB driver are grouped by 2:
|
||||||
|
* - group 0: PWM 0 & 1
|
||||||
|
* - group 1: PWM 2 & 3
|
||||||
|
* - group 2: PWM 4 & 5
|
||||||
|
*
|
||||||
|
* PWM devices in a given group must be configured with the
|
||||||
|
* same period_ns.
|
||||||
|
*
|
||||||
|
* We're checking the period value of the second PWM device
|
||||||
|
* in this group before applying the new config.
|
||||||
|
*/
|
||||||
|
if ((atcbpwm && atcbpwm->duty > 0 &&
|
||||||
|
atcbpwm->duty != atcbpwm->period) &&
|
||||||
|
(atcbpwm->div != i || atcbpwm->period != period)) {
|
||||||
|
dev_err(chip->dev,
|
||||||
|
"failed to configure period_ns: PWM group already configured with a different value\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcbpwm->period = period;
|
||||||
|
tcbpwm->div = i;
|
||||||
|
tcbpwm->duty = duty;
|
||||||
|
|
||||||
|
/* If the PWM is enabled, call enable to apply the new conf */
|
||||||
|
if (test_bit(PWMF_ENABLED, &pwm->flags))
|
||||||
|
atmel_tcb_pwm_enable(chip, pwm);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct pwm_ops atmel_tcb_pwm_ops = {
|
||||||
|
.request = atmel_tcb_pwm_request,
|
||||||
|
.free = atmel_tcb_pwm_free,
|
||||||
|
.config = atmel_tcb_pwm_config,
|
||||||
|
.set_polarity = atmel_tcb_pwm_set_polarity,
|
||||||
|
.enable = atmel_tcb_pwm_enable,
|
||||||
|
.disable = atmel_tcb_pwm_disable,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int atmel_tcb_pwm_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct atmel_tcb_pwm_chip *tcbpwm;
|
||||||
|
struct device_node *np = pdev->dev.of_node;
|
||||||
|
struct atmel_tc *tc;
|
||||||
|
int err;
|
||||||
|
int tcblock;
|
||||||
|
|
||||||
|
err = of_property_read_u32(np, "tc-block", &tcblock);
|
||||||
|
if (err < 0) {
|
||||||
|
dev_err(&pdev->dev,
|
||||||
|
"failed to get Timer Counter Block number from device tree (error: %d)\n",
|
||||||
|
err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
tc = atmel_tc_alloc(tcblock, "tcb-pwm");
|
||||||
|
if (tc == NULL) {
|
||||||
|
dev_err(&pdev->dev, "failed to allocate Timer Counter Block\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcbpwm = devm_kzalloc(&pdev->dev, sizeof(*tcbpwm), GFP_KERNEL);
|
||||||
|
if (tcbpwm == NULL) {
|
||||||
|
atmel_tc_free(tc);
|
||||||
|
dev_err(&pdev->dev, "failed to allocate memory\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcbpwm->chip.dev = &pdev->dev;
|
||||||
|
tcbpwm->chip.ops = &atmel_tcb_pwm_ops;
|
||||||
|
tcbpwm->chip.of_xlate = of_pwm_xlate_with_flags;
|
||||||
|
tcbpwm->chip.of_pwm_n_cells = 3;
|
||||||
|
tcbpwm->chip.base = -1;
|
||||||
|
tcbpwm->chip.npwm = NPWM;
|
||||||
|
tcbpwm->tc = tc;
|
||||||
|
|
||||||
|
spin_lock_init(&tcbpwm->lock);
|
||||||
|
|
||||||
|
err = pwmchip_add(&tcbpwm->chip);
|
||||||
|
if (err < 0) {
|
||||||
|
atmel_tc_free(tc);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, tcbpwm);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int atmel_tcb_pwm_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct atmel_tcb_pwm_chip *tcbpwm = platform_get_drvdata(pdev);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = pwmchip_remove(&tcbpwm->chip);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
atmel_tc_free(tcbpwm->tc);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id atmel_tcb_pwm_dt_ids[] = {
|
||||||
|
{ .compatible = "atmel,tcb-pwm", },
|
||||||
|
{ /* sentinel */ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids);
|
||||||
|
|
||||||
|
static struct platform_driver atmel_tcb_pwm_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "atmel-tcb-pwm",
|
||||||
|
.of_match_table = atmel_tcb_pwm_dt_ids,
|
||||||
|
},
|
||||||
|
.probe = atmel_tcb_pwm_probe,
|
||||||
|
.remove = atmel_tcb_pwm_remove,
|
||||||
|
};
|
||||||
|
module_platform_driver(atmel_tcb_pwm_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Boris BREZILLON <b.brezillon@overkiz.com>");
|
||||||
|
MODULE_DESCRIPTION("Atmel Timer Counter Pulse Width Modulation Driver");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
|
@ -233,7 +233,6 @@ static int tegra_pwm_remove(struct platform_device *pdev)
|
||||||
return pwmchip_remove(&pc->chip);
|
return pwmchip_remove(&pc->chip);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_OF
|
|
||||||
static struct of_device_id tegra_pwm_of_match[] = {
|
static struct of_device_id tegra_pwm_of_match[] = {
|
||||||
{ .compatible = "nvidia,tegra20-pwm" },
|
{ .compatible = "nvidia,tegra20-pwm" },
|
||||||
{ .compatible = "nvidia,tegra30-pwm" },
|
{ .compatible = "nvidia,tegra30-pwm" },
|
||||||
|
@ -241,12 +240,11 @@ static struct of_device_id tegra_pwm_of_match[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
MODULE_DEVICE_TABLE(of, tegra_pwm_of_match);
|
MODULE_DEVICE_TABLE(of, tegra_pwm_of_match);
|
||||||
#endif
|
|
||||||
|
|
||||||
static struct platform_driver tegra_pwm_driver = {
|
static struct platform_driver tegra_pwm_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "tegra-pwm",
|
.name = "tegra-pwm",
|
||||||
.of_match_table = of_match_ptr(tegra_pwm_of_match),
|
.of_match_table = tegra_pwm_of_match,
|
||||||
},
|
},
|
||||||
.probe = tegra_pwm_probe,
|
.probe = tegra_pwm_probe,
|
||||||
.remove = tegra_pwm_remove,
|
.remove = tegra_pwm_remove,
|
||||||
|
|
|
@ -41,10 +41,17 @@
|
||||||
#define ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6))
|
#define ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6))
|
||||||
#define ECCTL2_TSCTR_FREERUN BIT(4)
|
#define ECCTL2_TSCTR_FREERUN BIT(4)
|
||||||
|
|
||||||
|
struct ecap_context {
|
||||||
|
u32 cap3;
|
||||||
|
u32 cap4;
|
||||||
|
u16 ecctl2;
|
||||||
|
};
|
||||||
|
|
||||||
struct ecap_pwm_chip {
|
struct ecap_pwm_chip {
|
||||||
struct pwm_chip chip;
|
struct pwm_chip chip;
|
||||||
unsigned int clk_rate;
|
unsigned int clk_rate;
|
||||||
void __iomem *mmio_base;
|
void __iomem *mmio_base;
|
||||||
|
struct ecap_context ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline struct ecap_pwm_chip *to_ecap_pwm_chip(struct pwm_chip *chip)
|
static inline struct ecap_pwm_chip *to_ecap_pwm_chip(struct pwm_chip *chip)
|
||||||
|
@ -288,11 +295,57 @@ static int ecap_pwm_remove(struct platform_device *pdev)
|
||||||
return pwmchip_remove(&pc->chip);
|
return pwmchip_remove(&pc->chip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ecap_pwm_save_context(struct ecap_pwm_chip *pc)
|
||||||
|
{
|
||||||
|
pm_runtime_get_sync(pc->chip.dev);
|
||||||
|
pc->ctx.ecctl2 = readw(pc->mmio_base + ECCTL2);
|
||||||
|
pc->ctx.cap4 = readl(pc->mmio_base + CAP4);
|
||||||
|
pc->ctx.cap3 = readl(pc->mmio_base + CAP3);
|
||||||
|
pm_runtime_put_sync(pc->chip.dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ecap_pwm_restore_context(struct ecap_pwm_chip *pc)
|
||||||
|
{
|
||||||
|
writel(pc->ctx.cap3, pc->mmio_base + CAP3);
|
||||||
|
writel(pc->ctx.cap4, pc->mmio_base + CAP4);
|
||||||
|
writew(pc->ctx.ecctl2, pc->mmio_base + ECCTL2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ecap_pwm_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct ecap_pwm_chip *pc = dev_get_drvdata(dev);
|
||||||
|
struct pwm_device *pwm = pc->chip.pwms;
|
||||||
|
|
||||||
|
ecap_pwm_save_context(pc);
|
||||||
|
|
||||||
|
/* Disable explicitly if PWM is running */
|
||||||
|
if (test_bit(PWMF_ENABLED, &pwm->flags))
|
||||||
|
pm_runtime_put_sync(dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ecap_pwm_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct ecap_pwm_chip *pc = dev_get_drvdata(dev);
|
||||||
|
struct pwm_device *pwm = pc->chip.pwms;
|
||||||
|
|
||||||
|
/* Enable explicitly if PWM was running */
|
||||||
|
if (test_bit(PWMF_ENABLED, &pwm->flags))
|
||||||
|
pm_runtime_get_sync(dev);
|
||||||
|
|
||||||
|
ecap_pwm_restore_context(pc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SIMPLE_DEV_PM_OPS(ecap_pwm_pm_ops, ecap_pwm_suspend, ecap_pwm_resume);
|
||||||
|
|
||||||
static struct platform_driver ecap_pwm_driver = {
|
static struct platform_driver ecap_pwm_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "ecap",
|
.name = "ecap",
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.of_match_table = ecap_of_match,
|
.of_match_table = ecap_of_match,
|
||||||
|
.pm = &ecap_pwm_pm_ops,
|
||||||
},
|
},
|
||||||
.probe = ecap_pwm_probe,
|
.probe = ecap_pwm_probe,
|
||||||
.remove = ecap_pwm_remove,
|
.remove = ecap_pwm_remove,
|
||||||
|
|
|
@ -113,6 +113,17 @@
|
||||||
|
|
||||||
#define NUM_PWM_CHANNEL 2 /* EHRPWM channels */
|
#define NUM_PWM_CHANNEL 2 /* EHRPWM channels */
|
||||||
|
|
||||||
|
struct ehrpwm_context {
|
||||||
|
u16 tbctl;
|
||||||
|
u16 tbprd;
|
||||||
|
u16 cmpa;
|
||||||
|
u16 cmpb;
|
||||||
|
u16 aqctla;
|
||||||
|
u16 aqctlb;
|
||||||
|
u16 aqsfrc;
|
||||||
|
u16 aqcsfrc;
|
||||||
|
};
|
||||||
|
|
||||||
struct ehrpwm_pwm_chip {
|
struct ehrpwm_pwm_chip {
|
||||||
struct pwm_chip chip;
|
struct pwm_chip chip;
|
||||||
unsigned int clk_rate;
|
unsigned int clk_rate;
|
||||||
|
@ -120,6 +131,7 @@ struct ehrpwm_pwm_chip {
|
||||||
unsigned long period_cycles[NUM_PWM_CHANNEL];
|
unsigned long period_cycles[NUM_PWM_CHANNEL];
|
||||||
enum pwm_polarity polarity[NUM_PWM_CHANNEL];
|
enum pwm_polarity polarity[NUM_PWM_CHANNEL];
|
||||||
struct clk *tbclk;
|
struct clk *tbclk;
|
||||||
|
struct ehrpwm_context ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip)
|
static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip)
|
||||||
|
@ -127,6 +139,11 @@ static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip)
|
||||||
return container_of(chip, struct ehrpwm_pwm_chip, chip);
|
return container_of(chip, struct ehrpwm_pwm_chip, chip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static u16 ehrpwm_read(void *base, int offset)
|
||||||
|
{
|
||||||
|
return readw(base + offset);
|
||||||
|
}
|
||||||
|
|
||||||
static void ehrpwm_write(void *base, int offset, unsigned int val)
|
static void ehrpwm_write(void *base, int offset, unsigned int val)
|
||||||
{
|
{
|
||||||
writew(val & 0xFFFF, base + offset);
|
writew(val & 0xFFFF, base + offset);
|
||||||
|
@ -318,6 +335,7 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
{
|
{
|
||||||
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
|
struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip);
|
||||||
unsigned short aqcsfrc_val, aqcsfrc_mask;
|
unsigned short aqcsfrc_val, aqcsfrc_mask;
|
||||||
|
int ret;
|
||||||
|
|
||||||
/* Leave clock enabled on enabling PWM */
|
/* Leave clock enabled on enabling PWM */
|
||||||
pm_runtime_get_sync(chip->dev);
|
pm_runtime_get_sync(chip->dev);
|
||||||
|
@ -341,7 +359,12 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
configure_polarity(pc, pwm->hwpwm);
|
configure_polarity(pc, pwm->hwpwm);
|
||||||
|
|
||||||
/* Enable TBCLK before enabling PWM device */
|
/* Enable TBCLK before enabling PWM device */
|
||||||
clk_enable(pc->tbclk);
|
ret = clk_prepare_enable(pc->tbclk);
|
||||||
|
if (ret) {
|
||||||
|
pr_err("Failed to enable TBCLK for %s\n",
|
||||||
|
dev_name(pc->chip.dev));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/* Enable time counter for free_run */
|
/* Enable time counter for free_run */
|
||||||
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_FREE_RUN);
|
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_FREE_RUN);
|
||||||
|
@ -372,7 +395,7 @@ static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val);
|
ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val);
|
||||||
|
|
||||||
/* Disabling TBCLK on PWM disable */
|
/* Disabling TBCLK on PWM disable */
|
||||||
clk_disable(pc->tbclk);
|
clk_disable_unprepare(pc->tbclk);
|
||||||
|
|
||||||
/* Stop Time base counter */
|
/* Stop Time base counter */
|
||||||
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_STOP_NEXT);
|
ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_STOP_NEXT);
|
||||||
|
@ -510,11 +533,77 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev)
|
||||||
return pwmchip_remove(&pc->chip);
|
return pwmchip_remove(&pc->chip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ehrpwm_pwm_save_context(struct ehrpwm_pwm_chip *pc)
|
||||||
|
{
|
||||||
|
pm_runtime_get_sync(pc->chip.dev);
|
||||||
|
pc->ctx.tbctl = ehrpwm_read(pc->mmio_base, TBCTL);
|
||||||
|
pc->ctx.tbprd = ehrpwm_read(pc->mmio_base, TBPRD);
|
||||||
|
pc->ctx.cmpa = ehrpwm_read(pc->mmio_base, CMPA);
|
||||||
|
pc->ctx.cmpb = ehrpwm_read(pc->mmio_base, CMPB);
|
||||||
|
pc->ctx.aqctla = ehrpwm_read(pc->mmio_base, AQCTLA);
|
||||||
|
pc->ctx.aqctlb = ehrpwm_read(pc->mmio_base, AQCTLB);
|
||||||
|
pc->ctx.aqsfrc = ehrpwm_read(pc->mmio_base, AQSFRC);
|
||||||
|
pc->ctx.aqcsfrc = ehrpwm_read(pc->mmio_base, AQCSFRC);
|
||||||
|
pm_runtime_put_sync(pc->chip.dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ehrpwm_pwm_restore_context(struct ehrpwm_pwm_chip *pc)
|
||||||
|
{
|
||||||
|
ehrpwm_write(pc->mmio_base, TBPRD, pc->ctx.tbprd);
|
||||||
|
ehrpwm_write(pc->mmio_base, CMPA, pc->ctx.cmpa);
|
||||||
|
ehrpwm_write(pc->mmio_base, CMPB, pc->ctx.cmpb);
|
||||||
|
ehrpwm_write(pc->mmio_base, AQCTLA, pc->ctx.aqctla);
|
||||||
|
ehrpwm_write(pc->mmio_base, AQCTLB, pc->ctx.aqctlb);
|
||||||
|
ehrpwm_write(pc->mmio_base, AQSFRC, pc->ctx.aqsfrc);
|
||||||
|
ehrpwm_write(pc->mmio_base, AQCSFRC, pc->ctx.aqcsfrc);
|
||||||
|
ehrpwm_write(pc->mmio_base, TBCTL, pc->ctx.tbctl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ehrpwm_pwm_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
ehrpwm_pwm_save_context(pc);
|
||||||
|
for (i = 0; i < pc->chip.npwm; i++) {
|
||||||
|
struct pwm_device *pwm = &pc->chip.pwms[i];
|
||||||
|
|
||||||
|
if (!test_bit(PWMF_ENABLED, &pwm->flags))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Disable explicitly if PWM is running */
|
||||||
|
pm_runtime_put_sync(dev);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ehrpwm_pwm_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < pc->chip.npwm; i++) {
|
||||||
|
struct pwm_device *pwm = &pc->chip.pwms[i];
|
||||||
|
|
||||||
|
if (!test_bit(PWMF_ENABLED, &pwm->flags))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Enable explicitly if PWM was running */
|
||||||
|
pm_runtime_get_sync(dev);
|
||||||
|
}
|
||||||
|
ehrpwm_pwm_restore_context(pc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SIMPLE_DEV_PM_OPS(ehrpwm_pwm_pm_ops, ehrpwm_pwm_suspend,
|
||||||
|
ehrpwm_pwm_resume);
|
||||||
|
|
||||||
static struct platform_driver ehrpwm_pwm_driver = {
|
static struct platform_driver ehrpwm_pwm_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "ehrpwm",
|
.name = "ehrpwm",
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.of_match_table = ehrpwm_of_match,
|
.of_match_table = ehrpwm_of_match,
|
||||||
|
.pm = &ehrpwm_pwm_pm_ops,
|
||||||
},
|
},
|
||||||
.probe = ehrpwm_pwm_probe,
|
.probe = ehrpwm_pwm_probe,
|
||||||
.remove = ehrpwm_pwm_remove,
|
.remove = ehrpwm_pwm_remove,
|
||||||
|
|
|
@ -300,6 +300,7 @@ static int twl_pwmled_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
twl->chip.dev = &pdev->dev;
|
twl->chip.dev = &pdev->dev;
|
||||||
twl->chip.base = -1;
|
twl->chip.base = -1;
|
||||||
|
twl->chip.can_sleep = true;
|
||||||
|
|
||||||
mutex_init(&twl->mutex);
|
mutex_init(&twl->mutex);
|
||||||
|
|
||||||
|
|
|
@ -200,8 +200,7 @@ out:
|
||||||
|
|
||||||
static void twl4030_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
static void twl4030_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
{
|
{
|
||||||
struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
|
struct twl_pwm_chip *twl = to_twl(chip);
|
||||||
chip);
|
|
||||||
int ret;
|
int ret;
|
||||||
u8 val, mask;
|
u8 val, mask;
|
||||||
|
|
||||||
|
@ -231,8 +230,7 @@ out:
|
||||||
|
|
||||||
static int twl6030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
static int twl6030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
{
|
{
|
||||||
struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
|
struct twl_pwm_chip *twl = to_twl(chip);
|
||||||
chip);
|
|
||||||
int ret;
|
int ret;
|
||||||
u8 val;
|
u8 val;
|
||||||
|
|
||||||
|
@ -255,8 +253,7 @@ out:
|
||||||
|
|
||||||
static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
{
|
{
|
||||||
struct twl_pwm_chip *twl = container_of(chip, struct twl_pwm_chip,
|
struct twl_pwm_chip *twl = to_twl(chip);
|
||||||
chip);
|
|
||||||
int ret;
|
int ret;
|
||||||
u8 val;
|
u8 val;
|
||||||
|
|
||||||
|
@ -315,6 +312,7 @@ static int twl_pwm_probe(struct platform_device *pdev)
|
||||||
twl->chip.dev = &pdev->dev;
|
twl->chip.dev = &pdev->dev;
|
||||||
twl->chip.base = -1;
|
twl->chip.base = -1;
|
||||||
twl->chip.npwm = 2;
|
twl->chip.npwm = 2;
|
||||||
|
twl->chip.can_sleep = true;
|
||||||
|
|
||||||
mutex_init(&twl->mutex);
|
mutex_init(&twl->mutex);
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,25 @@
|
||||||
*/
|
*/
|
||||||
#define VT8500_NR_PWMS 2
|
#define VT8500_NR_PWMS 2
|
||||||
|
|
||||||
|
#define REG_CTRL(pwm) (((pwm) << 4) + 0x00)
|
||||||
|
#define REG_SCALAR(pwm) (((pwm) << 4) + 0x04)
|
||||||
|
#define REG_PERIOD(pwm) (((pwm) << 4) + 0x08)
|
||||||
|
#define REG_DUTY(pwm) (((pwm) << 4) + 0x0C)
|
||||||
|
#define REG_STATUS 0x40
|
||||||
|
|
||||||
|
#define CTRL_ENABLE BIT(0)
|
||||||
|
#define CTRL_INVERT BIT(1)
|
||||||
|
#define CTRL_AUTOLOAD BIT(2)
|
||||||
|
#define CTRL_STOP_IMM BIT(3)
|
||||||
|
#define CTRL_LOAD_PRESCALE BIT(4)
|
||||||
|
#define CTRL_LOAD_PERIOD BIT(5)
|
||||||
|
|
||||||
|
#define STATUS_CTRL_UPDATE BIT(0)
|
||||||
|
#define STATUS_SCALAR_UPDATE BIT(1)
|
||||||
|
#define STATUS_PERIOD_UPDATE BIT(2)
|
||||||
|
#define STATUS_DUTY_UPDATE BIT(3)
|
||||||
|
#define STATUS_ALL_UPDATE 0x0F
|
||||||
|
|
||||||
struct vt8500_chip {
|
struct vt8500_chip {
|
||||||
struct pwm_chip chip;
|
struct pwm_chip chip;
|
||||||
void __iomem *base;
|
void __iomem *base;
|
||||||
|
@ -45,15 +64,17 @@ struct vt8500_chip {
|
||||||
#define to_vt8500_chip(chip) container_of(chip, struct vt8500_chip, chip)
|
#define to_vt8500_chip(chip) container_of(chip, struct vt8500_chip, chip)
|
||||||
|
|
||||||
#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
|
#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
|
||||||
static inline void pwm_busy_wait(void __iomem *reg, u8 bitmask)
|
static inline void pwm_busy_wait(struct vt8500_chip *vt8500, int nr, u8 bitmask)
|
||||||
{
|
{
|
||||||
int loops = msecs_to_loops(10);
|
int loops = msecs_to_loops(10);
|
||||||
while ((readb(reg) & bitmask) && --loops)
|
u32 mask = bitmask << (nr << 8);
|
||||||
|
|
||||||
|
while ((readl(vt8500->base + REG_STATUS) & mask) && --loops)
|
||||||
cpu_relax();
|
cpu_relax();
|
||||||
|
|
||||||
if (unlikely(!loops))
|
if (unlikely(!loops))
|
||||||
pr_warn("Waiting for status bits 0x%x to clear timed out\n",
|
dev_warn(vt8500->chip.dev, "Waiting for status bits 0x%x to clear timed out\n",
|
||||||
bitmask);
|
mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
|
@ -63,6 +84,7 @@ static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
unsigned long long c;
|
unsigned long long c;
|
||||||
unsigned long period_cycles, prescale, pv, dc;
|
unsigned long period_cycles, prescale, pv, dc;
|
||||||
int err;
|
int err;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
err = clk_enable(vt8500->clk);
|
err = clk_enable(vt8500->clk);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
|
@ -91,14 +113,19 @@ static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
do_div(c, period_ns);
|
do_div(c, period_ns);
|
||||||
dc = c;
|
dc = c;
|
||||||
|
|
||||||
pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 1));
|
writel(prescale, vt8500->base + REG_SCALAR(pwm->hwpwm));
|
||||||
writel(prescale, vt8500->base + 0x4 + (pwm->hwpwm << 4));
|
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_SCALAR_UPDATE);
|
||||||
|
|
||||||
pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 2));
|
writel(pv, vt8500->base + REG_PERIOD(pwm->hwpwm));
|
||||||
writel(pv, vt8500->base + 0x8 + (pwm->hwpwm << 4));
|
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_PERIOD_UPDATE);
|
||||||
|
|
||||||
pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 3));
|
writel(dc, vt8500->base + REG_DUTY(pwm->hwpwm));
|
||||||
writel(dc, vt8500->base + 0xc + (pwm->hwpwm << 4));
|
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_DUTY_UPDATE);
|
||||||
|
|
||||||
|
val = readl(vt8500->base + REG_CTRL(pwm->hwpwm));
|
||||||
|
val |= CTRL_AUTOLOAD;
|
||||||
|
writel(val, vt8500->base + REG_CTRL(pwm->hwpwm));
|
||||||
|
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE);
|
||||||
|
|
||||||
clk_disable(vt8500->clk);
|
clk_disable(vt8500->clk);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -106,8 +133,9 @@ static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||||
|
|
||||||
static int vt8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
static int vt8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
{
|
{
|
||||||
int err;
|
|
||||||
struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
|
struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
|
||||||
|
int err;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
err = clk_enable(vt8500->clk);
|
err = clk_enable(vt8500->clk);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
|
@ -115,25 +143,52 @@ static int vt8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 0));
|
val = readl(vt8500->base + REG_CTRL(pwm->hwpwm));
|
||||||
writel(5, vt8500->base + (pwm->hwpwm << 4));
|
val |= CTRL_ENABLE;
|
||||||
|
writel(val, vt8500->base + REG_CTRL(pwm->hwpwm));
|
||||||
|
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void vt8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
static void vt8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||||
{
|
{
|
||||||
struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
|
struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
|
||||||
|
u32 val;
|
||||||
|
|
||||||
pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 0));
|
val = readl(vt8500->base + REG_CTRL(pwm->hwpwm));
|
||||||
writel(0, vt8500->base + (pwm->hwpwm << 4));
|
val &= ~CTRL_ENABLE;
|
||||||
|
writel(val, vt8500->base + REG_CTRL(pwm->hwpwm));
|
||||||
|
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE);
|
||||||
|
|
||||||
clk_disable(vt8500->clk);
|
clk_disable(vt8500->clk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int vt8500_pwm_set_polarity(struct pwm_chip *chip,
|
||||||
|
struct pwm_device *pwm,
|
||||||
|
enum pwm_polarity polarity)
|
||||||
|
{
|
||||||
|
struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
val = readl(vt8500->base + REG_CTRL(pwm->hwpwm));
|
||||||
|
|
||||||
|
if (polarity == PWM_POLARITY_INVERSED)
|
||||||
|
val |= CTRL_INVERT;
|
||||||
|
else
|
||||||
|
val &= ~CTRL_INVERT;
|
||||||
|
|
||||||
|
writel(val, vt8500->base + REG_CTRL(pwm->hwpwm));
|
||||||
|
pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static struct pwm_ops vt8500_pwm_ops = {
|
static struct pwm_ops vt8500_pwm_ops = {
|
||||||
.enable = vt8500_pwm_enable,
|
.enable = vt8500_pwm_enable,
|
||||||
.disable = vt8500_pwm_disable,
|
.disable = vt8500_pwm_disable,
|
||||||
.config = vt8500_pwm_config,
|
.config = vt8500_pwm_config,
|
||||||
|
.set_polarity = vt8500_pwm_set_polarity,
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -163,6 +218,8 @@ static int vt8500_pwm_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
chip->chip.dev = &pdev->dev;
|
chip->chip.dev = &pdev->dev;
|
||||||
chip->chip.ops = &vt8500_pwm_ops;
|
chip->chip.ops = &vt8500_pwm_ops;
|
||||||
|
chip->chip.of_xlate = of_pwm_xlate_with_flags;
|
||||||
|
chip->chip.of_pwm_n_cells = 3;
|
||||||
chip->chip.base = -1;
|
chip->chip.base = -1;
|
||||||
chip->chip.npwm = VT8500_NR_PWMS;
|
chip->chip.npwm = VT8500_NR_PWMS;
|
||||||
|
|
||||||
|
|
|
@ -41,10 +41,9 @@ static int pwm_backlight_update_status(struct backlight_device *bl)
|
||||||
int brightness = bl->props.brightness;
|
int brightness = bl->props.brightness;
|
||||||
int max = bl->props.max_brightness;
|
int max = bl->props.max_brightness;
|
||||||
|
|
||||||
if (bl->props.power != FB_BLANK_UNBLANK)
|
if (bl->props.power != FB_BLANK_UNBLANK ||
|
||||||
brightness = 0;
|
bl->props.fb_blank != FB_BLANK_UNBLANK ||
|
||||||
|
bl->props.state & BL_CORE_FBBLANK)
|
||||||
if (bl->props.fb_blank != FB_BLANK_UNBLANK)
|
|
||||||
brightness = 0;
|
brightness = 0;
|
||||||
|
|
||||||
if (pb->notify)
|
if (pb->notify)
|
||||||
|
@ -135,12 +134,6 @@ static int pwm_backlight_parse_dt(struct device *dev,
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
if (value >= data->max_brightness) {
|
|
||||||
dev_warn(dev, "invalid default brightness level: %u, using %u\n",
|
|
||||||
value, data->max_brightness - 1);
|
|
||||||
value = data->max_brightness - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
data->dft_brightness = value;
|
data->dft_brightness = value;
|
||||||
data->max_brightness--;
|
data->max_brightness--;
|
||||||
}
|
}
|
||||||
|
@ -249,6 +242,13 @@ static int pwm_backlight_probe(struct platform_device *pdev)
|
||||||
goto err_alloc;
|
goto err_alloc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data->dft_brightness > data->max_brightness) {
|
||||||
|
dev_warn(&pdev->dev,
|
||||||
|
"invalid default brightness level: %u, using %u\n",
|
||||||
|
data->dft_brightness, data->max_brightness);
|
||||||
|
data->dft_brightness = data->max_brightness;
|
||||||
|
}
|
||||||
|
|
||||||
bl->props.brightness = data->dft_brightness;
|
bl->props.brightness = data->dft_brightness;
|
||||||
backlight_update_status(bl);
|
backlight_update_status(bl);
|
||||||
|
|
||||||
|
|
|
@ -146,6 +146,8 @@ struct pwm_ops {
|
||||||
* @base: number of first PWM controlled by this chip
|
* @base: number of first PWM controlled by this chip
|
||||||
* @npwm: number of PWMs controlled by this chip
|
* @npwm: number of PWMs controlled by this chip
|
||||||
* @pwms: array of PWM devices allocated by the framework
|
* @pwms: array of PWM devices allocated by the framework
|
||||||
|
* @can_sleep: must be true if the .config(), .enable() or .disable()
|
||||||
|
* operations may sleep
|
||||||
*/
|
*/
|
||||||
struct pwm_chip {
|
struct pwm_chip {
|
||||||
struct device *dev;
|
struct device *dev;
|
||||||
|
@ -159,6 +161,7 @@ struct pwm_chip {
|
||||||
struct pwm_device * (*of_xlate)(struct pwm_chip *pc,
|
struct pwm_device * (*of_xlate)(struct pwm_chip *pc,
|
||||||
const struct of_phandle_args *args);
|
const struct of_phandle_args *args);
|
||||||
unsigned int of_pwm_n_cells;
|
unsigned int of_pwm_n_cells;
|
||||||
|
bool can_sleep;
|
||||||
};
|
};
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_PWM)
|
#if IS_ENABLED(CONFIG_PWM)
|
||||||
|
@ -182,6 +185,8 @@ struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id);
|
||||||
struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,
|
struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,
|
||||||
const char *con_id);
|
const char *con_id);
|
||||||
void devm_pwm_put(struct device *dev, struct pwm_device *pwm);
|
void devm_pwm_put(struct device *dev, struct pwm_device *pwm);
|
||||||
|
|
||||||
|
bool pwm_can_sleep(struct pwm_device *pwm);
|
||||||
#else
|
#else
|
||||||
static inline int pwm_set_chip_data(struct pwm_device *pwm, void *data)
|
static inline int pwm_set_chip_data(struct pwm_device *pwm, void *data)
|
||||||
{
|
{
|
||||||
|
@ -242,6 +247,11 @@ static inline struct pwm_device *devm_of_pwm_get(struct device *dev,
|
||||||
static inline void devm_pwm_put(struct device *dev, struct pwm_device *pwm)
|
static inline void devm_pwm_put(struct device *dev, struct pwm_device *pwm)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool pwm_can_sleep(struct pwm_device *pwm)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct pwm_lookup {
|
struct pwm_lookup {
|
||||||
|
|
Loading…
Reference in New Issue