Commit c8fff3ed authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'pwm/for-4.4-rc1' of...

Merge tag 'pwm/for-4.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm

Pull pwm updates from Thierry Reding:
 "This round contains a couple of new drivers for the Marvell Berlin
  family of SoCs, various SoCs from Renesas and Broadcom as well as the
  backlight PWM present on MediaTek SoCs.

  Further existing drivers are extended to support a wider range of
  hardware.

  The remaining patches are minor fixes and cleanups across the board.

  Note that one of the patches included in this pull request is against
  arch/unicore32.  I've included it here because I couldn't get a
  response from Guan Xuetao and I consider the change low-risk.
  Equivalent patches have been merged and tested in Samsung and PXA
  trees.  The goal is to finally get rid of legacy code paths that have
  repeatedly been causing headaches"

* tag 'pwm/for-4.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (24 commits)
  pwm: sunxi: Fix whitespace issue
  pwm: sysfs: Make use of the DEVICE_ATTR_[RW][WO] macro's
  pwm: sysfs: Remove unnecessary temporary variable
  unicore32: nb0916: Use PWM lookup table
  pwm: pwm-rcar: Revise the device tree binding document about compatible
  pwm: Return -ENODEV if no PWM lookup match is found
  pwm: sun4i: Add support for PWM controller on sun5i SoCs
  pwm: Set enable state properly on failed call to enable
  pwm: lpss: Add support for runtime PM
  pwm: lpss: Add more Intel Broxton IDs
  pwm: lpss: Support all four PWMs on Intel Broxton
  pwm: lpss: Add support for multiple PWMs
  pwm-pca9685: enable ACPI device found on Galileo Gen2
  pwm: Add MediaTek display PWM driver support
  dt-bindings: pwm: Add MediaTek display PWM bindings
  pwm: tipwmss: Enable on TI DRA7x and AM437x
  pwm: atmel-hlcdc: add sama5d2 SoC support.
  pwm: Add Broadcom BCM7038 PWM controller support
  Documentation: dt: add Broadcom BCM7038 PWM controller binding
  pwm: Add support for R-Car PWM Timer
  ...
