diff options
29 files changed, 1561 insertions, 283 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-pwm b/Documentation/ABI/testing/sysfs-class-pwm index c479d77b67c5..c20e61354561 100644 --- a/Documentation/ABI/testing/sysfs-class-pwm +++ b/Documentation/ABI/testing/sysfs-class-pwm @@ -77,3 +77,12 @@ Description: Enable/disable the PWM signal. 0 is disabled 1 is enabled + +What: /sys/class/pwm/pwmchipN/pwmX/capture +Date: June 2016 +KernelVersion: 4.8 +Contact: Lee Jones <lee.jones@linaro.org> +Description: + Capture information about a PWM signal. The output format is a + pair unsigned integers (period and duty cycle), separated by a + single space. diff --git a/Documentation/devicetree/bindings/pwm/brcm,iproc-pwm.txt b/Documentation/devicetree/bindings/pwm/brcm,iproc-pwm.txt new file mode 100644 index 000000000000..21f75bbd6dae --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/brcm,iproc-pwm.txt @@ -0,0 +1,21 @@ +Broadcom iProc PWM controller device tree bindings + +This controller has 4 channels. + +Required Properties : +- compatible: must be "brcm,iproc-pwm" +- reg: physical base address and length of the controller's registers +- clocks: phandle + clock specifier pair for the external clock +- #pwm-cells: Should be 3. See pwm.txt in this directory for a + description of the cells format. + +Refer to clocks/clock-bindings.txt for generic clock consumer properties. + +Example: + +pwm: pwm@18031000 { + compatible = "brcm,iproc-pwm"; + reg = <0x18031000 0x28>; + clocks = <&osc>; + #pwm-cells = <3>; +}; diff --git a/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt b/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt new file mode 100644 index 000000000000..472bd46ab5a4 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/google,cros-ec-pwm.txt @@ -0,0 +1,23 @@ +* PWM controlled by ChromeOS EC + +Google's ChromeOS EC PWM is a simple PWM attached to the Embedded Controller +(EC) and controlled via a host-command interface. + +An EC PWM node should be only found as a sub-node of the EC node (see +Documentation/devicetree/bindings/mfd/cros-ec.txt). + +Required properties: +- compatible: Must contain "google,cros-ec-pwm" +- #pwm-cells: Should be 1. The cell specifies the PWM index. + +Example: + cros-ec@0 { + compatible = "google,cros-ec-spi"; + + ... + + cros_ec_pwm: ec-pwm { + compatible = "google,cros-ec-pwm"; + #pwm-cells = <1>; + }; + }; diff --git a/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt b/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt index c52f03b5032f..b4e73778dda3 100644 --- a/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt +++ b/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt @@ -1,10 +1,14 @@ Tegra SoC PWFM controller Required properties: -- compatible: For Tegra20, must contain "nvidia,tegra20-pwm". For Tegra30, - must contain "nvidia,tegra30-pwm". Otherwise, must contain - "nvidia,<chip>-pwm", plus one of the above, where <chip> is tegra114, - tegra124, tegra132, or tegra210. +- compatible: Must be: + - "nvidia,tegra20-pwm": for Tegra20 + - "nvidia,tegra30-pwm", "nvidia,tegra20-pwm": for Tegra30 + - "nvidia,tegra114-pwm", "nvidia,tegra20-pwm": for Tegra114 + - "nvidia,tegra124-pwm", "nvidia,tegra20-pwm": for Tegra124 + - "nvidia,tegra132-pwm", "nvidia,tegra20-pwm": for Tegra132 + - "nvidia,tegra210-pwm", "nvidia,tegra20-pwm": for Tegra210 + - "nvidia,tegra186-pwm": for Tegra186 - reg: physical base address and length of the controller's registers - #pwm-cells: should be 2. See pwm.txt in this directory for a description of the cells format. diff --git a/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt b/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt index 0822a083fc57..d6de64335022 100644 --- a/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt +++ b/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.txt @@ -7,6 +7,7 @@ Required Properties: - "renesas,pwm-r8a7790": for R-Car H2 - "renesas,pwm-r8a7791": for R-Car M2-W - "renesas,pwm-r8a7794": for R-Car E2 + - "renesas,pwm-r8a7795": for R-Car H3 - reg: base address and length of the registers block for the PWM. - #pwm-cells: should be 2. See pwm.txt in this directory for a description of the cells format. diff --git a/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt b/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt new file mode 100644 index 000000000000..cb209646bf13 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/st,stmpe-pwm.txt @@ -0,0 +1,18 @@ +== ST STMPE PWM controller == + +This is a PWM block embedded in the ST Microelectronics STMPE +(ST Multi-Purpose Expander) chips. The PWM is registered as a +subdevices of the STMPE MFD device. + +Required properties: +- compatible: should be: + - "st,stmpe-pwm" +- #pwm-cells: should be 2. See pwm.txt in this directory for a description of + the cells format. + +Example: + +pwm0: pwm { + compatible = "st,stmpe-pwm"; + #pwm-cells = <2>; +}; diff --git a/Documentation/devicetree/bindings/regulator/pwm-regulator.txt b/Documentation/devicetree/bindings/regulator/pwm-regulator.txt index dd6f59cf1455..3aeba9f86ed8 100644 --- a/Documentation/devicetree/bindings/regulator/pwm-regulator.txt +++ b/Documentation/devicetree/bindings/regulator/pwm-regulator.txt @@ -34,6 +34,18 @@ Only required for Voltage Table Mode: First cell is voltage in microvolts (uV) Second cell is duty-cycle in percent (%) +Optional properties for Continuous mode: +- pwm-dutycycle-unit: Integer value encoding the duty cycle unit. If not + defined, <100> is assumed, meaning that + pwm-dutycycle-range contains values expressed in + percent. + +- pwm-dutycycle-range: Should contain 2 entries. The first entry is encoding + the dutycycle for regulator-min-microvolt and the + second one the dutycycle for regulator-max-microvolt. + Duty cycle values are expressed in pwm-dutycycle-unit. + If not defined, <0 100> is assumed. + NB: To be clear, if voltage-table is provided, then the device will be used in Voltage Table Mode. If no voltage-table is provided, then the device will be used in Continuous Voltage Mode. @@ -53,6 +65,13 @@ Continuous Voltage With Enable GPIO Example: regulator-min-microvolt = <1016000>; regulator-max-microvolt = <1114000>; regulator-name = "vdd_logic"; + /* unit == per-mille */ + pwm-dutycycle-unit = <1000>; + /* + * Inverted PWM logic, and the duty cycle range is limited + * to 30%-70%. + */ + pwm-dutycycle-range <700 300>; /* */ }; Voltage Table Example: diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index b6e161f71b26..6c084b266651 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -380,3 +380,20 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, return ret; } EXPORT_SYMBOL(cros_ec_cmd_xfer); + +int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + int ret; + + ret = cros_ec_cmd_xfer(ec_dev, msg); + if (ret < 0) { + dev_err(ec_dev->dev, "Command xfer error (err:%d)\n", ret); + } else if (msg->result != EC_RES_SUCCESS) { + dev_dbg(ec_dev->dev, "Command result (err: %d)\n", msg->result); + return -EPROTO; + } + + return ret; +} +EXPORT_SYMBOL(cros_ec_cmd_xfer_status); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index c182efc62c7b..80a566a00d04 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -74,6 +74,16 @@ config PWM_ATMEL_TCB To compile this driver as a module, choose M here: the module will be called pwm-atmel-tcb. +config PWM_BCM_IPROC + tristate "iProc PWM support" + depends on ARCH_BCM_IPROC + help + Generic PWM framework driver for Broadcom iProc PWM block. This + block is used in Broadcom iProc SoC's. + + To compile this driver as a module, choose M here: the module + will be called pwm-bcm-iproc. + config PWM_BCM_KONA tristate "Kona PWM support" depends on ARCH_BCM_MOBILE @@ -137,6 +147,13 @@ config PWM_CRC Generic PWM framework driver for Crystalcove (CRC) PMIC based PWM control. +config PWM_CROS_EC + tristate "ChromeOS EC PWM driver" + depends on MFD_CROS_EC + help + PWM driver for exposing a PWM attached to the ChromeOS Embedded + Controller. + config PWM_EP93XX tristate "Cirrus Logic EP93xx PWM support" depends on ARCH_EP93XX @@ -305,7 +322,7 @@ config PWM_PXA config PWM_RCAR tristate "Renesas R-Car PWM support" - depends on ARCH_RCAR_GEN1 || ARCH_RCAR_GEN2 || COMPILE_TEST + depends on ARCH_RENESAS || COMPILE_TEST depends on HAS_IOMEM help This driver exposes the PWM Timer controller found in Renesas @@ -362,6 +379,13 @@ config PWM_STI To compile this driver as a module, choose M here: the module will be called pwm-sti. +config PWM_STMPE + bool "STMPE expander PWM export" + depends on MFD_STMPE + help + This enables support for the PWMs found in the STMPE I/O + expanders. + config PWM_SUN4I tristate "Allwinner PWM support" depends on ARCH_SUNXI || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index dd35bc121a18..feef1dd29f73 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o +obj-$(CONFIG_PWM_BCM_IPROC) += pwm-bcm-iproc.o obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o @@ -11,6 +12,7 @@ obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o obj-$(CONFIG_PWM_CRC) += pwm-crc.o +obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o obj-$(CONFIG_PWM_IMG) += pwm-img.o @@ -34,6 +36,7 @@ obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o obj-$(CONFIG_PWM_STI) += pwm-sti.o +obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index ed337a8c34ab..0dbd29e287db 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -526,6 +526,33 @@ int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state) EXPORT_SYMBOL_GPL(pwm_apply_state); /** + * pwm_capture() - capture and report a PWM signal + * @pwm: PWM device + * @result: structure to fill with capture result + * @timeout: time to wait, in milliseconds, before giving up on capture + * + * Returns: 0 on success or a negative error code on failure. + */ +int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, + unsigned long timeout) +{ + int err; + + if (!pwm || !pwm->chip->ops) + return -EINVAL; + + if (!pwm->chip->ops->capture) + return -ENOSYS; + + mutex_lock(&pwm_lock); + err = pwm->chip->ops->capture(pwm->chip, pwm, result, timeout); + mutex_unlock(&pwm_lock); + + return err; +} +EXPORT_SYMBOL_GPL(pwm_capture); + +/** * pwm_adjust_config() - adjust the current PWM config to the PWM arguments * @pwm: PWM device * diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c index 0e4bd4e8e582..e6b8b1b7e6ba 100644 --- a/drivers/pwm/pwm-atmel.c +++ b/drivers/pwm/pwm-atmel.c @@ -64,7 +64,8 @@ struct atmel_pwm_chip { void __iomem *base; unsigned int updated_pwms; - struct mutex isr_lock; /* ISR is cleared when read, ensure only one thread does that */ + /* ISR is cleared when read, ensure only one thread does that */ + struct mutex isr_lock; void (*config)(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long dty, unsigned long prd); @@ -271,6 +272,16 @@ static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) mutex_unlock(&atmel_pwm->isr_lock); atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm); + /* + * Wait for the PWM channel disable operation to be effective before + * stopping the clock. + */ + timeout = jiffies + 2 * HZ; + + while ((atmel_pwm_readl(atmel_pwm, PWM_SR) & (1 << pwm->hwpwm)) && + time_before(jiffies, timeout)) + usleep_range(10, 100); + clk_disable(atmel_pwm->clk); } @@ -324,21 +335,14 @@ MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids); static inline const struct atmel_pwm_data * atmel_pwm_get_driver_data(struct platform_device *pdev) { - if (pdev->dev.of_node) { - const struct of_device_id *match; - - match = of_match_device(atmel_pwm_dt_ids, &pdev->dev); - if (!match) - return NULL; + const struct platform_device_id *id; - return match->data; - } else { - const struct platform_device_id *id; + if (pdev->dev.of_node) + return of_device_get_match_data(&pdev->dev); - id = platform_get_device_id(pdev); + id = platform_get_device_id(pdev); - return (struct atmel_pwm_data *)id->driver_data; - } + return (struct atmel_pwm_data *)id->driver_data; } static int atmel_pwm_probe(struct platform_device *pdev) diff --git a/drivers/pwm/pwm-bcm-iproc.c b/drivers/pwm/pwm-bcm-iproc.c new file mode 100644 index 000000000000..d961a8207b1c --- /dev/null +++ b/drivers/pwm/pwm-bcm-iproc.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2016 Broadcom + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +#define IPROC_PWM_CTRL_OFFSET 0x00 +#define IPROC_PWM_CTRL_TYPE_SHIFT(ch) (15 + (ch)) +#define IPROC_PWM_CTRL_POLARITY_SHIFT(ch) (8 + (ch)) +#define IPROC_PWM_CTRL_EN_SHIFT(ch) (ch) + +#define IPROC_PWM_PERIOD_OFFSET(ch) (0x04 + ((ch) << 3)) +#define IPROC_PWM_PERIOD_MIN 0x02 +#define IPROC_PWM_PERIOD_MAX 0xffff + +#define IPROC_PWM_DUTY_CYCLE_OFFSET(ch) (0x08 + ((ch) << 3)) +#define IPROC_PWM_DUTY_CYCLE_MIN 0x00 +#define IPROC_PWM_DUTY_CYCLE_MAX 0xffff + +#define IPROC_PWM_PRESCALE_OFFSET 0x24 +#define IPROC_PWM_PRESCALE_BITS 0x06 +#define IPROC_PWM_PRESCALE_SHIFT(ch) ((3 - (ch)) * \ + IPROC_PWM_PRESCALE_BITS) +#define IPROC_PWM_PRESCALE_MASK(ch) (IPROC_PWM_PRESCALE_MAX << \ + IPROC_PWM_PRESCALE_SHIFT(ch)) +#define IPROC_PWM_PRESCALE_MIN 0x00 +#define IPROC_PWM_PRESCALE_MAX 0x3f + +struct iproc_pwmc { + struct pwm_chip chip; + void __iomem *base; + struct clk *clk; +}; + +static inline struct iproc_pwmc *to_iproc_pwmc(struct pwm_chip *chip) +{ + return container_of(chip, struct iproc_pwmc, chip); +} + +static void iproc_pwmc_enable(struct iproc_pwmc *ip, unsigned int channel) +{ + u32 value; + + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + value |= 1 << IPROC_PWM_CTRL_EN_SHIFT(channel); + writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); + + /* must be a 400 ns delay between clearing and setting enable bit */ + ndelay(400); +} + +static void iproc_pwmc_disable(struct iproc_pwmc *ip, unsigned int channel) +{ + u32 value; + + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + value &= ~(1 << IPROC_PWM_CTRL_EN_SHIFT(channel)); + writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); + + /* must be a 400 ns delay between clearing and setting enable bit */ + ndelay(400); +} + +static void iproc_pwmc_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct iproc_pwmc *ip = to_iproc_pwmc(chip); + u64 tmp, multi, rate; + u32 value, prescale; + + rate = clk_get_rate(ip->clk); + + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + + if (value & BIT(IPROC_PWM_CTRL_EN_SHIFT(pwm->hwpwm))) + state->enabled = true; + else + state->enabled = false; + + if (value & BIT(IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm))) + state->polarity = PWM_POLARITY_NORMAL; + else + state->polarity = PWM_POLARITY_INVERSED; + + value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET); + prescale = value >> IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm); + prescale &= IPROC_PWM_PRESCALE_MAX; + + multi = NSEC_PER_SEC * (prescale + 1); + + value = readl(ip->base + IPROC_PWM_PERIOD_OFFSET(pwm->hwpwm)); + tmp = (value & IPROC_PWM_PERIOD_MAX) * multi; + state->period = div64_u64(tmp, rate); + + value = readl(ip->base + IPROC_PWM_DUTY_CYCLE_OFFSET(pwm->hwpwm)); + tmp = (value & IPROC_PWM_PERIOD_MAX) * multi; + state->duty_cycle = div64_u64(tmp, rate); +} + +static int iproc_pwmc_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + unsigned long prescale = IPROC_PWM_PRESCALE_MIN; + struct iproc_pwmc *ip = to_iproc_pwmc(chip); + u32 value, period, duty; + u64 rate; + + rate = clk_get_rate(ip->clk); + + /* + * Find period count, duty count and prescale to suit duty_cycle and + * period. This is done according to formulas described below: + * + * period_ns = 10^9 * (PRESCALE + 1) * PC / PWM_CLK_RATE + * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE + * + * PC = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1)) + * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1)) + */ + while (1) { + u64 value, div; + + div = NSEC_PER_SEC * (prescale + 1); + value = rate * state->period; + period = div64_u64(value, div); + value = rate * state->duty_cycle; + duty = div64_u64(value, div); + + if (period < IPROC_PWM_PERIOD_MIN || + duty < IPROC_PWM_DUTY_CYCLE_MIN) + return -EINVAL; + + if (period <= IPROC_PWM_PERIOD_MAX && + duty <= IPROC_PWM_DUTY_CYCLE_MAX) + break; + + /* Otherwise, increase prescale and recalculate counts */ + if (++prescale > IPROC_PWM_PRESCALE_MAX) + return -EINVAL; + } + + iproc_pwmc_disable(ip, pwm->hwpwm); + + /* Set prescale */ + value = readl(ip->base + IPROC_PWM_PRESCALE_OFFSET); + value &= ~IPROC_PWM_PRESCALE_MASK(pwm->hwpwm); + value |= prescale << IPROC_PWM_PRESCALE_SHIFT(pwm->hwpwm); + writel(value, ip->base + IPROC_PWM_PRESCALE_OFFSET); + + /* set period and duty cycle */ + writel(period, ip->base + IPROC_PWM_PERIOD_OFFSET(pwm->hwpwm)); + writel(duty, ip->base + IPROC_PWM_DUTY_CYCLE_OFFSET(pwm->hwpwm)); + + /* set polarity */ + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + + if (state->polarity == PWM_POLARITY_NORMAL) + value |= 1 << IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm); + else + value &= ~(1 << IPROC_PWM_CTRL_POLARITY_SHIFT(pwm->hwpwm)); + + writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); + + if (state->enabled) + iproc_pwmc_enable(ip, pwm->hwpwm); + + return 0; +} + +static const struct pwm_ops iproc_pwm_ops = { + .apply = iproc_pwmc_apply, + .get_state = iproc_pwmc_get_state, +}; + +static int iproc_pwmc_probe(struct platform_device *pdev) +{ + struct iproc_pwmc *ip; + struct resource *res; + unsigned int i; + u32 value; + int ret; + + ip = devm_kzalloc(&pdev->dev, sizeof(*ip), GFP_KERNEL); + if (!ip) + return -ENOMEM; + + platform_set_drvdata(pdev, ip); + + ip->chip.dev = &pdev->dev; + ip->chip.ops = &iproc_pwm_ops; + ip->chip.base = -1; + ip->chip.npwm = 4; + ip->chip.of_xlate = of_pwm_xlate_with_flags; + ip->chip.of_pwm_n_cells = 3; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ip->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ip->base)) + return PTR_ERR(ip->base); + + ip->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(ip->clk)) { + dev_err(&pdev->dev, "failed to get clock: %ld\n", + PTR_ERR(ip->clk)); + return PTR_ERR(ip->clk); + } + + ret = clk_prepare_enable(ip->clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable clock: %d\n", ret); + return ret; + } + + /* Set full drive and normal polarity for all channels */ + value = readl(ip->base + IPROC_PWM_CTRL_OFFSET); + + for (i = 0; i < ip->chip.npwm; i++) { + value &= ~(1 << IPROC_PWM_CTRL_TYPE_SHIFT(i)); + value |= 1 << IPROC_PWM_CTRL_POLARITY_SHIFT(i); + } + + writel(value, ip->base + IPROC_PWM_CTRL_OFFSET); + + ret = pwmchip_add(&ip->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + clk_disable_unprepare(ip->clk); + } + + return ret; +} + +static int iproc_pwmc_remove(struct platform_device *pdev) +{ + struct iproc_pwmc *ip = platform_get_drvdata(pdev); + + clk_disable_unprepare(ip->clk); + + return pwmchip_remove(&ip->chip); +} + +static const struct of_device_id bcm_iproc_pwmc_dt[] = { + { .compatible = "brcm,iproc-pwm" }, + { }, +}; +MODULE_DEVICE_TABLE(of, bcm_iproc_pwmc_dt); + +static struct platform_driver iproc_pwmc_driver = { + .driver = { + .name = "bcm-iproc-pwm", + .of_match_table = bcm_iproc_pwmc_dt, + }, + .probe = iproc_pwmc_probe, + .remove = iproc_pwmc_remove, +}; +module_platform_driver(iproc_pwmc_driver); + +MODULE_AUTHOR("Yendapally Reddy Dhananjaya Reddy <yendapally.reddy@broadcom.com>"); +MODULE_DESCRIPTION("Broadcom iProc PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-cros-ec.c b/drivers/pwm/pwm-cros-ec.c new file mode 100644 index 000000000000..99b9acc1a420 --- /dev/null +++ b/drivers/pwm/pwm-cros-ec.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2016 Google, Inc + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published by + * the Free Software Foundation. + * + * Expose a PWM controlled by the ChromeOS EC to the host processor. + */ + +#include <linux/module.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +/** + * struct cros_ec_pwm_device - Driver data for EC PWM + * + * @dev: Device node + * @ec: Pointer to EC device + * @chip: PWM controller chip + */ +struct cros_ec_pwm_device { + struct device *dev; + struct cros_ec_device *ec; + struct pwm_chip chip; +}; + +static inline struct cros_ec_pwm_device *pwm_to_cros_ec_pwm(struct pwm_chip *c) +{ + return container_of(c, struct cros_ec_pwm_device, chip); +} + +static int cros_ec_pwm_set_duty(struct cros_ec_device *ec, u8 index, u16 duty) +{ + struct { + struct cros_ec_command msg; + struct ec_params_pwm_set_duty params; + } buf; + struct ec_params_pwm_set_duty *params = &buf.params; + struct cros_ec_command *msg = &buf.msg; + + memset(&buf, 0, sizeof(buf)); + + msg->version = 0; + msg->command = EC_CMD_PWM_SET_DUTY; + msg->insize = 0; + msg->outsize = sizeof(*params); + + params->duty = duty; + params->pwm_type = EC_PWM_TYPE_GENERIC; + params->index = index; + + return cros_ec_cmd_xfer_status(ec, msg); +} + +static int __cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index, + u32 *result) +{ + struct { + struct cros_ec_command msg; + union { + struct ec_params_pwm_get_duty params; + struct ec_response_pwm_get_duty resp; + }; + } buf; + struct ec_params_pwm_get_duty *params = &buf.params; + struct ec_response_pwm_get_duty *resp = &buf.resp; + struct cros_ec_command *msg = &buf.msg; + int ret; + + memset(&buf, 0, sizeof(buf)); + + msg->version = 0; + msg->command = EC_CMD_PWM_GET_DUTY; + msg->insize = sizeof(*params); + msg->outsize = sizeof(*resp); + + params->pwm_type = EC_PWM_TYPE_GENERIC; + params->index = index; + + ret = cros_ec_cmd_xfer_status(ec, msg); + if (result) + *result = msg->result; + if (ret < 0) + return ret; + + return resp->duty; +} + +static int cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index) +{ + return __cros_ec_pwm_get_duty(ec, index, NULL); +} + +static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip); + int duty_cycle; + + /* The EC won't let us change the period */ + if (state->period != EC_PWM_MAX_DUTY) + return -EINVAL; + + /* + * EC doesn't separate the concept of duty cycle and enabled, but + * kernel does. Translate. + */ + duty_cycle = state->enabled ? state->duty_cycle : 0; + + return cros_ec_pwm_set_duty(ec_pwm->ec, pwm->hwpwm, duty_cycle); +} + +static void cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip); + int ret; + + ret = cros_ec_pwm_get_duty(ec_pwm->ec, pwm->hwpwm); + if (ret < 0) { + dev_err(chip->dev, "error getting initial duty: %d\n", ret); + return; + } + + state->enabled = (ret > 0); + state->period = EC_PWM_MAX_DUTY; + + /* Note that "disabled" and "duty cycle == 0" are treated the same */ + state->duty_cycle = ret; +} + +static struct pwm_device * +cros_ec_pwm_xlate(struct pwm_chip *pc, const struct of_phandle_args *args) +{ + struct pwm_device *pwm; + + if (args->args[0] >= pc->npwm) + return ERR_PTR(-EINVAL); + + pwm = pwm_request_from_chip(pc, args->args[0], NULL); + if (IS_ERR(pwm)) + return pwm; + + /* The EC won't let us change the period */ + pwm->args.period = EC_PWM_MAX_DUTY; + + return pwm; +} + +static const struct pwm_ops cros_ec_pwm_ops = { + .get_state = cros_ec_pwm_get_state, + .apply = cros_ec_pwm_apply, + .owner = THIS_MODULE, +}; + +static int cros_ec_num_pwms(struct cros_ec_device *ec) +{ + int i, ret; + + /* The index field is only 8 bits */ + for (i = 0; i <= U8_MAX; i++) { + u32 result = 0; + + ret = __cros_ec_pwm_get_duty(ec, i, &result); + /* We want to parse EC protocol errors */ + if (ret < 0 && !(ret == -EPROTO && result)) + return ret; + + /* + * We look for SUCCESS, INVALID_COMMAND, or INVALID_PARAM + * responses; everything else is treated as an error. + */ + if (result == EC_RES_INVALID_COMMAND) + return -ENODEV; + else if (result == EC_RES_INVALID_PARAM) + return i; + else if (result) + return -EPROTO; + } + + return U8_MAX; +} + +static int cros_ec_pwm_probe(struct platform_device *pdev) +{ + struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent) |
