mirror of
https://github.com/torvalds/linux.git
synced 2024-11-15 08:31:55 +00:00
pwm: Changes for v4.12-rc1
Adds a new driver for the PWM controller found on MediaTek SoCs and extends support for the Atmel PWM controller to include the SAMA5D2. Some existing drivers have been migrated to the atomic API and a few others see miscellaneous improvements. -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEEiOrDCAFJzPfAjcif3SOs138+s6EFAlkR5d8ZHHRoaWVycnku cmVkaW5nQGdtYWlsLmNvbQAKCRDdI6zXfz6zoRepEACg/iW8TgaWZwI64twDVowZ BgDm2QY/eIcseT+ooHoypNqprdzRk/f8lj75+SQKx65Q9FAqO1mfZbn0RmrJ5vNT 8UDkV/f2l/MKdFWYf9PfBkCbcfNzxBQ0qYmjj4XDJJGd3iUfffGTvzSmVmHlMWaE S7iN27JPc/a7C73sNgtf0d0iExXo3fFRdLoizeCXDjAGpvZBvrheQDpAiFFcuN+G kTsqGO+INxYJT/VqXb0kB9yJQUpcCm9oGgQV7oU0cHD07ZXOJ5Hlk2bskKz4FoWp gL1AXexwDupjrQwEa77X4T8bGkyW7DjDogVfV8Caks8DJk1NkdiDmmyVrdjkpCSe 5KB8PueQKNQ30rVzbvj5h7CwSQBAoRbzv6/7hqyo2nFvsfoijEgfVdFfwOZttCa+ N5tcnASkHGTF51Yvw4PEOYRbbdNniA5p0XZQlB8bLNlNxdgFJ865SrQFEcjfs+g5 Hyho7eSRjz+4BXWnQJWPCrAfkuN/kSpszwxVsXhsw+JSIWnSYnnMV22usSFKeOSz iR335N6To/YbolDCu2W8uPLenxRR9huvO0JLX1bxG/C2IFA/CPGuCpfZ1jafxVAr uZzhGkJh8MTbbFE1K/gIdFxP5dgl++ogAivHD/KUsaQ815+UwAY5UcSJBOLMPIIt zZkhin7cLigK0AW59Tl74Q== =bR2p -----END PGP SIGNATURE----- Merge tag 'pwm/for-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm Pull pwm updates from Thierry Reding: "Adds a new driver for the PWM controller found on MediaTek SoCs and extends support for the Atmel PWM controller to include the SAMA5D2. Some existing drivers have been migrated to the atomic API and a few others see miscellaneous improvements" * tag 'pwm/for-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: pwm: tegra: Read PWM clock source rate in driver init pwm: pca9685: Fix GPIO-only operation pwm: mediatek: Don't explicitly set .owner pwm: tegra: Avoid potential overflow for short periods pwm: tegra: Add support to configure pin state in suspends/resume pwm: tegra: Add DT binding details to configure pin in suspends/resume pwm: tegra: Increase precision in PWM rate calculation pwm: tegra: Use DIV_ROUND_CLOSEST_ULL() instead of local implementation pwm: Add MediaTek PWM support dt-bindings: pwm: Add MediaTek PWM bindings pwm: atmel: Enable PWM on sama5d2 pwm: atmel: Switch to atomic PWM pwm: atmel-hlcdc: Implement the suspend/resume hooks pwm: atmel-hlcdc: Convert to the atomic PWM API
This commit is contained in:
commit
ecc721a72c
@ -4,6 +4,7 @@ Required properties:
|
||||
- compatible: should be one of:
|
||||
- "atmel,at91sam9rl-pwm"
|
||||
- "atmel,sama5d3-pwm"
|
||||
- "atmel,sama5d2-pwm"
|
||||
- reg: physical base address and length of the controller's registers
|
||||
- #pwm-cells: Should be 3. See pwm.txt in this directory for a
|
||||
description of the cells format.
|
||||
|
@ -19,6 +19,19 @@ Required properties:
|
||||
- reset-names: Must include the following entries:
|
||||
- pwm
|
||||
|
||||
Optional properties:
|
||||
============================
|
||||
In some of the interface like PWM based regulator device, it is required
|
||||
to configure the pins differently in different states, especially in suspend
|
||||
state of the system. The configuration of pin is provided via the pinctrl
|
||||
DT node as detailed in the pinctrl DT binding document
|
||||
Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
|
||||
|
||||
The PWM node will have following optional properties.
|
||||
pinctrl-names: Pin state names. Must be "default" and "sleep".
|
||||
pinctrl-0: phandle for the default/active state of pin configurations.
|
||||
pinctrl-1: phandle for the sleep state of pin configurations.
|
||||
|
||||
Example:
|
||||
|
||||
pwm: pwm@7000a000 {
|
||||
@ -29,3 +42,35 @@ Example:
|
||||
resets = <&tegra_car 17>;
|
||||
reset-names = "pwm";
|
||||
};
|
||||
|
||||
|
||||
Example with the pin configuration for suspend and resume:
|
||||
=========================================================
|
||||
Suppose pin PE7 (On Tegra210) interfaced with the regulator device and
|
||||
it requires PWM output to be tristated when system enters suspend.
|
||||
Following will be DT binding to achieve this:
|
||||
|
||||
#include <dt-bindings/pinctrl/pinctrl-tegra.h>
|
||||
|
||||
pinmux@700008d4 {
|
||||
pwm_active_state: pwm_active_state {
|
||||
pe7 {
|
||||
nvidia,pins = "pe7";
|
||||
nvidia,tristate = <TEGRA_PIN_DISABLE>;
|
||||
};
|
||||
};
|
||||
|
||||
pwm_sleep_state: pwm_sleep_state {
|
||||
pe7 {
|
||||
nvidia,pins = "pe7";
|
||||
nvidia,tristate = <TEGRA_PIN_ENABLE>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pwm@7000a000 {
|
||||
/* Mandatory PWM properties */
|
||||
pinctrl-names = "default", "sleep";
|
||||
pinctrl-0 = <&pwm_active_state>;
|
||||
pinctrl-1 = <&pwm_sleep_state>;
|
||||
};
|
||||
|
34
Documentation/devicetree/bindings/pwm/pwm-mediatek.txt
Normal file
34
Documentation/devicetree/bindings/pwm/pwm-mediatek.txt
Normal file
@ -0,0 +1,34 @@
|
||||
MediaTek PWM controller
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "mediatek,<name>-pwm":
|
||||
- "mediatek,mt7623-pwm": found on mt7623 SoC.
|
||||
- reg: physical base address and length of the controller's registers.
|
||||
- #pwm-cells: must be 2. See pwm.txt in this directory for a description of
|
||||
the cell format.
|
||||
- clocks: phandle and clock specifier of the PWM reference clock.
|
||||
- clock-names: must contain the following:
|
||||
- "top": the top clock generator
|
||||
- "main": clock used by the PWM core
|
||||
- "pwm1-5": the five per PWM clocks
|
||||
- pinctrl-names: Must contain a "default" entry.
|
||||
- pinctrl-0: One property must exist for each entry in pinctrl-names.
|
||||
See pinctrl/pinctrl-bindings.txt for details of the property values.
|
||||
|
||||
Example:
|
||||
pwm0: pwm@11006000 {
|
||||
compatible = "mediatek,mt7623-pwm";
|
||||
reg = <0 0x11006000 0 0x1000>;
|
||||
#pwm-cells = <2>;
|
||||
clocks = <&topckgen CLK_TOP_PWM_SEL>,
|
||||
<&pericfg CLK_PERI_PWM>,
|
||||
<&pericfg CLK_PERI_PWM1>,
|
||||
<&pericfg CLK_PERI_PWM2>,
|
||||
<&pericfg CLK_PERI_PWM3>,
|
||||
<&pericfg CLK_PERI_PWM4>,
|
||||
<&pericfg CLK_PERI_PWM5>;
|
||||
clock-names = "top", "main", "pwm1", "pwm2",
|
||||
"pwm3", "pwm4", "pwm5";
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pwm0_pins>;
|
||||
};
|
@ -293,6 +293,15 @@ config PWM_MTK_DISP
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called pwm-mtk-disp.
|
||||
|
||||
config PWM_MEDIATEK
|
||||
tristate "MediaTek PWM support"
|
||||
depends on ARCH_MEDIATEK || COMPILE_TEST
|
||||
help
|
||||
Generic PWM framework driver for Mediatek ARM SoC.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called pwm-mxs.
|
||||
|
||||
config PWM_MXS
|
||||
tristate "Freescale MXS PWM support"
|
||||
depends on ARCH_MXS && OF
|
||||
|
@ -26,6 +26,7 @@ obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o
|
||||
obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o
|
||||
obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o
|
||||
obj-$(CONFIG_PWM_MESON) += pwm-meson.o
|
||||
obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o
|
||||
obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o
|
||||
obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
|
||||
obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o
|
||||
|
@ -49,162 +49,137 @@ static inline struct atmel_hlcdc_pwm *to_atmel_hlcdc_pwm(struct pwm_chip *chip)
|
||||
return container_of(chip, struct atmel_hlcdc_pwm, chip);
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_pwm_config(struct pwm_chip *c,
|
||||
struct pwm_device *pwm,
|
||||
int duty_ns, int period_ns)
|
||||
static int atmel_hlcdc_pwm_apply(struct pwm_chip *c, struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct atmel_hlcdc_pwm *chip = to_atmel_hlcdc_pwm(c);
|
||||
struct atmel_hlcdc *hlcdc = chip->hlcdc;
|
||||
struct clk *new_clk = hlcdc->slow_clk;
|
||||
u64 pwmcval = duty_ns * 256;
|
||||
unsigned long clk_freq;
|
||||
u64 clk_period_ns;
|
||||
u32 pwmcfg;
|
||||
int pres;
|
||||
unsigned int status;
|
||||
int ret;
|
||||
|
||||
if (!chip->errata || !chip->errata->slow_clk_erratum) {
|
||||
clk_freq = clk_get_rate(new_clk);
|
||||
if (!clk_freq)
|
||||
return -EINVAL;
|
||||
if (state->enabled) {
|
||||
struct clk *new_clk = hlcdc->slow_clk;
|
||||
u64 pwmcval = state->duty_cycle * 256;
|
||||
unsigned long clk_freq;
|
||||
u64 clk_period_ns;
|
||||
u32 pwmcfg;
|
||||
int pres;
|
||||
|
||||
clk_period_ns = (u64)NSEC_PER_SEC * 256;
|
||||
do_div(clk_period_ns, clk_freq);
|
||||
}
|
||||
if (!chip->errata || !chip->errata->slow_clk_erratum) {
|
||||
clk_freq = clk_get_rate(new_clk);
|
||||
if (!clk_freq)
|
||||
return -EINVAL;
|
||||
|
||||
/* Errata: cannot use slow clk on some IP revisions */
|
||||
if ((chip->errata && chip->errata->slow_clk_erratum) ||
|
||||
clk_period_ns > period_ns) {
|
||||
new_clk = hlcdc->sys_clk;
|
||||
clk_freq = clk_get_rate(new_clk);
|
||||
if (!clk_freq)
|
||||
return -EINVAL;
|
||||
clk_period_ns = (u64)NSEC_PER_SEC * 256;
|
||||
do_div(clk_period_ns, clk_freq);
|
||||
}
|
||||
|
||||
clk_period_ns = (u64)NSEC_PER_SEC * 256;
|
||||
do_div(clk_period_ns, clk_freq);
|
||||
}
|
||||
/* Errata: cannot use slow clk on some IP revisions */
|
||||
if ((chip->errata && chip->errata->slow_clk_erratum) ||
|
||||
clk_period_ns > state->period) {
|
||||
new_clk = hlcdc->sys_clk;
|
||||
clk_freq = clk_get_rate(new_clk);
|
||||
if (!clk_freq)
|
||||
return -EINVAL;
|
||||
|
||||
for (pres = 0; pres <= ATMEL_HLCDC_PWMPS_MAX; pres++) {
|
||||
clk_period_ns = (u64)NSEC_PER_SEC * 256;
|
||||
do_div(clk_period_ns, clk_freq);
|
||||
}
|
||||
|
||||
for (pres = 0; pres <= ATMEL_HLCDC_PWMPS_MAX; pres++) {
|
||||
/* Errata: cannot divide by 1 on some IP revisions */
|
||||
if (!pres && chip->errata && chip->errata->div1_clk_erratum)
|
||||
continue;
|
||||
if (!pres && chip->errata &&
|
||||
chip->errata->div1_clk_erratum)
|
||||
continue;
|
||||
|
||||
if ((clk_period_ns << pres) >= period_ns)
|
||||
break;
|
||||
}
|
||||
if ((clk_period_ns << pres) >= state->period)
|
||||
break;
|
||||
}
|
||||
|
||||
if (pres > ATMEL_HLCDC_PWMPS_MAX)
|
||||
return -EINVAL;
|
||||
if (pres > ATMEL_HLCDC_PWMPS_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
pwmcfg = ATMEL_HLCDC_PWMPS(pres);
|
||||
pwmcfg = ATMEL_HLCDC_PWMPS(pres);
|
||||
|
||||
if (new_clk != chip->cur_clk) {
|
||||
u32 gencfg = 0;
|
||||
int ret;
|
||||
if (new_clk != chip->cur_clk) {
|
||||
u32 gencfg = 0;
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(new_clk);
|
||||
ret = clk_prepare_enable(new_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clk_disable_unprepare(chip->cur_clk);
|
||||
chip->cur_clk = new_clk;
|
||||
|
||||
if (new_clk == hlcdc->sys_clk)
|
||||
gencfg = ATMEL_HLCDC_CLKPWMSEL;
|
||||
|
||||
ret = regmap_update_bits(hlcdc->regmap,
|
||||
ATMEL_HLCDC_CFG(0),
|
||||
ATMEL_HLCDC_CLKPWMSEL,
|
||||
gencfg);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
do_div(pwmcval, state->period);
|
||||
|
||||
/*
|
||||
* The PWM duty cycle is configurable from 0/256 to 255/256 of
|
||||
* the period cycle. Hence we can't set a duty cycle occupying
|
||||
* the whole period cycle if we're asked to.
|
||||
* Set it to 255 if pwmcval is greater than 256.
|
||||
*/
|
||||
if (pwmcval > 255)
|
||||
pwmcval = 255;
|
||||
|
||||
pwmcfg |= ATMEL_HLCDC_PWMCVAL(pwmcval);
|
||||
|
||||
if (state->polarity == PWM_POLARITY_NORMAL)
|
||||
pwmcfg |= ATMEL_HLCDC_PWMPOL;
|
||||
|
||||
ret = regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(6),
|
||||
ATMEL_HLCDC_PWMCVAL_MASK |
|
||||
ATMEL_HLCDC_PWMPS_MASK |
|
||||
ATMEL_HLCDC_PWMPOL,
|
||||
pwmcfg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(hlcdc->regmap, ATMEL_HLCDC_EN,
|
||||
ATMEL_HLCDC_PWM);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(hlcdc->regmap, ATMEL_HLCDC_SR,
|
||||
status,
|
||||
status & ATMEL_HLCDC_PWM,
|
||||
10, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
ret = regmap_write(hlcdc->regmap, ATMEL_HLCDC_DIS,
|
||||
ATMEL_HLCDC_PWM);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_poll_timeout(hlcdc->regmap, ATMEL_HLCDC_SR,
|
||||
status,
|
||||
!(status & ATMEL_HLCDC_PWM),
|
||||
10, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clk_disable_unprepare(chip->cur_clk);
|
||||
chip->cur_clk = new_clk;
|
||||
|
||||
if (new_clk == hlcdc->sys_clk)
|
||||
gencfg = ATMEL_HLCDC_CLKPWMSEL;
|
||||
|
||||
ret = regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(0),
|
||||
ATMEL_HLCDC_CLKPWMSEL, gencfg);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
do_div(pwmcval, period_ns);
|
||||
|
||||
/*
|
||||
* The PWM duty cycle is configurable from 0/256 to 255/256 of the
|
||||
* period cycle. Hence we can't set a duty cycle occupying the
|
||||
* whole period cycle if we're asked to.
|
||||
* Set it to 255 if pwmcval is greater than 256.
|
||||
*/
|
||||
if (pwmcval > 255)
|
||||
pwmcval = 255;
|
||||
|
||||
pwmcfg |= ATMEL_HLCDC_PWMCVAL(pwmcval);
|
||||
|
||||
return regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(6),
|
||||
ATMEL_HLCDC_PWMCVAL_MASK |
|
||||
ATMEL_HLCDC_PWMPS_MASK,
|
||||
pwmcfg);
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_pwm_set_polarity(struct pwm_chip *c,
|
||||
struct pwm_device *pwm,
|
||||
enum pwm_polarity polarity)
|
||||
{
|
||||
struct atmel_hlcdc_pwm *chip = to_atmel_hlcdc_pwm(c);
|
||||
struct atmel_hlcdc *hlcdc = chip->hlcdc;
|
||||
u32 cfg = 0;
|
||||
|
||||
if (polarity == PWM_POLARITY_NORMAL)
|
||||
cfg = ATMEL_HLCDC_PWMPOL;
|
||||
|
||||
return regmap_update_bits(hlcdc->regmap, ATMEL_HLCDC_CFG(6),
|
||||
ATMEL_HLCDC_PWMPOL, cfg);
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_pwm_enable(struct pwm_chip *c, struct pwm_device *pwm)
|
||||
{
|
||||
struct atmel_hlcdc_pwm *chip = to_atmel_hlcdc_pwm(c);
|
||||
struct atmel_hlcdc *hlcdc = chip->hlcdc;
|
||||
u32 status;
|
||||
int ret;
|
||||
|
||||
ret = regmap_write(hlcdc->regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_PWM);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
while (true) {
|
||||
ret = regmap_read(hlcdc->regmap, ATMEL_HLCDC_SR, &status);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if ((status & ATMEL_HLCDC_PWM) != 0)
|
||||
break;
|
||||
|
||||
usleep_range(1, 10);
|
||||
chip->cur_clk = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_pwm_disable(struct pwm_chip *c,
|
||||
struct pwm_device *pwm)
|
||||
{
|
||||
struct atmel_hlcdc_pwm *chip = to_atmel_hlcdc_pwm(c);
|
||||
struct atmel_hlcdc *hlcdc = chip->hlcdc;
|
||||
u32 status;
|
||||
int ret;
|
||||
|
||||
ret = regmap_write(hlcdc->regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_PWM);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
while (true) {
|
||||
ret = regmap_read(hlcdc->regmap, ATMEL_HLCDC_SR, &status);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
if ((status & ATMEL_HLCDC_PWM) == 0)
|
||||
break;
|
||||
|
||||
usleep_range(1, 10);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct pwm_ops atmel_hlcdc_pwm_ops = {
|
||||
.config = atmel_hlcdc_pwm_config,
|
||||
.set_polarity = atmel_hlcdc_pwm_set_polarity,
|
||||
.enable = atmel_hlcdc_pwm_enable,
|
||||
.disable = atmel_hlcdc_pwm_disable,
|
||||
.apply = atmel_hlcdc_pwm_apply,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
@ -216,6 +191,40 @@ static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_sama5d3_errata = {
|
||||
.div1_clk_erratum = true,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int atmel_hlcdc_pwm_suspend(struct device *dev)
|
||||
{
|
||||
struct atmel_hlcdc_pwm *chip = dev_get_drvdata(dev);
|
||||
|
||||
/* Keep the periph clock enabled if the PWM is still running. */
|
||||
if (pwm_is_enabled(&chip->chip.pwms[0]))
|
||||
clk_disable_unprepare(chip->hlcdc->periph_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_pwm_resume(struct device *dev)
|
||||
{
|
||||
struct atmel_hlcdc_pwm *chip = dev_get_drvdata(dev);
|
||||
struct pwm_state state;
|
||||
int ret;
|
||||
|
||||
pwm_get_state(&chip->chip.pwms[0], &state);
|
||||
|
||||
/* Re-enable the periph clock it was stopped during suspend. */
|
||||
if (!state.enabled) {
|
||||
ret = clk_prepare_enable(chip->hlcdc->periph_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return atmel_hlcdc_pwm_apply(&chip->chip, &chip->chip.pwms[0], &state);
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(atmel_hlcdc_pwm_pm_ops,
|
||||
atmel_hlcdc_pwm_suspend, atmel_hlcdc_pwm_resume);
|
||||
|
||||
static const struct of_device_id atmel_hlcdc_dt_ids[] = {
|
||||
{
|
||||
.compatible = "atmel,at91sam9n12-hlcdc",
|
||||
@ -305,6 +314,7 @@ static struct platform_driver atmel_hlcdc_pwm_driver = {
|
||||
.driver = {
|
||||
.name = "atmel-hlcdc-pwm",
|
||||
.of_match_table = atmel_hlcdc_pwm_dt_ids,
|
||||
.pm = &atmel_hlcdc_pwm_pm_ops,
|
||||
},
|
||||
.probe = atmel_hlcdc_pwm_probe,
|
||||
.remove = atmel_hlcdc_pwm_remove,
|
||||
|
@ -58,17 +58,22 @@
|
||||
#define PWM_MAX_PRD 0xFFFF
|
||||
#define PRD_MAX_PRES 10
|
||||
|
||||
struct atmel_pwm_registers {
|
||||
u8 period;
|
||||
u8 period_upd;
|
||||
u8 duty;
|
||||
u8 duty_upd;
|
||||
};
|
||||
|
||||
struct atmel_pwm_chip {
|
||||
struct pwm_chip chip;
|
||||
struct clk *clk;
|
||||
void __iomem *base;
|
||||
const struct atmel_pwm_registers *regs;
|
||||
|
||||
unsigned int updated_pwms;
|
||||
/* 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);
|
||||
};
|
||||
|
||||
static inline struct atmel_pwm_chip *to_atmel_pwm_chip(struct pwm_chip *chip)
|
||||
@ -105,153 +110,71 @@ static inline void atmel_pwm_ch_writel(struct atmel_pwm_chip *chip,
|
||||
writel_relaxed(val, chip->base + base + offset);
|
||||
}
|
||||
|
||||
static int atmel_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
int duty_ns, int period_ns)
|
||||
static int atmel_pwm_calculate_cprd_and_pres(struct pwm_chip *chip,
|
||||
const struct pwm_state *state,
|
||||
unsigned long *cprd, u32 *pres)
|
||||
{
|
||||
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
|
||||
unsigned long prd, dty;
|
||||
unsigned long long div;
|
||||
unsigned int pres = 0;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (pwm_is_enabled(pwm) && (period_ns != pwm_get_period(pwm))) {
|
||||
dev_err(chip->dev, "cannot change PWM period while enabled\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
unsigned long long cycles = state->period;
|
||||
|
||||
/* Calculate the period cycles and prescale value */
|
||||
div = (unsigned long long)clk_get_rate(atmel_pwm->clk) * period_ns;
|
||||
do_div(div, NSEC_PER_SEC);
|
||||
cycles *= clk_get_rate(atmel_pwm->clk);
|
||||
do_div(cycles, NSEC_PER_SEC);
|
||||
|
||||
while (div > PWM_MAX_PRD) {
|
||||
div >>= 1;
|
||||
pres++;
|
||||
}
|
||||
for (*pres = 0; cycles > PWM_MAX_PRD; cycles >>= 1)
|
||||
(*pres)++;
|
||||
|
||||
if (pres > PRD_MAX_PRES) {
|
||||
if (*pres > PRD_MAX_PRES) {
|
||||
dev_err(chip->dev, "pres exceeds the maximum value\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Calculate the duty cycles */
|
||||
prd = div;
|
||||
div *= duty_ns;
|
||||
do_div(div, period_ns);
|
||||
dty = prd - div;
|
||||
*cprd = cycles;
|
||||
|
||||
ret = clk_enable(atmel_pwm->clk);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "failed to enable PWM clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* It is necessary to preserve CPOL, inside CMR */
|
||||
val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
|
||||
val = (val & ~PWM_CMR_CPRE_MSK) | (pres & PWM_CMR_CPRE_MSK);
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
|
||||
atmel_pwm->config(chip, pwm, dty, prd);
|
||||
mutex_lock(&atmel_pwm->isr_lock);
|
||||
atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR);
|
||||
atmel_pwm->updated_pwms &= ~(1 << pwm->hwpwm);
|
||||
mutex_unlock(&atmel_pwm->isr_lock);
|
||||
|
||||
clk_disable(atmel_pwm->clk);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void atmel_pwm_config_v1(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
unsigned long dty, unsigned long prd)
|
||||
static void atmel_pwm_calculate_cdty(const struct pwm_state *state,
|
||||
unsigned long cprd, unsigned long *cdty)
|
||||
{
|
||||
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
|
||||
unsigned int val;
|
||||
unsigned long long cycles = state->duty_cycle;
|
||||
|
||||
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CUPD, dty);
|
||||
|
||||
val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
|
||||
val &= ~PWM_CMR_UPD_CDTY;
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
|
||||
|
||||
/*
|
||||
* If the PWM channel is enabled, only update CDTY by using the update
|
||||
* register, it needs to set bit 10 of CMR to 0
|
||||
*/
|
||||
if (pwm_is_enabled(pwm))
|
||||
return;
|
||||
/*
|
||||
* If the PWM channel is disabled, write value to duty and period
|
||||
* registers directly.
|
||||
*/
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CDTY, dty);
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CPRD, prd);
|
||||
cycles *= cprd;
|
||||
do_div(cycles, state->period);
|
||||
*cdty = cprd - cycles;
|
||||
}
|
||||
|
||||
static void atmel_pwm_config_v2(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
unsigned long dty, unsigned long prd)
|
||||
{
|
||||
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
|
||||
|
||||
if (pwm_is_enabled(pwm)) {
|
||||
/*
|
||||
* If the PWM channel is enabled, using the duty update register
|
||||
* to update the value.
|
||||
*/
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CDTYUPD, dty);
|
||||
} else {
|
||||
/*
|
||||
* If the PWM channel is disabled, write value to duty and
|
||||
* period registers directly.
|
||||
*/
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CDTY, dty);
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CPRD, prd);
|
||||
}
|
||||
}
|
||||
|
||||
static int atmel_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
enum pwm_polarity polarity)
|
||||
static void atmel_pwm_update_cdty(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
unsigned long cdty)
|
||||
{
|
||||
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
|
||||
|
||||
if (polarity == PWM_POLARITY_NORMAL)
|
||||
val &= ~PWM_CMR_CPOL;
|
||||
else
|
||||
val |= PWM_CMR_CPOL;
|
||||
|
||||
ret = clk_enable(atmel_pwm->clk);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "failed to enable PWM clock\n");
|
||||
return ret;
|
||||
if (atmel_pwm->regs->duty_upd ==
|
||||
atmel_pwm->regs->period_upd) {
|
||||
val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
|
||||
val &= ~PWM_CMR_UPD_CDTY;
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
|
||||
}
|
||||
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
|
||||
|
||||
clk_disable(atmel_pwm->clk);
|
||||
|
||||
return 0;
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm,
|
||||
atmel_pwm->regs->duty_upd, cdty);
|
||||
}
|
||||
|
||||
static int atmel_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
static void atmel_pwm_set_cprd_cdty(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
unsigned long cprd, unsigned long cdty)
|
||||
{
|
||||
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
|
||||
int ret;
|
||||
|
||||
ret = clk_enable(atmel_pwm->clk);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "failed to enable PWM clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
atmel_pwm_writel(atmel_pwm, PWM_ENA, 1 << pwm->hwpwm);
|
||||
|
||||
return 0;
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm,
|
||||
atmel_pwm->regs->duty, cdty);
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm,
|
||||
atmel_pwm->regs->period, cprd);
|
||||
}
|
||||
|
||||
static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
bool disable_clk)
|
||||
{
|
||||
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
|
||||
unsigned long timeout = jiffies + 2 * HZ;
|
||||
@ -282,37 +205,99 @@ static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
time_before(jiffies, timeout))
|
||||
usleep_range(10, 100);
|
||||
|
||||
clk_disable(atmel_pwm->clk);
|
||||
if (disable_clk)
|
||||
clk_disable(atmel_pwm->clk);
|
||||
}
|
||||
|
||||
static int atmel_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip);
|
||||
struct pwm_state cstate;
|
||||
unsigned long cprd, cdty;
|
||||
u32 pres, val;
|
||||
int ret;
|
||||
|
||||
pwm_get_state(pwm, &cstate);
|
||||
|
||||
if (state->enabled) {
|
||||
if (cstate.enabled &&
|
||||
cstate.polarity == state->polarity &&
|
||||
cstate.period == state->period) {
|
||||
cprd = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm,
|
||||
atmel_pwm->regs->period);
|
||||
atmel_pwm_calculate_cdty(state, cprd, &cdty);
|
||||
atmel_pwm_update_cdty(chip, pwm, cdty);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = atmel_pwm_calculate_cprd_and_pres(chip, state, &cprd,
|
||||
&pres);
|
||||
if (ret) {
|
||||
dev_err(chip->dev,
|
||||
"failed to calculate cprd and prescaler\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
atmel_pwm_calculate_cdty(state, cprd, &cdty);
|
||||
|
||||
if (cstate.enabled) {
|
||||
atmel_pwm_disable(chip, pwm, false);
|
||||
} else {
|
||||
ret = clk_enable(atmel_pwm->clk);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "failed to enable clock\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* It is necessary to preserve CPOL, inside CMR */
|
||||
val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR);
|
||||
val = (val & ~PWM_CMR_CPRE_MSK) | (pres & PWM_CMR_CPRE_MSK);
|
||||
if (state->polarity == PWM_POLARITY_NORMAL)
|
||||
val &= ~PWM_CMR_CPOL;
|
||||
else
|
||||
val |= PWM_CMR_CPOL;
|
||||
atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val);
|
||||
atmel_pwm_set_cprd_cdty(chip, pwm, cprd, cdty);
|
||||
mutex_lock(&atmel_pwm->isr_lock);
|
||||
atmel_pwm->updated_pwms |= atmel_pwm_readl(atmel_pwm, PWM_ISR);
|
||||
atmel_pwm->updated_pwms &= ~(1 << pwm->hwpwm);
|
||||
mutex_unlock(&atmel_pwm->isr_lock);
|
||||
atmel_pwm_writel(atmel_pwm, PWM_ENA, 1 << pwm->hwpwm);
|
||||
} else if (cstate.enabled) {
|
||||
atmel_pwm_disable(chip, pwm, true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwm_ops atmel_pwm_ops = {
|
||||
.config = atmel_pwm_config,
|
||||
.set_polarity = atmel_pwm_set_polarity,
|
||||
.enable = atmel_pwm_enable,
|
||||
.disable = atmel_pwm_disable,
|
||||
.apply = atmel_pwm_apply,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
struct atmel_pwm_data {
|
||||
void (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
unsigned long dty, unsigned long prd);
|
||||
static const struct atmel_pwm_registers atmel_pwm_regs_v1 = {
|
||||
.period = PWMV1_CPRD,
|
||||
.period_upd = PWMV1_CUPD,
|
||||
.duty = PWMV1_CDTY,
|
||||
.duty_upd = PWMV1_CUPD,
|
||||
};
|
||||
|
||||
static const struct atmel_pwm_data atmel_pwm_data_v1 = {
|
||||
.config = atmel_pwm_config_v1,
|
||||
};
|
||||
|
||||
static const struct atmel_pwm_data atmel_pwm_data_v2 = {
|
||||
.config = atmel_pwm_config_v2,
|
||||
static const struct atmel_pwm_registers atmel_pwm_regs_v2 = {
|
||||
.period = PWMV2_CPRD,
|
||||
.period_upd = PWMV2_CPRDUPD,
|
||||
.duty = PWMV2_CDTY,
|
||||
.duty_upd = PWMV2_CDTYUPD,
|
||||
};
|
||||
|
||||
static const struct platform_device_id atmel_pwm_devtypes[] = {
|
||||
{
|
||||
.name = "at91sam9rl-pwm",
|
||||
.driver_data = (kernel_ulong_t)&atmel_pwm_data_v1,
|
||||
.driver_data = (kernel_ulong_t)&atmel_pwm_regs_v1,
|
||||
}, {
|
||||
.name = "sama5d3-pwm",
|
||||
.driver_data = (kernel_ulong_t)&atmel_pwm_data_v2,
|
||||
.driver_data = (kernel_ulong_t)&atmel_pwm_regs_v2,
|
||||
}, {
|
||||
/* sentinel */
|
||||
},
|
||||
@ -322,17 +307,20 @@ MODULE_DEVICE_TABLE(platform, atmel_pwm_devtypes);
|
||||
static const struct of_device_id atmel_pwm_dt_ids[] = {
|
||||
{
|
||||
.compatible = "atmel,at91sam9rl-pwm",
|
||||
.data = &atmel_pwm_data_v1,
|
||||
.data = &atmel_pwm_regs_v1,
|
||||
}, {
|
||||
.compatible = "atmel,sama5d3-pwm",
|
||||
.data = &atmel_pwm_data_v2,
|
||||
.data = &atmel_pwm_regs_v2,
|
||||
}, {
|
||||
.compatible = "atmel,sama5d2-pwm",
|
||||
.data = &atmel_pwm_regs_v2,
|
||||
}, {
|
||||
/* sentinel */
|
||||
},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids);
|
||||
|
||||
static inline const struct atmel_pwm_data *
|
||||
static inline const struct atmel_pwm_registers *
|
||||
atmel_pwm_get_driver_data(struct platform_device *pdev)
|
||||
{
|
||||
const struct platform_device_id *id;
|
||||
@ -342,18 +330,18 @@ atmel_pwm_get_driver_data(struct platform_device *pdev)
|
||||
|
||||
id = platform_get_device_id(pdev);
|
||||
|
||||
return (struct atmel_pwm_data *)id->driver_data;
|
||||
return (struct atmel_pwm_registers *)id->driver_data;
|
||||
}
|
||||
|
||||
static int atmel_pwm_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct atmel_pwm_data *data;
|
||||
const struct atmel_pwm_registers *regs;
|
||||
struct atmel_pwm_chip *atmel_pwm;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
data = atmel_pwm_get_driver_data(pdev);
|
||||
if (!data)
|
||||
regs = atmel_pwm_get_driver_data(pdev);
|
||||
if (!regs)
|
||||
return -ENODEV;
|
||||
|
||||
atmel_pwm = devm_kzalloc(&pdev->dev, sizeof(*atmel_pwm), GFP_KERNEL);
|
||||
@ -385,7 +373,7 @@ static int atmel_pwm_probe(struct platform_device *pdev)
|
||||
|
||||
atmel_pwm->chip.base = -1;
|
||||
atmel_pwm->chip.npwm = 4;
|
||||
atmel_pwm->config = data->config;
|
||||
atmel_pwm->regs = regs;
|
||||
atmel_pwm->updated_pwms = 0;
|
||||
mutex_init(&atmel_pwm->isr_lock);
|
||||
|
||||
|
219
drivers/pwm/pwm-mediatek.c
Normal file
219
drivers/pwm/pwm-mediatek.c
Normal file
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Mediatek Pulse Width Modulator driver
|
||||
*
|
||||
* Copyright (C) 2015 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/* PWM registers and bits definitions */
|
||||
#define PWMCON 0x00
|
||||
#define PWMHDUR 0x04
|
||||
#define PWMLDUR 0x08
|
||||
#define PWMGDUR 0x0c
|
||||
#define PWMWAVENUM 0x28
|
||||
#define PWMDWIDTH 0x2c
|
||||
#define PWMTHRES 0x30
|
||||
|
||||
enum {
|
||||
MTK_CLK_MAIN = 0,
|
||||
MTK_CLK_TOP,
|
||||
MTK_CLK_PWM1,
|
||||
MTK_CLK_PWM2,
|
||||
MTK_CLK_PWM3,
|
||||
MTK_CLK_PWM4,
|
||||
MTK_CLK_PWM5,
|
||||
MTK_CLK_MAX,
|
||||
};
|
||||
|
||||
static const char * const mtk_pwm_clk_name[] = {
|
||||
"main", "top", "pwm1", "pwm2", "pwm3", "pwm4", "pwm5"
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mtk_pwm_chip - struct representing PWM chip
|
||||
* @chip: linux PWM chip representation
|
||||
* @regs: base address of PWM chip
|
||||
* @clks: list of clocks
|
||||
*/
|
||||
struct mtk_pwm_chip {
|
||||
struct pwm_chip chip;
|
||||
void __iomem *regs;
|
||||
struct clk *clks[MTK_CLK_MAX];
|
||||
};
|
||||
|
||||
static inline struct mtk_pwm_chip *to_mtk_pwm_chip(struct pwm_chip *chip)
|
||||
{
|
||||
return container_of(chip, struct mtk_pwm_chip, chip);
|
||||
}
|
||||
|
||||
static inline u32 mtk_pwm_readl(struct mtk_pwm_chip *chip, unsigned int num,
|
||||
unsigned int offset)
|
||||
{
|
||||
return readl(chip->regs + 0x10 + (num * 0x40) + offset);
|
||||
}
|
||||
|
||||
static inline void mtk_pwm_writel(struct mtk_pwm_chip *chip,
|
||||
unsigned int num, unsigned int offset,
|
||||
u32 value)
|
||||
{
|
||||
writel(value, chip->regs + 0x10 + (num * 0x40) + offset);
|
||||
}
|
||||
|
||||
static int mtk_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
int duty_ns, int period_ns)
|
||||
{
|
||||
struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip);
|
||||
struct clk *clk = pc->clks[MTK_CLK_PWM1 + pwm->hwpwm];
|
||||
u32 resolution, clkdiv = 0;
|
||||
|
||||
resolution = NSEC_PER_SEC / clk_get_rate(clk);
|
||||
|
||||
while (period_ns / resolution > 8191) {
|
||||
resolution *= 2;
|
||||
clkdiv++;
|
||||
}
|
||||
|
||||
if (clkdiv > 7)
|
||||
return -EINVAL;
|
||||
|
||||
mtk_pwm_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | BIT(3) | clkdiv);
|
||||
mtk_pwm_writel(pc, pwm->hwpwm, PWMDWIDTH, period_ns / resolution);
|
||||
mtk_pwm_writel(pc, pwm->hwpwm, PWMTHRES, duty_ns / resolution);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip);
|
||||
u32 value;
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare(pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
value = readl(pc->regs);
|
||||
value |= BIT(pwm->hwpwm);
|
||||
writel(value, pc->regs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mtk_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct mtk_pwm_chip *pc = to_mtk_pwm_chip(chip);
|
||||
u32 value;
|
||||
|
||||
value = readl(pc->regs);
|
||||
value &= ~BIT(pwm->hwpwm);
|
||||
writel(value, pc->regs);
|
||||
|
||||
clk_unprepare(pc->clks[MTK_CLK_PWM1 + pwm->hwpwm]);
|
||||
}
|
||||
|
||||
static const struct pwm_ops mtk_pwm_ops = {
|
||||
.config = mtk_pwm_config,
|
||||
.enable = mtk_pwm_enable,
|
||||
.disable = mtk_pwm_disable,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int mtk_pwm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mtk_pwm_chip *pc;
|
||||
struct resource *res;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
|
||||
if (!pc)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
pc->regs = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(pc->regs))
|
||||
return PTR_ERR(pc->regs);
|
||||
|
||||
for (i = 0; i < MTK_CLK_MAX; i++) {
|
||||
pc->clks[i] = devm_clk_get(&pdev->dev, mtk_pwm_clk_name[i]);
|
||||
if (IS_ERR(pc->clks[i]))
|
||||
return PTR_ERR(pc->clks[i]);
|
||||
}
|
||||
|
||||
ret = clk_prepare(pc->clks[MTK_CLK_TOP]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare(pc->clks[MTK_CLK_MAIN]);
|
||||
if (ret < 0)
|
||||
goto disable_clk_top;
|
||||
|
||||
platform_set_drvdata(pdev, pc);
|
||||
|
||||
pc->chip.dev = &pdev->dev;
|
||||
pc->chip.ops = &mtk_pwm_ops;
|
||||
pc->chip.base = -1;
|
||||
pc->chip.npwm = 5;
|
||||
|
||||
ret = pwmchip_add(&pc->chip);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
|
||||
goto disable_clk_main;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
disable_clk_main:
|
||||
clk_unprepare(pc->clks[MTK_CLK_MAIN]);
|
||||
disable_clk_top:
|
||||
clk_unprepare(pc->clks[MTK_CLK_TOP]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_pwm_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mtk_pwm_chip *pc = platform_get_drvdata(pdev);
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < pc->chip.npwm; i++)
|
||||
pwm_disable(&pc->chip.pwms[i]);
|
||||
|
||||
return pwmchip_remove(&pc->chip);
|
||||
}
|
||||
|
||||
static const struct of_device_id mtk_pwm_of_match[] = {
|
||||
{ .compatible = "mediatek,mt7623-pwm" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mtk_pwm_of_match);
|
||||
|
||||
static struct platform_driver mtk_pwm_driver = {
|
||||
.driver = {
|
||||
.name = "mtk-pwm",
|
||||
.of_match_table = mtk_pwm_of_match,
|
||||
},
|
||||
.probe = mtk_pwm_probe,
|
||||
.remove = mtk_pwm_remove,
|
||||
};
|
||||
module_platform_driver(mtk_pwm_driver);
|
||||
|
||||
MODULE_AUTHOR("John Crispin <blogic@openwrt.org>");
|
||||
MODULE_ALIAS("platform:mtk-pwm");
|
||||
MODULE_LICENSE("GPL");
|
@ -30,6 +30,7 @@
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
/*
|
||||
* Because the PCA9685 has only one prescaler per chip, changing the period of
|
||||
@ -79,7 +80,6 @@
|
||||
struct pca9685 {
|
||||
struct pwm_chip chip;
|
||||
struct regmap *regmap;
|
||||
int active_cnt;
|
||||
int duty_ns;
|
||||
int period_ns;
|
||||
#if IS_ENABLED(CONFIG_GPIOLIB)
|
||||
@ -111,20 +111,10 @@ static int pca9685_pwm_gpio_request(struct gpio_chip *gpio, unsigned int offset)
|
||||
pwm_set_chip_data(pwm, (void *)1);
|
||||
|
||||
mutex_unlock(&pca->lock);
|
||||
pm_runtime_get_sync(pca->chip.dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pca9685_pwm_gpio_free(struct gpio_chip *gpio, unsigned int offset)
|
||||
{
|
||||
struct pca9685 *pca = gpiochip_get_data(gpio);
|
||||
struct pwm_device *pwm;
|
||||
|
||||
mutex_lock(&pca->lock);
|
||||
pwm = &pca->chip.pwms[offset];
|
||||
pwm_set_chip_data(pwm, NULL);
|
||||
mutex_unlock(&pca->lock);
|
||||
}
|
||||
|
||||
static bool pca9685_pwm_is_gpio(struct pca9685 *pca, struct pwm_device *pwm)
|
||||
{
|
||||
bool is_gpio = false;
|
||||
@ -177,6 +167,19 @@ static void pca9685_pwm_gpio_set(struct gpio_chip *gpio, unsigned int offset,
|
||||
regmap_write(pca->regmap, LED_N_ON_H(pwm->hwpwm), on);
|
||||
}
|
||||
|
||||
static void pca9685_pwm_gpio_free(struct gpio_chip *gpio, unsigned int offset)
|
||||
{
|
||||
struct pca9685 *pca = gpiochip_get_data(gpio);
|
||||
struct pwm_device *pwm;
|
||||
|
||||
pca9685_pwm_gpio_set(gpio, offset, 0);
|
||||
pm_runtime_put(pca->chip.dev);
|
||||
mutex_lock(&pca->lock);
|
||||
pwm = &pca->chip.pwms[offset];
|
||||
pwm_set_chip_data(pwm, NULL);
|
||||
mutex_unlock(&pca->lock);
|
||||
}
|
||||
|
||||
static int pca9685_pwm_gpio_get_direction(struct gpio_chip *chip,
|
||||
unsigned int offset)
|
||||
{
|
||||
@ -238,6 +241,16 @@ static inline int pca9685_pwm_gpio_probe(struct pca9685 *pca)
|
||||
}
|
||||
#endif
|
||||
|
||||
static void pca9685_set_sleep_mode(struct pca9685 *pca, int sleep)
|
||||
{
|
||||
regmap_update_bits(pca->regmap, PCA9685_MODE1,
|
||||
MODE1_SLEEP, sleep ? MODE1_SLEEP : 0);
|
||||
if (!sleep) {
|
||||
/* Wait 500us for the oscillator to be back up */
|
||||
udelay(500);
|
||||
}
|
||||
}
|
||||
|
||||
static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
int duty_ns, int period_ns)
|
||||
{
|
||||
@ -252,19 +265,20 @@ static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
|
||||
if (prescale >= PCA9685_PRESCALE_MIN &&
|
||||
prescale <= PCA9685_PRESCALE_MAX) {
|
||||
/*
|
||||
* putting the chip briefly into SLEEP mode
|
||||
* at this point won't interfere with the
|
||||
* pm_runtime framework, because the pm_runtime
|
||||
* state is guaranteed active here.
|
||||
*/
|
||||
/* Put chip into sleep mode */
|
||||
regmap_update_bits(pca->regmap, PCA9685_MODE1,
|
||||
MODE1_SLEEP, MODE1_SLEEP);
|
||||
pca9685_set_sleep_mode(pca, 1);
|
||||
|
||||
/* Change the chip-wide output frequency */
|
||||
regmap_write(pca->regmap, PCA9685_PRESCALE, prescale);
|
||||
|
||||
/* Wake the chip up */
|
||||
regmap_update_bits(pca->regmap, PCA9685_MODE1,
|
||||
MODE1_SLEEP, 0x0);
|
||||
|
||||
/* Wait 500us for the oscillator to be back up */
|
||||
udelay(500);
|
||||
pca9685_set_sleep_mode(pca, 0);
|
||||
|
||||
pca->period_ns = period_ns;
|
||||
} else {
|
||||
@ -406,21 +420,15 @@ static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
|
||||
if (pca9685_pwm_is_gpio(pca, pwm))
|
||||
return -EBUSY;
|
||||
|
||||
if (pca->active_cnt++ == 0)
|
||||
return regmap_update_bits(pca->regmap, PCA9685_MODE1,
|
||||
MODE1_SLEEP, 0x0);
|
||||
pm_runtime_get_sync(chip->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct pca9685 *pca = to_pca(chip);
|
||||
|
||||
if (--pca->active_cnt == 0)
|
||||
regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP,
|
||||
MODE1_SLEEP);
|
||||
pca9685_pwm_disable(chip, pwm);
|
||||
pm_runtime_put(chip->dev);
|
||||
}
|
||||
|
||||
static const struct pwm_ops pca9685_pwm_ops = {
|
||||
@ -492,22 +500,54 @@ static int pca9685_pwm_probe(struct i2c_client *client,
|
||||
return ret;
|
||||
|
||||
ret = pca9685_pwm_gpio_probe(pca);
|
||||
if (ret < 0)
|
||||
if (ret < 0) {
|
||||
pwmchip_remove(&pca->chip);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
/* the chip comes out of power-up in the active state */
|
||||
pm_runtime_set_active(&client->dev);
|
||||
/*
|
||||
* enable will put the chip into suspend, which is what we
|
||||
* want as all outputs are disabled at this point
|
||||
*/
|
||||
pm_runtime_enable(&client->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pca9685_pwm_remove(struct i2c_client *client)
|
||||
{
|
||||
struct pca9685 *pca = i2c_get_clientdata(client);
|
||||
int ret;
|
||||
|
||||
regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP,
|
||||
MODE1_SLEEP);
|
||||
|
||||
return pwmchip_remove(&pca->chip);
|
||||
ret = pwmchip_remove(&pca->chip);
|
||||
if (ret)
|
||||
return ret;
|
||||
pm_runtime_disable(&client->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int pca9685_pwm_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct pca9685 *pca = i2c_get_clientdata(client);
|
||||
|
||||
pca9685_set_sleep_mode(pca, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pca9685_pwm_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct pca9685 *pca = i2c_get_clientdata(client);
|
||||
|
||||
pca9685_set_sleep_mode(pca, 0);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id pca9685_id[] = {
|
||||
{ "pca9685", 0 },
|
||||
{ /* sentinel */ },
|
||||
@ -530,11 +570,17 @@ static const struct of_device_id pca9685_dt_ids[] = {
|
||||
MODULE_DEVICE_TABLE(of, pca9685_dt_ids);
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops pca9685_pwm_pm = {
|
||||
SET_RUNTIME_PM_OPS(pca9685_pwm_runtime_suspend,
|
||||
pca9685_pwm_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static struct i2c_driver pca9685_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "pca9685-pwm",
|
||||
.acpi_match_table = ACPI_PTR(pca9685_acpi_ids),
|
||||
.of_match_table = of_match_ptr(pca9685_dt_ids),
|
||||
.pm = &pca9685_pwm_pm,
|
||||
},
|
||||
.probe = pca9685_pwm_probe,
|
||||
.remove = pca9685_pwm_remove,
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
@ -49,6 +50,8 @@ struct tegra_pwm_chip {
|
||||
struct clk *clk;
|
||||
struct reset_control*rst;
|
||||
|
||||
unsigned long clk_rate;
|
||||
|
||||
void __iomem *regs;
|
||||
|
||||
const struct tegra_pwm_soc *soc;
|
||||
@ -74,8 +77,8 @@ 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 = duty_ns;
|
||||
unsigned long rate, hz;
|
||||
unsigned long long c = duty_ns, hz;
|
||||
unsigned long rate;
|
||||
u32 val = 0;
|
||||
int err;
|
||||
|
||||
@ -85,8 +88,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
* nearest integer during division.
|
||||
*/
|
||||
c *= (1 << PWM_DUTY_WIDTH);
|
||||
c += period_ns / 2;
|
||||
do_div(c, period_ns);
|
||||
c = DIV_ROUND_CLOSEST_ULL(c, period_ns);
|
||||
|
||||
val = (u32)c << PWM_DUTY_SHIFT;
|
||||
|
||||
@ -94,10 +96,11 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
* Compute the prescaler value for which (1 << PWM_DUTY_WIDTH)
|
||||
* cycles at the PWM clock rate will take period_ns nanoseconds.
|
||||
*/
|
||||
rate = clk_get_rate(pc->clk) >> PWM_DUTY_WIDTH;
|
||||
hz = NSEC_PER_SEC / period_ns;
|
||||
rate = pc->clk_rate >> PWM_DUTY_WIDTH;
|
||||
|
||||
rate = (rate + (hz / 2)) / hz;
|
||||
/* Consider precision in PWM_SCALE_WIDTH rate calculation */
|
||||
hz = DIV_ROUND_CLOSEST_ULL(100ULL * NSEC_PER_SEC, period_ns);
|
||||
rate = DIV_ROUND_CLOSEST_ULL(100ULL * rate, hz);
|
||||
|
||||
/*
|
||||
* Since the actual PWM divider is the register's frequency divider
|
||||
@ -198,6 +201,9 @@ static int tegra_pwm_probe(struct platform_device *pdev)
|
||||
if (IS_ERR(pwm->clk))
|
||||
return PTR_ERR(pwm->clk);
|
||||
|
||||
/* Read PWM clock rate from source */
|
||||
pwm->clk_rate = clk_get_rate(pwm->clk);
|
||||
|
||||
pwm->rst = devm_reset_control_get(&pdev->dev, "pwm");
|
||||
if (IS_ERR(pwm->rst)) {
|
||||
ret = PTR_ERR(pwm->rst);
|
||||
@ -253,6 +259,18 @@ static int tegra_pwm_remove(struct platform_device *pdev)
|
||||
return pwmchip_remove(&pc->chip);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int tegra_pwm_suspend(struct device *dev)
|
||||
{
|
||||
return pinctrl_pm_select_sleep_state(dev);
|
||||
}
|
||||
|
||||
static int tegra_pwm_resume(struct device *dev)
|
||||
{
|
||||
return pinctrl_pm_select_default_state(dev);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct tegra_pwm_soc tegra20_pwm_soc = {
|
||||
.num_channels = 4,
|
||||
};
|
||||
@ -269,10 +287,15 @@ static const struct of_device_id tegra_pwm_of_match[] = {
|
||||
|
||||
MODULE_DEVICE_TABLE(of, tegra_pwm_of_match);
|
||||
|
||||
static const struct dev_pm_ops tegra_pwm_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(tegra_pwm_suspend, tegra_pwm_resume)
|
||||
};
|
||||
|
||||
static struct platform_driver tegra_pwm_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-pwm",
|
||||
.of_match_table = tegra_pwm_of_match,
|
||||
.pm = &tegra_pwm_pm_ops,
|
||||
},
|
||||
.probe = tegra_pwm_probe,
|
||||
.remove = tegra_pwm_remove,
|
||||
|
Loading…
Reference in New Issue
Block a user