parents baf51c43 5dcd7b42
Broadcom BCM7038 PWM controller (BCM7xxx Set Top Box PWM controller)
Required properties:
- compatible: must be "brcm,bcm7038-pwm"
- reg: physical base address and length for this controller
- #pwm-cells: should be 2. See pwm.txt in this directory for a description
of the cells format
- clocks: a phandle to the reference clock for this block which is fed through
its internal variable clock frequency generator
Example:
pwm: pwm@f0408000 {
compatible = "brcm,bcm7038-pwm";
reg = <0xf0408000 0x28>;
#pwm-cells = <2>;
clocks = <&upg_fixed>;
};
Berlin PWM controller
Required properties:
- compatible: should be "marvell,berlin-pwm"
- reg: physical base address and length of the controller's registers
- clocks: phandle to the input clock
- #pwm-cells: should be 3. See pwm.txt in this directory for a description of
the cells format.
Example:
pwm: pwm@f7f20000 {
compatible = "marvell,berlin-pwm";
reg = <0xf7f20000 0x40>;
clocks = <&chip_clk CLKID_CFG>;
#pwm-cells = <3>;
}
MediaTek display PWM controller
Required properties:
- compatible: should be "mediatek,<name>-disp-pwm":
- "mediatek,mt8173-disp-pwm": found on mt8173 SoC.
- "mediatek,mt6595-disp-pwm": found on mt6595 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:
- "main": clock used to generate PWM signals.
- "mm": sync signals from the modules of mmsys.
- 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@1401e000 {
compatible = "mediatek,mt8173-disp-pwm",
"mediatek,mt6595-disp-pwm";
reg = <0 0x1401e000 0 0x1000>;
#pwm-cells = <2>;
clocks = <&mmsys CLK_MM_DISP_PWM026M>,
<&mmsys CLK_MM_DISP_PWM0MM>;
clock-names = "main", "mm";
pinctrl-names = "default";
pinctrl-0 = <&disp_pwm0_pins>;
};
backlight_lcd: backlight_lcd {
compatible = "pwm-backlight";
pwms = <&pwm0 0 1000000>;
brightness-levels = <
0 16 32 48 64 80 96 112
128 144 160 176 192 208 224 240
255
>;
default-brightness-level = <9>;
power-supply = <&mt6397_vio18_reg>;
enable-gpios = <&pio 95 GPIO_ACTIVE_HIGH>;
};
......@@ -3,6 +3,8 @@ Allwinner sun4i and sun7i SoC PWM controller
Required properties:
- compatible: should be one of:
- "allwinner,sun4i-a10-pwm"
- "allwinner,sun5i-a10s-pwm"
- "allwinner,sun5i-a13-pwm"
- "allwinner,sun7i-a20-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
......
* Renesas R-Car PWM Timer Controller
Required Properties:
- compatible: should be "renesas,pwm-rcar" and one of the following.
- "renesas,pwm-r8a7778": for R-Car M1A
- "renesas,pwm-r8a7779": for R-Car H1
- "renesas,pwm-r8a7790": for R-Car H2
- "renesas,pwm-r8a7791": for R-Car M2-W
- "renesas,pwm-r8a7794": for R-Car E2
- reg: base address and length of the registers block for the PWM.
- #pwm-cells: should be 2. See pwm.txt in this directory for a description of
the cells format.
- clocks: clock phandle and specifier pair.
- pinctrl-0: phandle, referring to a default pin configuration node.
- pinctrl-names: Set to "default".
Example: R8A7790 (R-Car H2) PWM Timer node
pwm0: pwm@e6e30000 {
compatible = "renesas,pwm-r8a7790", "renesas,pwm-rcar";
reg = <0 0xe6e30000 0 0x8>;
#pwm-cells = <2>;
clocks = <&mstp5_clks R8A7790_CLK_PWM>;
pinctrl-0 = <&pwm0_pins>;
pinctrl-names = "default";
};
......@@ -19,6 +19,7 @@
#include <linux/reboot.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/pwm.h>
#include <linux/pwm_backlight.h>
#include <linux/gpio.h>
#include <linux/gpio_keys.h>
......@@ -49,11 +50,14 @@ static struct resource puv3_i2c_resources[] = {
}
};
static struct pwm_lookup nb0916_pwm_lookup[] = {
PWM_LOOKUP("PKUnity-v3-PWM", 0, "pwm-backlight", NULL, 70 * 1024,
PWM_POLARITY_NORMAL),
};
static struct platform_pwm_backlight_data nb0916_backlight_data = {
.pwm_id = 0,
.max_brightness = 100,
.dft_brightness = 100,
.pwm_period_ns = 70 * 1024,
.enable_gpio = -1,
};
......@@ -112,6 +116,8 @@ int __init mach_nb0916_init(void)
platform_device_register_simple("PKUnity-v3-I2C", -1,
puv3_i2c_resources, ARRAY_SIZE(puv3_i2c_resources));
pwm_add_table(nb0916_pwm_lookup, ARRAY_SIZE(nb0916_pwm_lookup));
platform_device_register_data(NULL, "pwm-backlight", -1,
&nb0916_backlight_data, sizeof(nb0916_backlight_data));
......
......@@ -92,6 +92,15 @@ config PWM_BCM2835
To compile this driver as a module, choose M here: the module
will be called pwm-bcm2835.
config PWM_BERLIN
tristate "Marvell Berlin PWM support"
depends on ARCH_BERLIN
help
PWM framework driver for Marvell Berlin SoCs.
To compile this driver as a module, choose M here: the module
will be called pwm-berlin.
config PWM_BFIN
tristate "Blackfin PWM support"
depends on BFIN_GPTIMERS
......@@ -101,6 +110,16 @@ config PWM_BFIN
To compile this driver as a module, choose M here: the module
will be called pwm-bfin.
config PWM_BRCMSTB
tristate "Broadcom STB PWM support"
depends on ARCH_BRCMSTB || BMIPS_GENERIC
help
Generic PWM framework driver for the Broadcom Set-top-Box
SoCs (BCM7xxx).
To compile this driver as a module, choose M Here: the module
will be called pwm-brcmstb.c.
config PWM_CLPS711X
tristate "CLPS711X PWM support"
depends on ARCH_CLPS711X || COMPILE_TEST
......@@ -230,6 +249,17 @@ config PWM_LPSS_PLATFORM
To compile this driver as a module, choose M here: the module
will be called pwm-lpss-platform.
config PWM_MTK_DISP
tristate "MediaTek display PWM driver"
depends on ARCH_MEDIATEK || COMPILE_TEST
depends on HAS_IOMEM
help
Generic PWM framework driver for MediaTek disp-pwm device.
The PWM is used to control the backlight brightness for display.
To compile this driver as a module, choose M here: the module
will be called pwm-mtk-disp.
config PWM_MXS
tristate "Freescale MXS PWM support"
depends on ARCH_MXS && OF
......@@ -242,7 +272,7 @@ config PWM_MXS
config PWM_PCA9685
tristate "NXP PCA9685 PWM driver"
depends on OF && I2C
depends on I2C
select REGMAP_I2C
help
Generic PWM framework driver for NXP PCA9685 LED controller.
......@@ -268,6 +298,17 @@ config PWM_PXA
To compile this driver as a module, choose M here: the module
will be called pwm-pxa.
config PWM_RCAR
tristate "Renesas R-Car PWM support"
depends on ARCH_RCAR_GEN1 || ARCH_RCAR_GEN2 || COMPILE_TEST
depends on HAS_IOMEM
help
This driver exposes the PWM Timer controller found in Renesas
R-Car chips through the PWM API.
To compile this driver as a module, choose M here: the module
will be called pwm-rcar.
config PWM_RENESAS_TPU
tristate "Renesas TPU PWM support"
depends on ARCH_SHMOBILE || COMPILE_TEST
......@@ -338,7 +379,7 @@ config PWM_TEGRA
config PWM_TIECAP
tristate "ECAP PWM support"
depends on SOC_AM33XX || ARCH_DAVINCI_DA8XX
depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX
help
PWM driver support for the ECAP APWM controller found on AM33XX
TI SOC
......@@ -348,7 +389,7 @@ config PWM_TIECAP
config PWM_TIEHRPWM
tristate "EHRPWM PWM support"
depends on SOC_AM33XX || ARCH_DAVINCI_DA8XX
depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX
help
PWM driver support for the EHRPWM controller found on AM33XX
TI SOC
......@@ -358,7 +399,7 @@ config PWM_TIEHRPWM
config PWM_TIPWMSS
bool
default y if SOC_AM33XX && (PWM_TIECAP || PWM_TIEHRPWM)
default y if (ARCH_OMAP2PLUS) && (PWM_TIECAP || PWM_TIEHRPWM)
help
PWM Subsystem driver support for AM33xx SOC.
......
......@@ -6,7 +6,9 @@ obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o
obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o
obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o
obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o
obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o
obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
obj-$(CONFIG_PWM_CRC) += pwm-crc.o
obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
......@@ -20,10 +22,12 @@ obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o
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_MTK_DISP) += pwm-mtk-disp.o
obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o
obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o
obj-$(CONFIG_PWM_PXA) += pwm-pxa.o
obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
......
......@@ -269,6 +269,7 @@ int pwmchip_add_with_polarity(struct pwm_chip *chip,
pwm->pwm = chip->base + i;
pwm->hwpwm = i;
pwm->polarity = polarity;
mutex_init(&pwm->lock);
radix_tree_insert(&pwm_tree, pwm->pwm, pwm);
}
......@@ -473,16 +474,22 @@ int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity)
if (!pwm->chip->ops->set_polarity)
return -ENOSYS;
if (pwm_is_enabled(pwm))
return -EBUSY;
mutex_lock(&pwm->lock);
if (pwm_is_enabled(pwm)) {
err = -EBUSY;
goto unlock;
}
err = pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity);
if (err)
return err;
goto unlock;
pwm->polarity = polarity;
return 0;
unlock:
mutex_unlock(&pwm->lock);
return err;
}
EXPORT_SYMBOL_GPL(pwm_set_polarity);
......@@ -494,10 +501,22 @@ EXPORT_SYMBOL_GPL(pwm_set_polarity);
*/
int pwm_enable(struct pwm_device *pwm)
{
if (pwm && !test_and_set_bit(PWMF_ENABLED, &pwm->flags))
return pwm->chip->ops->enable(pwm->chip, pwm);
int err = 0;
return pwm ? 0 : -EINVAL;
if (!pwm)
return -EINVAL;
mutex_lock(&pwm->lock);
if (!test_and_set_bit(PWMF_ENABLED, &pwm->flags)) {
err = pwm->chip->ops->enable(pwm->chip, pwm);
if (err)
clear_bit(PWMF_ENABLED, &pwm->flags);
}
mutex_unlock(&pwm->lock);
return err;
}
EXPORT_SYMBOL_GPL(pwm_enable);
......@@ -719,8 +738,10 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
}
}
if (!chosen)
if (!chosen) {
pwm = ERR_PTR(-ENODEV);
goto out;
}
chip = pwmchip_find_by_name(chosen->provider);
if (!chip)
......
......@@ -226,6 +226,9 @@ static const struct of_device_id atmel_hlcdc_dt_ids[] = {
.compatible = "atmel,at91sam9x5-hlcdc",
.data = &atmel_hlcdc_pwm_at91sam9x5_errata,
},
{
.compatible = "atmel,sama5d2-hlcdc",
},
{
.compatible = "atmel,sama5d3-hlcdc",
.data = &atmel_hlcdc_pwm_sama5d3_errata,
......@@ -236,6 +239,7 @@ static const struct of_device_id atmel_hlcdc_dt_ids[] = {
},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, atmel_hlcdc_dt_ids);
static int atmel_hlcdc_pwm_probe(struct platform_device *pdev)
{
......
/*
* Marvell Berlin PWM driver
*
* Copyright (C) 2015 Marvell Technology Group Ltd.
*
* Author: Antoine Tenart <antoine.tenart@free-electrons.com>
*
* 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/clk.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#define BERLIN_PWM_EN 0x0
#define BERLIN_PWM_ENABLE BIT(0)
#define BERLIN_PWM_CONTROL 0x4
#define BERLIN_PWM_PRESCALE_MASK 0x7
#define BERLIN_PWM_PRESCALE_MAX 4096
#define BERLIN_PWM_INVERT_POLARITY BIT(3)
#define BERLIN_PWM_DUTY 0x8
#define BERLIN_PWM_TCNT 0xc
#define BERLIN_PWM_MAX_TCNT 65535
struct berlin_pwm_chip {
struct pwm_chip chip;
struct clk *clk;
void __iomem *base;
};
static inline struct berlin_pwm_chip *to_berlin_pwm_chip(struct pwm_chip *chip)
{
return container_of(chip, struct berlin_pwm_chip, chip);
}
static const u32 prescaler_table[] = {
1, 4, 8, 16, 64, 256, 1024, 4096
};
static inline u32 berlin_pwm_readl(struct berlin_pwm_chip *chip,
unsigned int channel, unsigned long offset)
{
return readl_relaxed(chip->base + channel * 0x10 + offset);
}
static inline void berlin_pwm_writel(struct berlin_pwm_chip *chip,
unsigned int channel, u32 value,
unsigned long offset)
{
writel_relaxed(value, chip->base + channel * 0x10 + offset);
}
static int berlin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm_dev,
int duty_ns, int period_ns)
{
struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip);
unsigned int prescale;
u32 value, duty, period;
u64 cycles, tmp;
cycles = clk_get_rate(pwm->clk);
cycles *= period_ns;
do_div(cycles, NSEC_PER_SEC);
for (prescale = 0; prescale < ARRAY_SIZE(prescaler_table); prescale++) {
tmp = cycles;
do_div(tmp, prescaler_table[prescale]);
if (tmp <= BERLIN_PWM_MAX_TCNT)
break;
}
if (tmp > BERLIN_PWM_MAX_TCNT)
return -ERANGE;
period = tmp;
cycles = tmp * duty_ns;
do_div(cycles, period_ns);
duty = cycles;
value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_CONTROL);
value &= ~BERLIN_PWM_PRESCALE_MASK;
value |= prescale;
berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_CONTROL);
berlin_pwm_writel(pwm, pwm_dev->hwpwm, duty, BERLIN_PWM_DUTY);
berlin_pwm_writel(pwm, pwm_dev->hwpwm, period, BERLIN_PWM_TCNT);
return 0;
}
static int berlin_pwm_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm_dev,
enum pwm_polarity polarity)
{
struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip);
u32 value;
value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_CONTROL);
if (polarity == PWM_POLARITY_NORMAL)
value &= ~BERLIN_PWM_INVERT_POLARITY;
else
value |= BERLIN_PWM_INVERT_POLARITY;
berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_CONTROL);
return 0;
}
static int berlin_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm_dev)
{
struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip);
u32 value;
value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_EN);
value |= BERLIN_PWM_ENABLE;
berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_EN);
return 0;
}
static void berlin_pwm_disable(struct pwm_chip *chip,
struct pwm_device *pwm_dev)
{
struct berlin_pwm_chip *pwm = to_berlin_pwm_chip(chip);
u32 value;
value = berlin_pwm_readl(pwm, pwm_dev->hwpwm, BERLIN_PWM_EN);
value &= ~BERLIN_PWM_ENABLE;
berlin_pwm_writel(pwm, pwm_dev->hwpwm, value, BERLIN_PWM_EN);
}
static const struct pwm_ops berlin_pwm_ops = {
.config = berlin_pwm_config,
.set_polarity = berlin_pwm_set_polarity,
.enable = berlin_pwm_enable,
.disable = berlin_pwm_disable,
.owner = THIS_MODULE,
};
static const struct of_device_id berlin_pwm_match[] = {
{ .compatible = "marvell,berlin-pwm" },
{ },
};
MODULE_DEVICE_TABLE(of, berlin_pwm_match);
static int berlin_pwm_probe(struct platform_device *pdev)
{
struct berlin_pwm_chip *pwm;
struct resource *res;
int ret;
pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
if (!pwm)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pwm->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(pwm->base))
return PTR_ERR(pwm->base);
pwm->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(pwm->clk))
return PTR_ERR(pwm->clk);
ret = clk_prepare_enable(pwm->clk);
if (ret)
return ret;
pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &berlin_pwm_ops;
pwm->chip.base = -1;
pwm->chip.npwm = 4;
pwm->chip.can_sleep = true;
pwm->chip.of_xlate = of_pwm_xlate_with_flags;
pwm->chip.of_pwm_n_cells = 3;
ret = pwmchip_add(&pwm->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
clk_disable_unprepare(pwm->clk);
return ret;
}
platform_set_drvdata(pdev, pwm);
return 0;
}
static int berlin_pwm_remove(struct platform_device *pdev)
{
struct berlin_pwm_chip *pwm = platform_get_drvdata(pdev);
int ret;
ret = pwmchip_remove(&pwm->chip);
clk_disable_unprepare(pwm->clk);
return ret;
}
static struct platform_driver berlin_pwm_driver = {
.probe = berlin_pwm_probe,
.remove = berlin_pwm_remove,
.driver = {
.name = "berlin-pwm",
.of_match_table = berlin_pwm_match,
},
};
module_platform_driver(berlin_pwm_driver);
MODULE_AUTHOR("Antoine Tenart <antoine.tenart@free-electrons.com>");
MODULE_DESCRIPTION("Marvell Berlin PWM driver");
MODULE_LICENSE("GPL v2");
/*
* Broadcom BCM7038 PWM driver
* Author: Florian Fainelli
*
* Copyright (C) 2015 Broadcom Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/clk.h>
#include <linux/export.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/spinlock.h>
#define PWM_CTRL 0x00
#define CTRL_START BIT(0)
#define CTRL_OEB BIT(1)
#define CTRL_FORCE_HIGH BIT(2)
#define CTRL_OPENDRAIN BIT(3)
#define CTRL_CHAN_OFFS 4
#define PWM_CTRL2 0x04
#define CTRL2_OUT_SELECT BIT(0)
#define PWM_CH_SIZE 0x8
#define PWM_CWORD_MSB(ch) (0x08 + ((ch) * PWM_CH_SIZE))
#define PWM_CWORD_LSB(ch) (0x0c + ((ch) * PWM_CH_SIZE))
/* Number of bits for the CWORD value */
#define CWORD_BIT_SIZE 16
/*
* Maximum control word value allowed when variable-frequency PWM is used as a
* clock for the constant-frequency PMW.
*/
#define CONST_VAR_F_MAX 32768
#define CONST_VAR_F_MIN 1
#define PWM_ON(ch) (0x18 + ((ch) * PWM_CH_SIZE))
#define PWM_ON_MIN 1
#define PWM_PERIOD(ch) (0x1c + ((ch) * PWM_CH_SIZE))
#define PWM_PERIOD_MIN 0
#define PWM_ON_PERIOD_MAX 0xff
struct brcmstb_pwm {
void __iomem *base;
spinlock_t lock;
struct clk *clk;
struct pwm_chip chip;
};
static inline u32 brcmstb_pwm_readl(struct brcmstb_pwm *p,
unsigned int offset)
{
if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
return __raw_readl(p->base + offset);
else
return readl_relaxed(p->base + offset);
}
static inline void brcmstb_pwm_writel(struct brcmstb_pwm *p, u32 value,
unsigned int offset)
{
if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
__raw_writel(value, p->base + offset);
else
writel_relaxed(value, p->base + offset);
}
static inline struct brcmstb_pwm *to_brcmstb_pwm(struct pwm_chip *chip)
{
return container_of(chip, struct brcmstb_pwm, chip);
}
/*
* Fv is derived from the variable frequency output. The variable frequency
* output is configured using this formula:
*
* W = cword, if cword < 2 ^ 15 else 16-bit 2's complement of cword
*
* Fv = W x 2 ^ -16 x 27Mhz (reference clock)
*
* The period is: (period + 1) / Fv and "on" time is on / (period + 1)
*
* The PWM core framework specifies that the "duty_ns" parameter is in fact the
* "on" time, so this translates directly into our HW programming here.
*/
static int brcmstb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct brcmstb_pwm *p = to_brcmstb_pwm(chip);
unsigned long pc, dc, cword = CONST_VAR_F_MAX;
unsigned int channel = pwm->hwpwm;
u32 value;
/*
* If asking for a duty_ns equal to period_ns, we need to substract
* the period value by 1 to make it shorter than the "on" time and
* produce a flat 100% duty cycle signal, and max out the "on" time
*/
if (duty_ns == period_ns) {
dc = PWM_ON_PERIOD_MAX;
pc = PWM_ON_PERIOD_MAX - 1;
goto done;
}
while (1) {
u64 rate, tmp;
/*
* Calculate the base rate from base frequency and current
* cword
*/
rate = (u64)clk_get_rate(p->clk) * (u64)cword;
do_div(rate, 1 << CWORD_BIT_SIZE);
tmp = period_ns * rate;
do_div(tmp, NSEC_PER_SEC);
pc = tmp;
tmp = (duty_ns + 1) * rate;
do_div(tmp, NSEC_PER_SEC);
dc = tmp;
/*
* We can be called with separate duty and period updates,
* so do not reject dc == 0 right away
*/
if (pc == PWM_PERIOD_MIN || (dc < PWM_ON_MIN && duty_ns))
return -EINVAL;
/* We converged on a calculation */
if (pc <= PWM_ON_PERIOD_MAX && dc <= PWM_ON_PERIOD_MAX)
break;
/*
* The cword needs to be a power of 2 for the variable
* frequency generator to output a 50% duty cycle variable
* frequency which is used as input clock to the fixed
* frequency generator.
*/
cword >>= 1;
/*
* Desired periods are too large, we do not have a divider
* for them
*/
if (cword < CONST_VAR_F_MIN)
return -EINVAL;
}
done:
/*
* Configure the defined "cword" value to have the variable frequency
* generator output a base frequency for the constant frequency
* generator to derive from.
*/
spin_lock(&p->lock);
brcmstb_pwm_writel(p, cword >> 8, PWM_CWORD_MSB(channel));
brcmstb_pwm_writel(p, cword & 0xff, PWM_CWORD_LSB(channel));
/* Select constant frequency signal output */
value = brcmstb_pwm_readl(p, PWM_CTRL2);
value |= CTRL2_OUT_SELECT << (channel * CTRL_CHAN_OFFS);
brcmstb_pwm_writel(p, value, PWM_CTRL2);
/* Configure on and period value */
brcmstb_pwm_writel(p, pc, PWM_PERIOD(channel));
brcmstb_pwm_writel(p, dc, PWM_ON(channel));
spin_unlock(&p->lock);
return 0;
}
static inline void brcmstb_pwm_enable_set(struct brcmstb_pwm *p,
unsigned int channel, bool enable)
{
unsigned int shift = channel * CTRL_CHAN_OFFS;
u32 value;
spin_lock(&p->lock);
value = brcmstb_pwm_readl(p, PWM_CTRL);
if (enable) {
value &= ~(CTRL_OEB << shift);
value |= (CTRL_START | CTRL_OPENDRAIN) << shift;
} else {
value &= ~((CTRL_START | CTRL_OPENDRAIN) << shift);
value |= CTRL_OEB << shift;
}
brcmstb_pwm_writel(p, value, PWM_CTRL);
spin_unlock(&p->lock);
}
static int brcmstb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct brcmstb_pwm *p = to_brcmstb_pwm(chip);
brcmstb_pwm_enable_set(p, pwm->hwpwm, true);
return 0;
}
static void brcmstb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct brcmstb_pwm *p = to_brcmstb_pwm(chip);
brcmstb_pwm_enable_set(p, pwm->hwpwm, false);
}
static const struct pwm_ops brcmstb_pwm_ops = {
.config = brcmstb_pwm_config,
.enable = brcmstb_pwm_enable,
.disable = brcmstb_pwm_disable,
.owner = THIS_MODULE,
};
static const struct of_device_id brcmstb_pwm_of_match[] = {
{ .compatible = "brcm,bcm7038-pwm", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, brcmstb_pwm_of_match);
static int brcmstb_pwm_probe(struct platform_device *pdev)
{
struct brcmstb_pwm *p;
struct resource *res;
int ret;
p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
if (!p)
return -ENOMEM;
spin_lock_init(&p->lock);
p->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(p->clk)) {
dev_err(&pdev->dev, "failed to obtain clock\n");
return PTR_ERR(p->clk);
}
ret = clk_prepare_enable(p->clk);
if (ret < 0) {
dev_err(&pdev->dev, "failed to enable clock: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, p);
p->chip.dev = &pdev->dev;
p->chip.ops = &brcmstb_pwm_ops;
p->chip.base = -1;
p->chip.npwm = 2;
p->chip.can_sleep = true;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
p->base = devm_ioremap_resource(&pdev->dev, res);
if (!p->base) {
ret = -ENOMEM;
goto out_clk;
}
ret = pwmchip_add(&p->chip);
if (ret) {
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
goto out_clk;
}
return 0;
out_clk:
clk_disable_unprepare(p->clk);
return ret;
}
static int brcmstb_pwm_remove(struct platform_device *pdev)
{
struct brcmstb_pwm *p = platform_get_drvdata(pdev);
int ret;
ret = pwmchip_remove(&p->chip);
clk_disable_unprepare(p->clk);
return ret;
}
#ifdef CONFIG_PM_SLEEP
static int brcmstb_pwm_suspend(struct device *dev)
{
struct brcmstb_pwm *p = dev_get_drvdata(dev);
clk_disable(p->clk);
return 0;
}
static int brcmstb_pwm_resume(struct device *dev)
{
struct brcmstb_pwm *p = dev_get_drvdata(dev);
clk_enable(p->clk);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(brcmstb_pwm_pm_ops, brcmstb_pwm_suspend,
brcmstb_pwm_resume);
static struct platform_driver brcmstb_pwm_driver = {
.probe = brcmstb_pwm_probe,
.remove = brcmstb_pwm_remove,
.driver = {
.name = "pwm-brcmstb",
.of_match_table = brcmstb_pwm_of_match,
.pm = &brcmstb_pwm_pm_ops,
},
};
module_platform_driver(brcmstb_pwm_driver);
MODULE_AUTHOR("Florian Fainelli <f.fainelli@gmail.com>");
MODULE_DESCRIPTION("Broadcom STB PWM driver");
MODULE_ALIAS("platform:pwm-brcmstb");
MODULE_LICENSE("GPL");
......@@ -13,6 +13,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include "pwm-lpss.h"
......@@ -33,6 +34,10 @@ static int pwm_lpss_probe_pci(struct pci_dev *pdev,
return PTR_ERR(lpwm);
pci_set_drvdata(pdev, lpwm);
pm_runtime_put(&pdev->dev);
pm_runtime_allow(&pdev->dev);
return 0;
}
......@@ -40,16 +45,41 @@ static void pwm_lpss_remove_pci(struct pci_dev *pdev)
{
struct pwm_lpss_chip *lpwm = pci_get_drvdata(pdev);
pm_runtime_forbid(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
pwm_lpss_remove(lpwm);
}
#ifdef CONFIG_PM
static int pwm_lpss_runtime_suspend_pci(struct device *dev)
{
/*
* The PCI core will handle transition to D3 automatically. We only
* need to provide runtime PM hooks for that to happen.
*/
return 0;
}
static int pwm_lpss_runtime_resume_pci(struct device *dev)
{
return 0;
}
#endif
static const struct dev_pm_ops pwm_lpss_pci_pm = {
SET_RUNTIME_PM_OPS(pwm_lpss_runtime_suspend_pci,
pwm_lpss_runtime_resume_pci, NULL)
};
static const struct pci_device_id pwm_lpss_pci_ids[] = {
{ PCI_VDEVICE(INTEL, 0x0ac8), (unsigned long)&pwm_lpss_bsw_info},
{ PCI_VDEVICE(INTEL, 0x0ac8), (unsigned long)&pwm_lpss_bxt_info},
{ PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&pwm_lpss_byt_info},
{ PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&pwm_lpss_byt_info},
{ PCI_VDEVICE(INTEL, 0x1ac8), (unsigned long)&pwm_lpss_bsw_info},
{ PCI_VDEVICE(INTEL, 0x1ac8), (unsigned long)&pwm_lpss_bxt_info},
{ PCI_VDEVICE(INTEL, 0x2288), (unsigned long)&pwm_lpss_bsw_info},
{ PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info},
{ PCI_VDEVICE(INTEL, 0x5ac8), (unsigned long)&pwm_lpss_bxt_info},
{ },
};
MODULE_DEVICE_TABLE(pci, pwm_lpss_pci_ids);
......@@ -59,6 +89,9 @@ static struct pci_driver pwm_lpss_driver_pci = {
.id_table = pwm_lpss_pci_ids,
.probe = pwm_lpss_probe_pci,
.remove = pwm_lpss_remove_pci,
.driver = {
.pm = &pwm_lpss_pci_pm,
},
};
module_pci_driver(pwm_lpss_driver_pci);
......
......@@ -14,6 +14,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include "pwm-lpss.h"
......@@ -36,6 +37,10 @@ static int pwm_lpss_probe_platform(struct platform_device *pdev)
return PTR_ERR(lpwm);
platform_set_drvdata(pdev, lpwm);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
return 0;
}
......@@ -43,12 +48,14 @@ static int pwm_lpss_remove_platform(struct platform_device *pdev)
{
struct pwm_lpss_chip *lpwm = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
return pwm_lpss_remove(lpwm);
}
static const struct acpi_device_id pwm_lpss_acpi_match[] = {
{ "80860F09", (unsigned long)&pwm_lpss_byt_info },
{ "80862288", (unsigned long)&pwm_lpss_bsw_info },
{ "80865AC8", (unsigned long)&pwm_lpss_bxt_info },
{ },
};
MODULE_DEVICE_TABLE(acpi, pwm_lpss_acpi_match);
......
......@@ -16,6 +16,7 @@
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include "pwm-lpss.h"
......@@ -29,6 +30,9 @@
#define PWM_LIMIT (0x8000 + PWM_DIVISION_CORRECTION)
#define NSECS_PER_SEC 1000000000UL
/* Size of each PWM register space if multiple */
#define PWM_SIZE 0x400
struct pwm_lpss_chip {
struct pwm_chip chip;
void __iomem *regs;
......@@ -37,21 +41,44 @@ struct pwm_lpss_chip {
/* BayTrail */
const struct pwm_lpss_boardinfo pwm_lpss_byt_info = {
.clk_rate = 25000000
.clk_rate = 25000000,
.npwm = 1,
};
EXPORT_SYMBOL_GPL(pwm_lpss_byt_info);
/* Braswell */
const struct pwm_lpss_boardinfo pwm_lpss_bsw_info = {
.clk_rate = 19200000
.clk_rate = 19200000,
.npwm = 1,
};
EXPORT_SYMBOL_GPL(pwm_lpss_bsw_info);
/* Broxton */
const struct pwm_lpss_boardinfo pwm_lpss_bxt_info = {
.clk_rate = 19200000,
.npwm = 4,
};
EXPORT_SYMBOL_GPL(pwm_lpss_bxt_info);
static inline struct pwm_lpss_chip *to_lpwm(struct pwm_chip *chip)
{
return container_of(chip, struct pwm_lpss_chip, chip);
}
static inline u32 pwm_lpss_read(const struct pwm_device *pwm)
{
struct pwm_lpss_chip *lpwm = to_lpwm(pwm->chip);
return readl(lpwm->regs + pwm->hwpwm * PWM_SIZE + PWM);
}
static inline void pwm_lpss_write(const struct pwm_device *pwm, u32 value)
{
struct pwm_lpss_chip *lpwm = to_lpwm(pwm->chip);
writel(value, lpwm->regs + pwm->hwpwm * PWM_SIZE + PWM);
}
static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
......@@ -79,38 +106,36 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm,
duty_ns = 1;
on_time_div = 255 - (255 * duty_ns / period_ns);
ctrl = readl(lpwm->regs + PWM);
pm_runtime_get_sync(chip->dev);
ctrl = pwm_lpss_read(pwm);
ctrl &= ~(PWM_BASE_UNIT_MASK | PWM_ON_TIME_DIV_MASK);
ctrl |= (u16) base_unit << PWM_BASE_UNIT_SHIFT;
ctrl |= on_time_div;
/* request PWM to update on next cycle */
ctrl |= PWM_SW_UPDATE;
writel(ctrl, lpwm->regs + PWM);
pwm_lpss_write(pwm, ctrl);
pm_runtime_put(chip->dev);
return 0;
}
static int pwm_lpss_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pwm_lpss_chip *lpwm = to_lpwm(chip);
u32 ctrl;
ctrl = readl(lpwm->regs + PWM);
writel(ctrl | PWM_ENABLE, lpwm->regs + PWM);
pm_runtime_get_sync(chip->dev);
pwm_lpss_write(pwm, pwm_lpss_read(pwm) | PWM_ENABLE);
return 0;
}
static void pwm_lpss_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct pwm_lpss_chip *lpwm = to_lpwm(chip);
u32 ctrl;
ctrl = readl(lpwm->regs + PWM);
writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM);
pwm_lpss_write(pwm, pwm_lpss_read(pwm) & ~PWM_ENABLE);
pm_runtime_put(chip->dev);
}
static const struct pwm_ops pwm_lpss_ops = {
.free = pwm_lpss_disable,
.config = pwm_lpss_config,
.enable = pwm_lpss_enable,
.disable = pwm_lpss_disable,
......@@ -135,7 +160,7 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
lpwm->chip.dev = dev;
lpwm->chip.ops = &pwm_lpss_ops;
lpwm->chip.base = -1;
lpwm->chip.npwm = 1;
lpwm->chip.npwm = info->npwm;
ret = pwmchip_add(&lpwm->chip);
if (ret) {
......@@ -149,11 +174,6 @@ EXPORT_SYMBOL_GPL(pwm_lpss_probe);
int pwm_lpss_remove(struct pwm_lpss_chip *lpwm)
{
u32 ctrl;
ctrl = readl(lpwm->regs + PWM);
writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM);
return pwmchip_remove(&lpwm->chip);
}
EXPORT_SYMBOL_GPL(pwm_lpss_remove);
......
......@@ -20,10 +20,12 @@ struct pwm_lpss_chip;
struct pwm_lpss_boardinfo {
unsigned long clk_rate;
unsigned int npwm;
};
extern const struct pwm_lpss_boardinfo pwm_lpss_byt_info;
extern const struct pwm_lpss_boardinfo pwm_lpss_bsw_info;
extern const struct pwm_lpss_boardinfo pwm_lpss_bxt_info;
struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
const struct pwm_lpss_boardinfo *info);
......
/*
* MediaTek display pulse-width-modulation controller driver.
* Copyright (c) 2015 MediaTek Inc.
* Author: YH Huang <yh.huang@mediatek.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#define DISP_PWM_EN 0x00
#define PWM_ENABLE_MASK BIT(0)
#define DISP_PWM_COMMIT 0x08
#define PWM_COMMIT_MASK BIT(0)
#define DISP_PWM_CON_0 0x10
#define PWM_CLKDIV_SHIFT 16
#define PWM_CLKDIV_MAX 0x3ff
#define PWM_CLKDIV_MASK (PWM_CLKDIV_MAX << PWM_CLKDIV_SHIFT)
#define DISP_PWM_CON_1 0x14
#define PWM_PERIOD_BIT_WIDTH 12
#define PWM_PERIOD_MASK ((1 << PWM_PERIOD_BIT_WIDTH) - 1)
#define PWM_HIGH_WIDTH_SHIFT 16
#define PWM_HIGH_WIDTH_MASK (0x1fff << PWM_HIGH_WIDTH_SHIFT)
struct mtk_disp_pwm {
struct pwm_chip chip;
struct clk *clk_main;
struct clk *clk_mm;
void __iomem *base;
};
static inline struct mtk_disp_pwm *to_mtk_disp_pwm(struct pwm_chip *chip)
{
return container_of(chip, struct mtk_disp_pwm, chip);
}
static void mtk_disp_pwm_update_bits(struct mtk_disp_pwm *mdp, u32 offset,
u32 mask, u32 data)
{
void __iomem *address = mdp->base + offset;
u32 value;
value = readl(address);
value &= ~mask;
value |= data;
writel(value, address);
}
static int mtk_disp_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip);
u32 clk_div, period, high_width, value;
u64 div, rate;
int err;
/*
* Find period, high_width and clk_div to suit duty_ns and period_ns.
* Calculate proper div value to keep period value in the bound.
*
* period_ns = 10^9 * (clk_div + 1) * (period + 1) / PWM_CLK_RATE
* duty_ns = 10^9 * (clk_div + 1) * high_width / PWM_CLK_RATE
*
* period = (PWM_CLK_RATE * period_ns) / (10^9 * (clk_div + 1)) - 1
* high_width = (PWM_CLK_RATE * duty_ns) / (10^9 * (clk_div + 1))
*/
rate = clk_get_rate(mdp->clk_main);
clk_div = div_u64(rate * period_ns, NSEC_PER_SEC) >>
PWM_PERIOD_BIT_WIDTH;
if (clk_div > PWM_CLKDIV_MAX)
return -EINVAL;
div = NSEC_PER_SEC * (clk_div + 1);
period = div64_u64(rate * period_ns, div);
if (period > 0)
period--;
high_width = div64_u64(rate * duty_ns, div);
value = period | (high_width << PWM_HIGH_WIDTH_SHIFT);
err = clk_enable(mdp->clk_main);
if (err < 0)
return err;
err = clk_enable(mdp->clk_mm);
if (err < 0) {
clk_disable(mdp->clk_main);
return err;
}
mtk_disp_pwm_update_bits(mdp, DISP_PWM_CON_0, PWM_CLKDIV_MASK,
clk_div << PWM_CLKDIV_SHIFT);
mtk_disp_pwm_update_bits(mdp, DISP_PWM_CON_1,
PWM_PERIOD_MASK | PWM_HIGH_WIDTH_MASK, value);
mtk_disp_pwm_update_bits(mdp, DISP_PWM_COMMIT, PWM_COMMIT_MASK, 1);
mtk_disp_pwm_update_bits(mdp, DISP_PWM_COMMIT, PWM_COMMIT_MASK, 0);
clk_disable(mdp->clk_mm);
clk_disable(mdp->clk_main);
return 0;
}
static int mtk_disp_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip);
int err;
err = clk_enable(mdp->clk_main);
if (err < 0)
return err;
err = clk_enable(mdp->clk_mm);
if (err < 0) {
clk_disable(mdp->clk_main);
return err;
}
mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, PWM_ENABLE_MASK, 1);
return 0;
}
static void mtk_disp_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct mtk_disp_pwm *mdp = to_mtk_disp_pwm(chip);
mtk_disp_pwm_update_bits(mdp, DISP_PWM_EN, PWM_ENABLE_MASK, 0);
clk_disable(mdp->clk_mm);
clk_disable(mdp->clk_main);
}
static const struct pwm_ops mtk_disp_pwm_ops = {
.config = mtk_disp_pwm_config,
.enable = mtk_disp_pwm_enable,
.disable = mtk_disp_pwm_disable,
.owner = THIS_MODULE,
};
static int mtk_disp_pwm_probe(struct platform_device *pdev)
{
struct mtk_disp_pwm *mdp;
struct resource *r;
int ret;
mdp = devm_kzalloc(&pdev->dev, sizeof(*mdp), GFP_KERNEL);
if (!mdp)
return -ENOMEM;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mdp->base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(mdp->base))
return PTR_ERR(mdp->base);
mdp->clk_main = devm_clk_get(&pdev->dev, "main");
if (IS_ERR(mdp->clk_main))
return PTR_ERR(mdp->clk_main);
mdp->clk_mm = devm_clk_get(&pdev->dev, "mm");
if (IS_ERR(mdp->clk_mm))
return PTR_ERR(mdp->clk_mm);
ret = clk_prepare(mdp->clk_main);
if (ret < 0)
return ret;
ret = clk_prepare(mdp->clk_mm);
if (ret < 0)
goto disable_clk_main;
mdp->chip.dev = &pdev->dev;
mdp->chip.ops = &mtk_disp_pwm_ops;
mdp->chip.base = -1;
mdp->chip.npwm = 1;
ret = pwmchip_add(&mdp->chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
goto disable_clk_mm;
}
platform_set_drvdata(pdev, mdp);
return 0;
disable_clk_mm:
clk_unprepare(mdp->clk_mm);
disable_clk_main:
clk_unprepare(mdp->clk_main);
return ret;
}
static int mtk_disp_pwm_remove(struct platform_device *pdev)
{
struct mtk_disp_pwm *mdp = platform_get_drvdata(pdev);
int ret;
ret = pwmchip_remove(&mdp->chip);
clk_unprepare(mdp->clk_mm);
clk_unprepare(mdp->clk_main);
return ret;
}
static const struct of_device_id mtk_disp_pwm_of_match[] = {
{ .compatible = "mediatek,mt8173-disp-pwm" },
{ .compatible = "mediatek,mt6595-disp-pwm" },
{ }
};
MODULE_DEVICE_TABLE(of, mtk_disp_pwm_of_match);
static struct platform_driver mtk_disp_pwm_driver = {
.driver = {
.name = "mediatek-disp-pwm",
.of_match_table = mtk_disp_pwm_of_match,
},
.probe = mtk_disp_pwm_probe,
.remove = mtk_disp_pwm_remove,
};
module_platform_driver(mtk_disp_pwm_driver);
MODULE_AUTHOR("YH Huang <yh.huang@mediatek.com>");
MODULE_DESCRIPTION("MediaTek SoC display PWM driver");
MODULE_LICENSE("GPL v2");
......@@ -19,9 +19,11 @@
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/slab.h>
......@@ -297,7 +299,6 @@ static const struct regmap_config pca9685_regmap_i2c_config = {
static int pca9685_pwm_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device_node *np = client->dev.of_node;
struct pca9685 *pca;
int ret;
int mode2;
......@@ -320,12 +321,12 @@ static int pca9685_pwm_probe(struct i2c_client *client,
regmap_read(pca->regmap, PCA9685_MODE2, &mode2);
if (of_property_read_bool(np, "invert"))
if (device_property_read_bool(&client->dev, "invert"))
mode2 |= MODE2_INVRT;
else
mode2 &= ~MODE2_INVRT;
if (of_property_read_bool(np, "open-drain"))
if (device_property_read_bool(&client->dev, "open-drain"))
mode2 &= ~MODE2_OUTDRV;
else
mode2 |= MODE2_OUTDRV;
......@@ -363,16 +364,27 @@ static const struct i2c_device_id pca9685_id[] = {
};
MODULE_DEVICE_TABLE(i2c, pca9685_id);
#ifdef CONFIG_ACPI
static const struct acpi_device_id pca9685_acpi_ids[] = {
{ "INT3492", 0 },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(acpi, pca9685_acpi_ids);
#endif
#ifdef CONFIG_OF
static const struct of_device_id pca9685_dt_ids[] = {
{ .compatible = "nxp,pca9685-pwm", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, pca9685_dt_ids);
#endif
static struct i2c_driver pca9685_i2c_driver = {
.driver = {
.name = "pca9685-pwm",
.of_match_table = pca9685_dt_ids,
.acpi_match_table = ACPI_PTR(pca9685_acpi_ids),
.of_match_table = of_match_ptr(pca9685_dt_ids),
},
.probe = pca9685_pwm_probe,
.remove = pca9685_pwm_remove,
......
/*
* R-Car PWM Timer driver
*
* Copyright (C) 2015 Renesas Electronics Corporation
*
* This is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#define RCAR_PWM_MAX_DIVISION 24
#define RCAR_PWM_MAX_CYCLE 1023
#define RCAR_PWMCR 0x00
#define RCAR_PWMCR_CC0_MASK 0x000f0000
#define RCAR_PWMCR_CC0_SHIFT 16
#define RCAR_PWMCR_CCMD BIT(15)
#define RCAR_PWMCR_SYNC BIT(11)
#define RCAR_PWMCR_SS0 BIT(4)
#define RCAR_PWMCR_EN0 BIT(0)
#define RCAR_PWMCNT 0x04
#define RCAR_PWMCNT_CYC0_MASK 0x03ff0000
#define RCAR_PWMCNT_CYC0_SHIFT 16
#define RCAR_PWMCNT_PH0_MASK 0x000003ff
#define RCAR_PWMCNT_PH0_SHIFT 0
struct rcar_pwm_chip {
struct pwm_chip chip;
void __iomem *base;
struct clk *clk;
};
static inline struct rcar_pwm_chip *to_rcar_pwm_chip(struct pwm_chip *chip)
{
return container_of(chip, struct rcar_pwm_chip, chip);
}
static void rcar_pwm_write(struct rcar_pwm_chip *rp, u32 data,
unsigned int offset)
{
writel(data, rp->base + offset);
}
static u32 rcar_pwm_read(struct rcar_pwm_chip *rp, unsigned int offset)
{
return readl(rp->base + offset);
}
static void rcar_pwm_update(struct rcar_pwm_chip *rp, u32 mask, u32 data,
unsigned int offset)
{
u32 value;
value = rcar_pwm_read(rp, offset);
value &= ~mask;
value |= data & mask;
rcar_pwm_write(rp, value, offset);
}
static int rcar_pwm_get_clock_division(struct rcar_pwm_chip *rp, int period_ns)
{
unsigned long clk_rate = clk_get_rate(rp->clk);
unsigned long long max; /* max cycle / nanoseconds */
unsigned int div;
if (clk_rate == 0)
return -EINVAL;
for (div = 0; div <= RCAR_PWM_MAX_DIVISION; div++) {
max = (unsigned long long)NSEC_PER_SEC * RCAR_PWM_MAX_CYCLE *
(1 << div);
do_div(max, clk_rate);
if (period_ns < max)
break;
}
return (div <= RCAR_PWM_MAX_DIVISION) ? div : -ERANGE;
}
static void rcar_pwm_set_clock_control(struct rcar_pwm_chip *rp,
unsigned int div)
{
u32 value;
value = rcar_pwm_read(rp, RCAR_PWMCR);
value &= ~(RCAR_PWMCR_CCMD | RCAR_PWMCR_CC0_MASK);
if (div & 1)
value |= RCAR_PWMCR_CCMD;
div >>= 1;
value |= div << RCAR_PWMCR_CC0_SHIFT;
rcar_pwm_write(rp, value, RCAR_PWMCR);
}
static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, int duty_ns,
int period_ns)
{
unsigned long long one_cycle, tmp; /* 0.01 nanoseconds */
unsigned long clk_rate = clk_get_rate(rp->clk);
u32 cyc, ph;
one_cycle = (unsigned long long)NSEC_PER_SEC * 100ULL * (1 << div);
do_div(one_cycle, clk_rate);
tmp = period_ns * 100ULL;
do_div(tmp, one_cycle);
cyc = (tmp << RCAR_PWMCNT_CYC0_SHIFT) & RCAR_PWMCNT_CYC0_MASK;
tmp = duty_ns * 100ULL;
do_div(tmp, one_cycle);
ph = tmp & RCAR_PWMCNT_PH0_MASK;
/* Avoid prohibited setting */
if (cyc == 0 || ph == 0)
return -EINVAL;
rcar_pwm_write(rp, cyc | ph, RCAR_PWMCNT);
return 0;
}
static int rcar_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip);
return clk_prepare_enable(rp->clk);
}
static void rcar_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip);
clk_disable_unprepare(rp->clk);
}
static int rcar_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip);
int div, ret;
div = rcar_pwm_get_clock_division(rp, period_ns);
if (div < 0)
return div;
/* Let the core driver set pwm->period if disabled and duty_ns == 0 */
if (!test_bit(PWMF_ENABLED, &pwm->flags) && !duty_ns)
return 0;
rcar_pwm_update(rp, RCAR_PWMCR_SYNC, RCAR_PWMCR_SYNC, RCAR_PWMCR);
ret = rcar_pwm_set_counter(rp, div, duty_ns, period_ns);
if (!ret)
rcar_pwm_set_clock_control(rp, div);
/* The SYNC should be set to 0 even if rcar_pwm_set_counter failed */
rcar_pwm_update(rp, RCAR_PWMCR_SYNC, 0, RCAR_PWMCR);
return ret;
}
static int rcar_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip);
u32 value;
/* Don't enable the PWM device if CYC0 or PH0 is 0 */
value = rcar_pwm_read(rp, RCAR_PWMCNT);
if ((value & RCAR_PWMCNT_CYC0_MASK) == 0 ||
(value & RCAR_PWMCNT_PH0_MASK) == 0)
return -EINVAL;
rcar_pwm_update(rp, RCAR_PWMCR_EN0, RCAR_PWMCR_EN0, RCAR_PWMCR);
return 0;
}
static void rcar_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct rcar_pwm_chip *rp = to_rcar_pwm_chip(chip);
rcar_pwm_update(rp, RCAR_PWMCR_EN0, 0, RCAR_PWMCR);
}
static const struct pwm_ops rcar_pwm_ops = {
.request = rcar_pwm_request,
.free = rcar_pwm_free,
.config = rcar_pwm_config,
.enable = rcar_pwm_enable,
.disable = rcar_pwm_disable,
.owner = THIS_MODULE,
};
static int rcar_pwm_probe(struct platform_device *pdev)
{
struct rcar_pwm_chip *rcar_pwm;
struct resource *res;
int ret;
rcar_pwm = devm_kzalloc(&pdev->dev, sizeof(*rcar_pwm), GFP_KERNEL);
if (rcar_pwm == NULL)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
rcar_pwm->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(rcar_pwm->base))
return PTR_ERR(rcar_pwm->base);
rcar_pwm->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(rcar_pwm->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
return PTR_ERR(rcar_pwm->clk);
}
platform_set_drvdata(pdev, rcar_pwm);
rcar_pwm->chip.dev = &pdev->dev;
rcar_pwm->chip.ops = &rcar_pwm_ops;
rcar_pwm->chip.base = -1;
rcar_pwm->chip.npwm = 1;
ret = pwmchip_add(&rcar_pwm->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register PWM chip: %d\n", ret);
return ret;
}
pm_runtime_enable(&pdev->dev);
return 0;
}
static int rcar_pwm_remove(struct platform_device *pdev)
{
struct rcar_pwm_chip *rcar_pwm = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
return pwmchip_remove(&rcar_pwm->chip);
}
static const struct of_device_id rcar_pwm_of_table[] = {
{ .compatible = "renesas,pwm-rcar", },
{ },
};
MODULE_DEVICE_TABLE(of, rcar_pwm_of_table);
static struct platform_driver rcar_pwm_driver = {
.probe = rcar_pwm_probe,
.remove = rcar_pwm_remove,
.driver = {
.name = "pwm-rcar",
.of_match_table = of_match_ptr(rcar_pwm_of_table),
}
};
module_platform_driver(rcar_pwm_driver);
MODULE_AUTHOR("Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>");
MODULE_DESCRIPTION("Renesas PWM Timer Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:pwm-rcar");
......@@ -68,6 +68,7 @@ static const u32 prescaler_table[] = {
struct sun4i_pwm_data {
bool has_prescaler_bypass;
bool has_rdy;
unsigned int npwm;
};
struct sun4i_pwm_chip {
......@@ -114,7 +115,7 @@ static int sun4i_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* is not an integer so round it half up instead of
* truncating to get less surprising values.
*/
div = clk_rate * period_ns + NSEC_PER_SEC/2;
div = clk_rate * period_ns + NSEC_PER_SEC / 2;
do_div(div, NSEC_PER_SEC);
if (div - 1 > PWM_PRD_MASK)
prescaler = 0;
......@@ -262,17 +263,37 @@ static const struct pwm_ops sun4i_pwm_ops = {
static const struct sun4i_pwm_data sun4i_pwm_data_a10 = {
.has_prescaler_bypass = false,
.has_rdy = false,
.npwm = 2,
};
static const struct sun4i_pwm_data sun4i_pwm_data_a10s = {
.has_prescaler_bypass = true,
.has_rdy = true,
.npwm = 2,
};
static const struct sun4i_pwm_data sun4i_pwm_data_a13 = {
.has_prescaler_bypass = true,
.has_rdy = true,
.npwm = 1,
};
static const struct sun4i_pwm_data sun4i_pwm_data_a20 = {
.has_prescaler_bypass = true,
.has_rdy = true,
.npwm = 2,
};
static const struct of_device_id sun4i_pwm_dt_ids[] = {
{
.compatible = "allwinner,sun4i-a10-pwm",
.data = &sun4i_pwm_data_a10,
}, {
.compatible = "allwinner,sun5i-a10s-pwm",
.data = &sun4i_pwm_data_a10s,
}, {
.compatible = "allwinner,sun5i-a13-pwm",
.data = &sun4i_pwm_data_a13,
}, {
.compatible = "allwinner,sun7i-a20-pwm",
.data = &sun4i_pwm_data_a20,
......@@ -305,14 +326,14 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
if (IS_ERR(pwm->clk))
return PTR_ERR(pwm->clk);
pwm->data = match->data;
pwm->chip.dev = &pdev->dev;
pwm->chip.ops = &sun4i_pwm_ops;
pwm->chip.base = -1;
pwm->chip.npwm = 2;
pwm->chip.npwm = pwm->data->npwm;
pwm->chip.can_sleep = true;
pwm->chip.of_xlate = of_pwm_xlate_with_flags;
pwm->chip.of_pwm_n_cells = 3;
pwm->data = match->data;
spin_lock_init(&pwm->ctrl_lock);
......
......@@ -40,7 +40,7 @@ static struct pwm_device *child_to_pwm_device(struct device *child)
return export->pwm;
}
static ssize_t pwm_period_show(struct device *child,
static ssize_t period_show(struct device *child,
struct device_attribute *attr,
char *buf)
{
......@@ -49,7 +49,7 @@ static ssize_t pwm_period_show(struct device *child,
return sprintf(buf, "%u\n", pwm_get_period(pwm));
}
static ssize_t pwm_period_store(struct device *child,
static ssize_t period_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
......@@ -66,7 +66,7 @@ static ssize_t pwm_period_store(struct device *child,
return ret ? : size;
}
static ssize_t pwm_duty_cycle_show(struct device *child,
static ssize_t duty_cycle_show(struct device *child,
struct device_attribute *attr,
char *buf)
{
......@@ -75,7 +75,7 @@ static ssize_t pwm_duty_cycle_show(struct device *child,
return sprintf(buf, "%u\n", pwm_get_duty_cycle(pwm));
}
static ssize_t pwm_duty_cycle_store(struct device *child,
static ssize_t duty_cycle_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
......@@ -92,17 +92,16 @@ static ssize_t pwm_duty_cycle_store(struct device *child,
return ret ? : size;
}
static ssize_t pwm_enable_show(struct device *child,
static ssize_t enable_show(struct device *child,
struct device_attribute *attr,
char *buf)
{
const struct pwm_device *pwm = child_to_pwm_device(child);
int enabled = pwm_is_enabled(pwm);
return sprintf(buf, "%d\n", enabled);
return sprintf(buf, "%d\n", pwm_is_enabled(pwm));
}
static ssize_t pwm_enable_store(struct device *child,
static ssize_t enable_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
......@@ -128,7 +127,7 @@ static ssize_t pwm_enable_store(struct device *child,
return ret ? : size;
}
static ssize_t pwm_polarity_show(struct device *child,
static ssize_t polarity_show(struct device *child,
struct device_attribute *attr,
char *buf)
{
......@@ -148,7 +147,7 @@ static ssize_t pwm_polarity_show(struct device *child,
return sprintf(buf, "%s\n", polarity);
}
static ssize_t pwm_polarity_store(struct device *child,
static ssize_t polarity_store(struct device *child,
struct device_attribute *attr,
const char *buf, size_t size)
{
......@@ -168,10 +167,10 @@ static ssize_t pwm_polarity_store(struct device *child,
return ret ? : size;
}
static DEVICE_ATTR(period, 0644, pwm_period_show, pwm_period_store);
static DEVICE_ATTR(duty_cycle, 0644, pwm_duty_cycle_show, pwm_duty_cycle_store);
static DEVICE_ATTR(enable, 0644, pwm_enable_show, pwm_enable_store);
static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store);
static DEVICE_ATTR_RW(period);
static DEVICE_ATTR_RW(duty_cycle);
static DEVICE_ATTR_RW(enable);
static DEVICE_ATTR_RW(polarity);
static struct attribute *pwm_attrs[] = {
&dev_attr_period.attr,
......@@ -245,7 +244,7 @@ static int pwm_unexport_child(struct device *parent, struct pwm_device *pwm)
return 0;
}
static ssize_t pwm_export_store(struct device *parent,
static ssize_t export_store(struct device *parent,
struct device_attribute *attr,
const char *buf, size_t len)
{
......@@ -271,9 +270,9 @@ static ssize_t pwm_export_store(struct device *parent,
return ret ? : len;
}
static DEVICE_ATTR(export, 0200, NULL, pwm_export_store);
static DEVICE_ATTR_WO(export);
static ssize_t pwm_unexport_store(struct device *parent,
static ssize_t unexport_store(struct device *parent,
struct device_attribute *attr,
const char *buf, size_t len)
{
......@@ -292,7 +291,7 @@ static ssize_t pwm_unexport_store(struct device *parent,
return ret ? : len;
}
static DEVICE_ATTR(unexport, 0200, NULL, pwm_unexport_store);
static DEVICE_ATTR_WO(unexport);
static ssize_t npwm_show(struct device *parent, struct device_attribute *attr,
char *buf)
......
......@@ -2,6 +2,7 @@
#define __LINUX_PWM_H
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/of.h>
struct pwm_device;
......@@ -87,6 +88,7 @@ enum {
* @pwm: global index of the PWM device
* @chip: PWM chip providing this PWM device
* @chip_data: chip-private data associated with the PWM device
* @lock: used to serialize accesses to the PWM device where necessary
* @period: period of the PWM signal (in nanoseconds)
* @duty_cycle: duty cycle of the PWM signal (in nanoseconds)
* @polarity: polarity of the PWM signal
......@@ -98,6 +100,7 @@ struct pwm_device {
unsigned int pwm;
struct pwm_chip *chip;
void *chip_data;
struct mutex lock;
unsigned int period;
unsigned int duty_cycle;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment