diff --git a/Documentation/ABI/testing/sysfs-class-pwm b/Documentation/ABI/testing/sysfs-class-pwm index c479d77b67c540ab6c643942d992d37d9043d1c8..c20e61354561db0b1023a4a15b4499de06958029 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 +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 0000000000000000000000000000000000000000..21f75bbd6dae4524cf489289cf00d35e0d2f1f04 --- /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 0000000000000000000000000000000000000000..472bd46ab5a4dc6714358b45be33d7beafb63b68 --- /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 c52f03b5032f8fab8616d91e7a705a06318331ca..b4e73778dda3a2e211ec210869d4d78807066122 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,-pwm", plus one of the above, where 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 0822a083fc577c4b1da34521041728b7034cb568..d6de643350223869dba6fdfa09ef668e17de784c 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 0000000000000000000000000000000000000000..cb209646bf132c555e1e2f9111683a2fd14254fa --- /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 dd6f59cf1455ee9c629dd277d704469cf7cdad33..3aeba9f86ed8ee515dc8a169aafd65a0d4a777ba 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 b6e161f71b26af834b2366b2d2f9f0e32f61ac0c..6c084b26665113ca0698cf24d0cc7436a974b6ec 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 c182efc62c7bc3ffe31d81a539c88c35f55abac9..80a566a00d0437dcc8fca7715c1109e7c2d35f67 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 dd35bc121a185e3d6ea0c22df4308158272ba078..feef1dd29f731a187b43bb1af7cd74be15f8e231 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 ed337a8c34ab4bb231814136fe5cd9628962eff1..0dbd29e287dbbf495a28fa4b142fb407773b2f0d 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -525,6 +525,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 0e4bd4e8e5823727c03b7701ad893b4cae1f7e7e..e6b8b1b7e6ba7ddcbb2fa6142b2ba33ff0af777e 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 0000000000000000000000000000000000000000..d961a8207b1cbe660cfb6f38d21b7383f1834847 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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 0000000000000000000000000000000000000000..99b9acc1a420801a8d6fb6999d45ca97399d30b6 --- /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 +#include +#include +#include +#include +#include + +/** + * struct cros_ec_pwm_device - Driver data for EC PWM + * + * @dev: Device node + * @ec: Pointer to EC device + * @chip: PWM controller chip + */ +struct cros_ec_pwm_device { + struct device *dev; + struct cros_ec_device *ec; + struct pwm_chip chip; +}; + +static inline struct cros_ec_pwm_device *pwm_to_cros_ec_pwm(struct pwm_chip *c) +{ + return container_of(c, struct cros_ec_pwm_device, chip); +} + +static int cros_ec_pwm_set_duty(struct cros_ec_device *ec, u8 index, u16 duty) +{ + struct { + struct cros_ec_command msg; + struct ec_params_pwm_set_duty params; + } buf; + struct ec_params_pwm_set_duty *params = &buf.params; + struct cros_ec_command *msg = &buf.msg; + + memset(&buf, 0, sizeof(buf)); + + msg->version = 0; + msg->command = EC_CMD_PWM_SET_DUTY; + msg->insize = 0; + msg->outsize = sizeof(*params); + + params->duty = duty; + params->pwm_type = EC_PWM_TYPE_GENERIC; + params->index = index; + + return cros_ec_cmd_xfer_status(ec, msg); +} + +static int __cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index, + u32 *result) +{ + struct { + struct cros_ec_command msg; + union { + struct ec_params_pwm_get_duty params; + struct ec_response_pwm_get_duty resp; + }; + } buf; + struct ec_params_pwm_get_duty *params = &buf.params; + struct ec_response_pwm_get_duty *resp = &buf.resp; + struct cros_ec_command *msg = &buf.msg; + int ret; + + memset(&buf, 0, sizeof(buf)); + + msg->version = 0; + msg->command = EC_CMD_PWM_GET_DUTY; + msg->insize = sizeof(*params); + msg->outsize = sizeof(*resp); + + params->pwm_type = EC_PWM_TYPE_GENERIC; + params->index = index; + + ret = cros_ec_cmd_xfer_status(ec, msg); + if (result) + *result = msg->result; + if (ret < 0) + return ret; + + return resp->duty; +} + +static int cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index) +{ + return __cros_ec_pwm_get_duty(ec, index, NULL); +} + +static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip); + int duty_cycle; + + /* The EC won't let us change the period */ + if (state->period != EC_PWM_MAX_DUTY) + return -EINVAL; + + /* + * EC doesn't separate the concept of duty cycle and enabled, but + * kernel does. Translate. + */ + duty_cycle = state->enabled ? state->duty_cycle : 0; + + return cros_ec_pwm_set_duty(ec_pwm->ec, pwm->hwpwm, duty_cycle); +} + +static void cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip); + int ret; + + ret = cros_ec_pwm_get_duty(ec_pwm->ec, pwm->hwpwm); + if (ret < 0) { + dev_err(chip->dev, "error getting initial duty: %d\n", ret); + return; + } + + state->enabled = (ret > 0); + state->period = EC_PWM_MAX_DUTY; + + /* Note that "disabled" and "duty cycle == 0" are treated the same */ + state->duty_cycle = ret; +} + +static struct pwm_device * +cros_ec_pwm_xlate(struct pwm_chip *pc, const struct of_phandle_args *args) +{ + struct pwm_device *pwm; + + if (args->args[0] >= pc->npwm) + return ERR_PTR(-EINVAL); + + pwm = pwm_request_from_chip(pc, args->args[0], NULL); + if (IS_ERR(pwm)) + return pwm; + + /* The EC won't let us change the period */ + pwm->args.period = EC_PWM_MAX_DUTY; + + return pwm; +} + +static const struct pwm_ops cros_ec_pwm_ops = { + .get_state = cros_ec_pwm_get_state, + .apply = cros_ec_pwm_apply, + .owner = THIS_MODULE, +}; + +static int cros_ec_num_pwms(struct cros_ec_device *ec) +{ + int i, ret; + + /* The index field is only 8 bits */ + for (i = 0; i <= U8_MAX; i++) { + u32 result = 0; + + ret = __cros_ec_pwm_get_duty(ec, i, &result); + /* We want to parse EC protocol errors */ + if (ret < 0 && !(ret == -EPROTO && result)) + return ret; + + /* + * We look for SUCCESS, INVALID_COMMAND, or INVALID_PARAM + * responses; everything else is treated as an error. + */ + if (result == EC_RES_INVALID_COMMAND) + return -ENODEV; + else if (result == EC_RES_INVALID_PARAM) + return i; + else if (result) + return -EPROTO; + } + + return U8_MAX; +} + +static int cros_ec_pwm_probe(struct platform_device *pdev) +{ + struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct cros_ec_pwm_device *ec_pwm; + struct pwm_chip *chip; + int ret; + + if (!ec) { + dev_err(dev, "no parent EC device\n"); + return -EINVAL; + } + + ec_pwm = devm_kzalloc(dev, sizeof(*ec_pwm), GFP_KERNEL); + if (!ec_pwm) + return -ENOMEM; + chip = &ec_pwm->chip; + ec_pwm->ec = ec; + + /* PWM chip */ + chip->dev = dev; + chip->ops = &cros_ec_pwm_ops; + chip->of_xlate = cros_ec_pwm_xlate; + chip->of_pwm_n_cells = 1; + chip->base = -1; + ret = cros_ec_num_pwms(ec); + if (ret < 0) { + dev_err(dev, "Couldn't find PWMs: %d\n", ret); + return ret; + } + chip->npwm = ret; + dev_dbg(dev, "Probed %u PWMs\n", chip->npwm); + + ret = pwmchip_add(chip); + if (ret < 0) { + dev_err(dev, "cannot register PWM: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, ec_pwm); + + return ret; +} + +static int cros_ec_pwm_remove(struct platform_device *dev) +{ + struct cros_ec_pwm_device *ec_pwm = platform_get_drvdata(dev); + struct pwm_chip *chip = &ec_pwm->chip; + + return pwmchip_remove(chip); +} + +#ifdef CONFIG_OF +static const struct of_device_id cros_ec_pwm_of_match[] = { + { .compatible = "google,cros-ec-pwm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, cros_ec_pwm_of_match); +#endif + +static struct platform_driver cros_ec_pwm_driver = { + .probe = cros_ec_pwm_probe, + .remove = cros_ec_pwm_remove, + .driver = { + .name = "cros-ec-pwm", + .of_match_table = of_match_ptr(cros_ec_pwm_of_match), + }, +}; +module_platform_driver(cros_ec_pwm_driver); + +MODULE_ALIAS("platform:cros-ec-pwm"); +MODULE_DESCRIPTION("ChromeOS EC PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-lpc32xx.c b/drivers/pwm/pwm-lpc32xx.c index 4d470c1a406a470081961db9d452f8c31bf70055..a9b3cff96aaca1c1a4429f58be7129fa82ec8827 100644 --- a/drivers/pwm/pwm-lpc32xx.c +++ b/drivers/pwm/pwm-lpc32xx.c @@ -25,6 +25,7 @@ struct lpc32xx_pwm_chip { }; #define PWM_ENABLE BIT(31) +#define PWM_PIN_LEVEL BIT(30) #define to_lpc32xx_pwm_chip(_chip) \ container_of(_chip, struct lpc32xx_pwm_chip, chip) @@ -103,6 +104,7 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev) struct lpc32xx_pwm_chip *lpc32xx; struct resource *res; int ret; + u32 val; lpc32xx = devm_kzalloc(&pdev->dev, sizeof(*lpc32xx), GFP_KERNEL); if (!lpc32xx) @@ -128,6 +130,11 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev) return ret; } + /* When PWM is disable, configure the output to the default value */ + val = readl(lpc32xx->base + (lpc32xx->chip.pwms[0].hwpwm << 2)); + val &= ~PWM_PIN_LEVEL; + writel(val, lpc32xx->base + (lpc32xx->chip.pwms[0].hwpwm << 2)); + platform_set_drvdata(pdev, lpc32xx); return 0; diff --git a/drivers/pwm/pwm-lpss-pci.c b/drivers/pwm/pwm-lpss-pci.c index 7160e8ab38a4ea83f7cd01b21f58b6309b0859fc..3622f093490e73cdc1133978e9314861e76aadd5 100644 --- a/drivers/pwm/pwm-lpss-pci.c +++ b/drivers/pwm/pwm-lpss-pci.c @@ -76,6 +76,7 @@ static const struct pci_device_id pwm_lpss_pci_ids[] = { { PCI_VDEVICE(INTEL, 0x0ac8), (unsigned long)&pwm_lpss_bxt_info}, { PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&pwm_lpss_byt_info}, { PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&pwm_lpss_byt_info}, + { PCI_VDEVICE(INTEL, 0x11a5), (unsigned long)&pwm_lpss_bxt_info}, { PCI_VDEVICE(INTEL, 0x1ac8), (unsigned long)&pwm_lpss_bxt_info}, { PCI_VDEVICE(INTEL, 0x2288), (unsigned long)&pwm_lpss_bsw_info}, { PCI_VDEVICE(INTEL, 0x2289), (unsigned long)&pwm_lpss_bsw_info}, diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 295b963dbddb7bdf59b159c6de03ba701d4efc9e..72c0bce5a75cc646dc0094d77774c48cbeb5ce6a 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -27,7 +27,6 @@ #define PWM_SW_UPDATE BIT(30) #define PWM_BASE_UNIT_SHIFT 8 #define PWM_ON_TIME_DIV_MASK 0x000000ff -#define PWM_DIVISION_CORRECTION 0x2 /* Size of each PWM register space if multiple */ #define PWM_SIZE 0x400 @@ -92,8 +91,8 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct pwm_lpss_chip *lpwm = to_lpwm(chip); - u8 on_time_div; - unsigned long c, base_unit_range; + unsigned long long on_time_div; + unsigned long c = lpwm->info->clk_rate, base_unit_range; unsigned long long base_unit, freq = NSEC_PER_SEC; u32 ctrl; @@ -101,21 +100,18 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, /* * The equation is: - * base_unit = ((freq / c) * base_unit_range) + correction + * base_unit = round(base_unit_range * freq / c) */ base_unit_range = BIT(lpwm->info->base_unit_bits); - base_unit = freq * base_unit_range; + freq *= base_unit_range; - c = lpwm->info->clk_rate; - if (!c) - return -EINVAL; - - do_div(base_unit, c); - base_unit += PWM_DIVISION_CORRECTION; + base_unit = DIV_ROUND_CLOSEST_ULL(freq, c); if (duty_ns <= 0) duty_ns = 1; - on_time_div = 255 - (255 * duty_ns / period_ns); + on_time_div = 255ULL * duty_ns; + do_div(on_time_div, period_ns); + on_time_div = 255ULL - on_time_div; pm_runtime_get_sync(chip->dev); @@ -169,6 +165,7 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, const struct pwm_lpss_boardinfo *info) { struct pwm_lpss_chip *lpwm; + unsigned long c; int ret; lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL); @@ -180,6 +177,11 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, return ERR_CAST(lpwm->regs); lpwm->info = info; + + c = lpwm->info->clk_rate; + if (!c) + return ERR_PTR(-EINVAL); + lpwm->chip.dev = dev; lpwm->chip.ops = &pwm_lpss_ops; lpwm->chip.base = -1; diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c index 7d9cc9049522348dd15951f927ed4649b1cdf364..ef89df1f7336c77a70c3a4de42b6d71ec96162ba 100644 --- a/drivers/pwm/pwm-rockchip.c +++ b/drivers/pwm/pwm-rockchip.c @@ -47,10 +47,14 @@ struct rockchip_pwm_regs { struct rockchip_pwm_data { struct rockchip_pwm_regs regs; unsigned int prescaler; + bool supports_polarity; const struct pwm_ops *ops; void (*set_enable)(struct pwm_chip *chip, - struct pwm_device *pwm, bool enable); + struct pwm_device *pwm, bool enable, + enum pwm_polarity polarity); + void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state); }; static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c) @@ -59,7 +63,8 @@ static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c) } static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip, - struct pwm_device *pwm, bool enable) + struct pwm_device *pwm, bool enable, + enum pwm_polarity polarity) { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN; @@ -75,15 +80,29 @@ static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip, writel_relaxed(val, pc->base + pc->data->regs.ctrl); } +static void rockchip_pwm_get_state_v1(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN; + u32 val; + + val = readl_relaxed(pc->base + pc->data->regs.ctrl); + if ((val & enable_conf) == enable_conf) + state->enabled = true; +} + static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip, - struct pwm_device *pwm, bool enable) + struct pwm_device *pwm, bool enable, + enum pwm_polarity polarity) { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | PWM_CONTINUOUS; u32 val; - if (pwm_get_polarity(pwm) == PWM_POLARITY_INVERSED) + if (polarity == PWM_POLARITY_INVERSED) enable_conf |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE; else enable_conf |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE; @@ -98,13 +117,59 @@ static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip, writel_relaxed(val, pc->base + pc->data->regs.ctrl); } +static void rockchip_pwm_get_state_v2(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | + PWM_CONTINUOUS; + u32 val; + + val = readl_relaxed(pc->base + pc->data->regs.ctrl); + if ((val & enable_conf) != enable_conf) + return; + + state->enabled = true; + + if (!(val & PWM_DUTY_POSITIVE)) + state->polarity = PWM_POLARITY_INVERSED; +} + +static void rockchip_pwm_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + unsigned long clk_rate; + u64 tmp; + int ret; + + ret = clk_enable(pc->clk); + if (ret) + return; + + clk_rate = clk_get_rate(pc->clk); + + tmp = readl_relaxed(pc->base + pc->data->regs.period); + tmp *= pc->data->prescaler * NSEC_PER_SEC; + state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); + + tmp = readl_relaxed(pc->base + pc->data->regs.duty); + tmp *= pc->data->prescaler * NSEC_PER_SEC; + state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); + + pc->data->get_state(chip, pwm, state); + + clk_disable(pc->clk); +} + static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); unsigned long period, duty; u64 clk_rate, div; - int ret; clk_rate = clk_get_rate(pc->clk); @@ -114,74 +179,72 @@ static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, * default prescaler value for all practical clock rate values. */ div = clk_rate * period_ns; - do_div(div, pc->data->prescaler * NSEC_PER_SEC); - period = div; + period = DIV_ROUND_CLOSEST_ULL(div, + pc->data->prescaler * NSEC_PER_SEC); div = clk_rate * duty_ns; - do_div(div, pc->data->prescaler * NSEC_PER_SEC); - duty = div; - - ret = clk_enable(pc->clk); - if (ret) - return ret; + duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC); writel(period, pc->base + pc->data->regs.period); writel(duty, pc->base + pc->data->regs.duty); - writel(0, pc->base + pc->data->regs.cntr); - - clk_disable(pc->clk); - - return 0; -} - -static int rockchip_pwm_set_polarity(struct pwm_chip *chip, - struct pwm_device *pwm, - enum pwm_polarity polarity) -{ - /* - * No action needed here because pwm->polarity will be set by the core - * and the core will only change polarity when the PWM is not enabled. - * We'll handle things in set_enable(). - */ return 0; } -static int rockchip_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) { struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + struct pwm_state curstate; + bool enabled; int ret; + pwm_get_state(pwm, &curstate); + enabled = curstate.enabled; + ret = clk_enable(pc->clk); if (ret) return ret; - pc->data->set_enable(chip, pwm, true); + if (state->polarity != curstate.polarity && enabled) { + pc->data->set_enable(chip, pwm, false, state->polarity); + enabled = false; + } - return 0; -} + ret = rockchip_pwm_config(chip, pwm, state->duty_cycle, state->period); + if (ret) { + if (enabled != curstate.enabled) + pc->data->set_enable(chip, pwm, !enabled, + state->polarity); -static void rockchip_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + goto out; + } + + if (state->enabled != enabled) + pc->data->set_enable(chip, pwm, state->enabled, + state->polarity); - pc->data->set_enable(chip, pwm, false); + /* + * Update the state with the real hardware, which can differ a bit + * because of period/duty_cycle approximation. + */ + rockchip_pwm_get_state(chip, pwm, state); +out: clk_disable(pc->clk); + + return ret; } static const struct pwm_ops rockchip_pwm_ops_v1 = { - .config = rockchip_pwm_config, - .enable = rockchip_pwm_enable, - .disable = rockchip_pwm_disable, + .get_state = rockchip_pwm_get_state, + .apply = rockchip_pwm_apply, .owner = THIS_MODULE, }; static const struct pwm_ops rockchip_pwm_ops_v2 = { - .config = rockchip_pwm_config, - .set_polarity = rockchip_pwm_set_polarity, - .enable = rockchip_pwm_enable, - .disable = rockchip_pwm_disable, + .get_state = rockchip_pwm_get_state, + .apply = rockchip_pwm_apply, .owner = THIS_MODULE, }; @@ -195,6 +258,7 @@ static const struct rockchip_pwm_data pwm_data_v1 = { .prescaler = 2, .ops = &rockchip_pwm_ops_v1, .set_enable = rockchip_pwm_set_enable_v1, + .get_state = rockchip_pwm_get_state_v1, }; static const struct rockchip_pwm_data pwm_data_v2 = { @@ -205,8 +269,10 @@ static const struct rockchip_pwm_data pwm_data_v2 = { .ctrl = 0x0c, }, .prescaler = 1, + .supports_polarity = true, .ops = &rockchip_pwm_ops_v2, .set_enable = rockchip_pwm_set_enable_v2, + .get_state = rockchip_pwm_get_state_v2, }; static const struct rockchip_pwm_data pwm_data_vop = { @@ -217,8 +283,10 @@ static const struct rockchip_pwm_data pwm_data_vop = { .ctrl = 0x00, }, .prescaler = 1, + .supports_polarity = true, .ops = &rockchip_pwm_ops_v2, .set_enable = rockchip_pwm_set_enable_v2, + .get_state = rockchip_pwm_get_state_v2, }; static const struct of_device_id rockchip_pwm_dt_ids[] = { @@ -253,7 +321,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev) if (IS_ERR(pc->clk)) return PTR_ERR(pc->clk); - ret = clk_prepare(pc->clk); + ret = clk_prepare_enable(pc->clk); if (ret) return ret; @@ -265,7 +333,7 @@ static int rockchip_pwm_probe(struct platform_device *pdev) pc->chip.base = -1; pc->chip.npwm = 1; - if (pc->data->ops->set_polarity) { + if (pc->data->supports_polarity) { pc->chip.of_xlate = of_pwm_xlate_with_flags; pc->chip.of_pwm_n_cells = 3; } @@ -276,6 +344,10 @@ static int rockchip_pwm_probe(struct platform_device *pdev) dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); } + /* Keep the PWM clk enabled if the PWM appears to be up and running. */ + if (!pwm_is_enabled(pc->chip.pwms)) + clk_disable(pc->clk); + return ret; } @@ -283,6 +355,20 @@ static int rockchip_pwm_remove(struct platform_device *pdev) { struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev); + /* + * Disable the PWM clk before unpreparing it if the PWM device is still + * running. This should only happen when the last PWM user left it + * enabled, or when nobody requested a PWM that was previously enabled + * by the bootloader. + * + * FIXME: Maybe the core should disable all PWM devices in + * pwmchip_remove(). In this case we'd only have to call + * clk_unprepare() after pwmchip_remove(). + * + */ + if (pwm_is_enabled(pc->chip.pwms)) + clk_disable(pc->clk); + clk_unprepare(pc->clk); return pwmchip_remove(&pc->chip); diff --git a/drivers/pwm/pwm-stmpe.c b/drivers/pwm/pwm-stmpe.c new file mode 100644 index 0000000000000000000000000000000000000000..e464582a390a58ef68064a1cc925822d2ec95354 --- /dev/null +++ b/drivers/pwm/pwm-stmpe.c @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2016 Linaro Ltd. + * + * Author: Linus Walleij + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STMPE24XX_PWMCS 0x30 +#define PWMCS_EN_PWM0 BIT(0) +#define PWMCS_EN_PWM1 BIT(1) +#define PWMCS_EN_PWM2 BIT(2) +#define STMPE24XX_PWMIC0 0x38 +#define STMPE24XX_PWMIC1 0x39 +#define STMPE24XX_PWMIC2 0x3a + +#define STMPE_PWM_24XX_PINBASE 21 + +struct stmpe_pwm { + struct stmpe *stmpe; + struct pwm_chip chip; + u8 last_duty; +}; + +static inline struct stmpe_pwm *to_stmpe_pwm(struct pwm_chip *chip) +{ + return container_of(chip, struct stmpe_pwm, chip); +} + +static int stmpe_24xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip); + u8 value; + int ret; + + ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS); + if (ret < 0) { + dev_err(chip->dev, "error reading PWM#%u control\n", + pwm->hwpwm); + return ret; + } + + value = ret | BIT(pwm->hwpwm); + + ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value); + if (ret) { + dev_err(chip->dev, "error writing PWM#%u control\n", + pwm->hwpwm); + return ret; + } + + return 0; +} + +static void stmpe_24xx_pwm_disable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip); + u8 value; + int ret; + + ret = stmpe_reg_read(stmpe_pwm->stmpe, STMPE24XX_PWMCS); + if (ret < 0) { + dev_err(chip->dev, "error reading PWM#%u control\n", + pwm->hwpwm); + return; + } + + value = ret & ~BIT(pwm->hwpwm); + + ret = stmpe_reg_write(stmpe_pwm->stmpe, STMPE24XX_PWMCS, value); + if (ret) { + dev_err(chip->dev, "error writing PWM#%u control\n", + pwm->hwpwm); + return; + } +} + +/* STMPE 24xx PWM instructions */ +#define SMAX 0x007f +#define SMIN 0x00ff +#define GTS 0x0000 +#define LOAD BIT(14) /* Only available on 2403 */ +#define RAMPUP 0x0000 +#define RAMPDOWN BIT(7) +#define PRESCALE_512 BIT(14) +#define STEPTIME_1 BIT(8) +#define BRANCH (BIT(15) | BIT(13)) + +static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct stmpe_pwm *stmpe_pwm = to_stmpe_pwm(chip); + unsigned int i, pin; + u16 program[3] = { + SMAX, + GTS, + GTS, + }; + u8 offset; + int ret; + + /* Make sure we are disabled */ + if (pwm_is_enabled(pwm)) { + stmpe_24xx_pwm_disable(chip, pwm); + } else { + /* Connect the PWM to the pin */ + pin = pwm->hwpwm; + + /* On STMPE2401 and 2403 pins 21,22,23 are used */ + if (stmpe_pwm->stmpe->partnum == STMPE2401 || + stmpe_pwm->stmpe->partnum == STMPE2403) + pin += STMPE_PWM_24XX_PINBASE; + + ret = stmpe_set_altfunc(stmpe_pwm->stmpe, BIT(pin), + STMPE_BLOCK_PWM); + if (ret) { + dev_err(chip->dev, "unable to connect PWM#%u to pin\n", + pwm->hwpwm); + return ret; + } + } + + /* STMPE24XX */ + switch (pwm->hwpwm) { + case 0: + offset = STMPE24XX_PWMIC0; + break; + + case 1: + offset = STMPE24XX_PWMIC1; + break; + + case 2: + offset = STMPE24XX_PWMIC1; + break; + + default: + /* Should not happen as npwm is 3 */ + return -ENODEV; + } + + dev_dbg(chip->dev, "PWM#%u: config duty %d ns, period %d ns\n", + pwm->hwpwm, duty_ns, period_ns); + + if (duty_ns == 0) { + if (stmpe_pwm->stmpe->partnum == STMPE2401) + program[0] = SMAX; /* off all the time */ + + if (stmpe_pwm->stmpe->partnum == STMPE2403) + program[0] = LOAD | 0xff; /* LOAD 0xff */ + + stmpe_pwm->last_duty = 0x00; + } else if (duty_ns == period_ns) { + if (stmpe_pwm->stmpe->partnum == STMPE2401) + program[0] = SMIN; /* on all the time */ + + if (stmpe_pwm->stmpe->partnum == STMPE2403) + program[0] = LOAD | 0x00; /* LOAD 0x00 */ + + stmpe_pwm->last_duty = 0xff; + } else { + u8 value, last = stmpe_pwm->last_duty; + unsigned long duty; + + /* + * Counter goes from 0x00 to 0xff repeatedly at 32768 Hz, + * (means a period of 30517 ns) then this is compared to the + * counter from the ramp, if this is >= PWM counter the output + * is high. With LOAD we can define how much of the cycle it + * is on. + * + * Prescale = 0 -> 2 kHz -> T = 1/f = 488281.25 ns + */ + + /* Scale to 0..0xff */ + duty = duty_ns * 256; + duty = DIV_ROUND_CLOSEST(duty, period_ns); + value = duty; + + if (value == last) { + /* Run the old program */ + if (pwm_is_enabled(pwm)) + stmpe_24xx_pwm_enable(chip, pwm); + + return 0; + } else if (stmpe_pwm->stmpe->partnum == STMPE2403) { + /* STMPE2403 can simply set the right PWM value */ + program[0] = LOAD | value; + program[1] = 0x0000; + } else if (stmpe_pwm->stmpe->partnum == STMPE2401) { + /* STMPE2401 need a complex program */ + u16 incdec = 0x0000; + + if (last < value) + /* Count up */ + incdec = RAMPUP | (value - last); + else + /* Count down */ + incdec = RAMPDOWN | (last - value); + + /* Step to desired value, smoothly */ + program[0] = PRESCALE_512 | STEPTIME_1 | incdec; + + /* Loop eternally to 0x00 */ + program[1] = BRANCH; + } + + dev_dbg(chip->dev, + "PWM#%u: value = %02x, last_duty = %02x, program=%04x,%04x,%04x\n", + pwm->hwpwm, value, last, program[0], program[1], + program[2]); + stmpe_pwm->last_duty = value; + } + + /* + * We can write programs of up to 64 16-bit words into this channel. + */ + for (i = 0; i < ARRAY_SIZE(program); i++) { + u8 value; + + value = (program[i] >> 8) & 0xff; + + ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value); + if (ret) { + dev_err(chip->dev, "error writing register %02x: %d\n", + offset, ret); + return ret; + } + + value = program[i] & 0xff; + + ret = stmpe_reg_write(stmpe_pwm->stmpe, offset, value); + if (ret) { + dev_err(chip->dev, "error writing register %02x: %d\n", + offset, ret); + return ret; + } + } + + /* If we were enabled, re-enable this PWM */ + if (pwm_is_enabled(pwm)) + stmpe_24xx_pwm_enable(chip, pwm); + + /* Sleep for 200ms so we're sure it will take effect */ + msleep(200); + + dev_dbg(chip->dev, "programmed PWM#%u, %u bytes\n", pwm->hwpwm, i); + + return 0; +} + +static const struct pwm_ops stmpe_24xx_pwm_ops = { + .config = stmpe_24xx_pwm_config, + .enable = stmpe_24xx_pwm_enable, + .disable = stmpe_24xx_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __init stmpe_pwm_probe(struct platform_device *pdev) +{ + struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent); + struct stmpe_pwm *pwm; + int ret; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + pwm->stmpe = stmpe; + pwm->chip.dev = &pdev->dev; + pwm->chip.base = -1; + + if (stmpe->partnum == STMPE2401 || stmpe->partnum == STMPE2403) { + pwm->chip.ops = &stmpe_24xx_pwm_ops; + pwm->chip.npwm = 3; + } else { + if (stmpe->partnum == STMPE1601) + dev_err(&pdev->dev, "STMPE1601 not yet supported\n"); + else + dev_err(&pdev->dev, "Unknown STMPE PWM\n"); + + return -ENODEV; + } + + ret = stmpe_enable(stmpe, STMPE_BLOCK_PWM); + if (ret) + return ret; + + ret = pwmchip_add(&pwm->chip); + if (ret) { + stmpe_disable(stmpe, STMPE_BLOCK_PWM); + return ret; + } + + platform_set_drvdata(pdev, pwm); + + return 0; +} + +static struct platform_driver stmpe_pwm_driver = { + .driver = { + .name = "stmpe-pwm", + }, +}; +builtin_platform_driver_probe(stmpe_pwm_driver, stmpe_pwm_probe); diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index d4de0607b502a87c03498f7362b731dd44f06d50..e4647840cd6e3129fb94da036e30470714852f14 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -26,9 +26,11 @@ #include #include #include +#include #include #include #include +#include #define PWM_ENABLE (1 << 31) #define PWM_DUTY_WIDTH 8 @@ -36,15 +38,20 @@ #define PWM_SCALE_WIDTH 13 #define PWM_SCALE_SHIFT 0 -#define NUM_PWM 4 +struct tegra_pwm_soc { + unsigned int num_channels; +}; struct tegra_pwm_chip { - struct pwm_chip chip; - struct device *dev; + struct pwm_chip chip; + struct device *dev; + + struct clk *clk; + struct reset_control*rst; - struct clk *clk; + void __iomem *regs; - void __iomem *mmio_base; + const struct tegra_pwm_soc *soc; }; static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip) @@ -54,20 +61,20 @@ static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip) static inline u32 pwm_readl(struct tegra_pwm_chip *chip, unsigned int num) { - return readl(chip->mmio_base + (num << 4)); + return readl(chip->regs + (num << 4)); } static inline void pwm_writel(struct tegra_pwm_chip *chip, unsigned int num, unsigned long val) { - writel(val, chip->mmio_base + (num << 4)); + writel(val, chip->regs + (num << 4)); } static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); - unsigned long long c; + unsigned long long c = duty_ns; unsigned long rate, hz; u32 val = 0; int err; @@ -77,7 +84,8 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, * per (1 << PWM_DUTY_WIDTH) cycles and make sure to round to the * nearest integer during division. */ - c = duty_ns * ((1 << PWM_DUTY_WIDTH) - 1) + period_ns / 2; + c *= (1 << PWM_DUTY_WIDTH); + c += period_ns / 2; do_div(c, period_ns); val = (u32)c << PWM_DUTY_SHIFT; @@ -176,12 +184,13 @@ static int tegra_pwm_probe(struct platform_device *pdev) if (!pwm) return -ENOMEM; + pwm->soc = of_device_get_match_data(&pdev->dev); pwm->dev = &pdev->dev; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(pwm->mmio_base)) - return PTR_ERR(pwm->mmio_base); + pwm->regs = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(pwm->regs)) + return PTR_ERR(pwm->regs); platform_set_drvdata(pdev, pwm); @@ -189,14 +198,24 @@ static int tegra_pwm_probe(struct platform_device *pdev) if (IS_ERR(pwm->clk)) return PTR_ERR(pwm->clk); + pwm->rst = devm_reset_control_get(&pdev->dev, "pwm"); + if (IS_ERR(pwm->rst)) { + ret = PTR_ERR(pwm->rst); + dev_err(&pdev->dev, "Reset control is not found: %d\n", ret); + return ret; + } + + reset_control_deassert(pwm->rst); + pwm->chip.dev = &pdev->dev; pwm->chip.ops = &tegra_pwm_ops; pwm->chip.base = -1; - pwm->chip.npwm = NUM_PWM; + pwm->chip.npwm = pwm->soc->num_channels; ret = pwmchip_add(&pwm->chip); if (ret < 0) { dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + reset_control_assert(pwm->rst); return ret; } @@ -206,12 +225,17 @@ static int tegra_pwm_probe(struct platform_device *pdev) static int tegra_pwm_remove(struct platform_device *pdev) { struct tegra_pwm_chip *pc = platform_get_drvdata(pdev); - int i; + unsigned int i; + int err; if (WARN_ON(!pc)) return -ENODEV; - for (i = 0; i < NUM_PWM; i++) { + err = clk_prepare_enable(pc->clk); + if (err < 0) + return err; + + for (i = 0; i < pc->chip.npwm; i++) { struct pwm_device *pwm = &pc->chip.pwms[i]; if (!pwm_is_enabled(pwm)) @@ -223,12 +247,23 @@ static int tegra_pwm_remove(struct platform_device *pdev) clk_disable_unprepare(pc->clk); } + reset_control_assert(pc->rst); + clk_disable_unprepare(pc->clk); + return pwmchip_remove(&pc->chip); } +static const struct tegra_pwm_soc tegra20_pwm_soc = { + .num_channels = 4, +}; + +static const struct tegra_pwm_soc tegra186_pwm_soc = { + .num_channels = 1, +}; + static const struct of_device_id tegra_pwm_of_match[] = { - { .compatible = "nvidia,tegra20-pwm" }, - { .compatible = "nvidia,tegra30-pwm" }, + { .compatible = "nvidia,tegra20-pwm", .data = &tegra20_pwm_soc }, + { .compatible = "nvidia,tegra186-pwm", .data = &tegra186_pwm_soc }, { } }; diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c index 616af764a27682ed0301e897ab731f5ffe8c5a13..6ec342dd3eea48ec71be46e698ab87ba9e70433f 100644 --- a/drivers/pwm/pwm-tiecap.c +++ b/drivers/pwm/pwm-tiecap.c @@ -27,8 +27,6 @@ #include #include -#include "pwm-tipwmss.h" - /* ECAP registers and bits definitions */ #define CAP1 0x08 #define CAP2 0x0C @@ -195,6 +193,7 @@ static const struct pwm_ops ecap_pwm_ops = { }; static const struct of_device_id ecap_of_match[] = { + { .compatible = "ti,am3352-ecap" }, { .compatible = "ti,am33xx-ecap" }, {}, }; @@ -202,17 +201,24 @@ MODULE_DEVICE_TABLE(of, ecap_of_match); static int ecap_pwm_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; int ret; struct resource *r; struct clk *clk; struct ecap_pwm_chip *pc; - u16 status; pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); if (!pc) return -ENOMEM; clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(clk)) { + if (of_device_is_compatible(np, "ti,am33xx-ecap")) { + dev_warn(&pdev->dev, "Binding is obsolete.\n"); + clk = devm_clk_get(pdev->dev.parent, "fck"); + } + } + if (IS_ERR(clk)) { dev_err(&pdev->dev, "failed to get clock\n"); return PTR_ERR(clk); @@ -243,40 +249,15 @@ static int ecap_pwm_probe(struct platform_device *pdev) } pm_runtime_enable(&pdev->dev); - pm_runtime_get_sync(&pdev->dev); - - status = pwmss_submodule_state_change(pdev->dev.parent, - PWMSS_ECAPCLK_EN); - if (!(status & PWMSS_ECAPCLK_EN_ACK)) { - dev_err(&pdev->dev, "PWMSS config space clock enable failed\n"); - ret = -EINVAL; - goto pwmss_clk_failure; - } - - pm_runtime_put_sync(&pdev->dev); platform_set_drvdata(pdev, pc); return 0; - -pwmss_clk_failure: - pm_runtime_put_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); - pwmchip_remove(&pc->chip); - return ret; } static int ecap_pwm_remove(struct platform_device *pdev) { struct ecap_pwm_chip *pc = platform_get_drvdata(pdev); - pm_runtime_get_sync(&pdev->dev); - /* - * Due to hardware misbehaviour, acknowledge of the stop_req - * is missing. Hence checking of the status bit skipped. - */ - pwmss_submodule_state_change(pdev->dev.parent, PWMSS_ECAPCLK_STOP_REQ); - pm_runtime_put_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); return pwmchip_remove(&pc->chip); } diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index 6a41e66015b67fa84865891cbc3e97e12de6539f..b5c6b06368934044f36be06b11051290d6bfdad7 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -27,8 +27,6 @@ #include #include -#include "pwm-tipwmss.h" - /* EHRPWM registers and bits definitions */ /* Time base module registers */ @@ -426,6 +424,7 @@ static const struct pwm_ops ehrpwm_pwm_ops = { }; static const struct of_device_id ehrpwm_of_match[] = { + { .compatible = "ti,am3352-ehrpwm" }, { .compatible = "ti,am33xx-ehrpwm" }, {}, }; @@ -433,17 +432,24 @@ MODULE_DEVICE_TABLE(of, ehrpwm_of_match); static int ehrpwm_pwm_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; int ret; struct resource *r; struct clk *clk; struct ehrpwm_pwm_chip *pc; - u16 status; pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); if (!pc) return -ENOMEM; clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(clk)) { + if (of_device_is_compatible(np, "ti,am33xx-ecap")) { + dev_warn(&pdev->dev, "Binding is obsolete.\n"); + clk = devm_clk_get(pdev->dev.parent, "fck"); + } + } + if (IS_ERR(clk)) { dev_err(&pdev->dev, "failed to get clock\n"); return PTR_ERR(clk); @@ -487,27 +493,9 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev) } pm_runtime_enable(&pdev->dev); - pm_runtime_get_sync(&pdev->dev); - - status = pwmss_submodule_state_change(pdev->dev.parent, - PWMSS_EPWMCLK_EN); - if (!(status & PWMSS_EPWMCLK_EN_ACK)) { - dev_err(&pdev->dev, "PWMSS config space clock enable failed\n"); - ret = -EINVAL; - goto pwmss_clk_failure; - } - - pm_runtime_put_sync(&pdev->dev); platform_set_drvdata(pdev, pc); return 0; - -pwmss_clk_failure: - pm_runtime_put_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); - pwmchip_remove(&pc->chip); - clk_unprepare(pc->tbclk); - return ret; } static int ehrpwm_pwm_remove(struct platform_device *pdev) @@ -516,14 +504,6 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev) clk_unprepare(pc->tbclk); - pm_runtime_get_sync(&pdev->dev); - /* - * Due to hardware misbehaviour, acknowledge of the stop_req - * is missing. Hence checking of the status bit skipped. - */ - pwmss_submodule_state_change(pdev->dev.parent, PWMSS_EPWMCLK_STOP_REQ); - pm_runtime_put_sync(&pdev->dev); - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); return pwmchip_remove(&pc->chip); diff --git a/drivers/pwm/pwm-tipwmss.c b/drivers/pwm/pwm-tipwmss.c index 5cf65a15d02191b3b8400713133470d5bcc303f8..829f4991c96f5e8a5a6c9af4d3eef76dde2f25e2 100644 --- a/drivers/pwm/pwm-tipwmss.c +++ b/drivers/pwm/pwm-tipwmss.c @@ -22,32 +22,6 @@ #include #include -#include "pwm-tipwmss.h" - -#define PWMSS_CLKCONFIG 0x8 /* Clock gating reg */ -#define PWMSS_CLKSTATUS 0xc /* Clock gating status reg */ - -struct pwmss_info { - void __iomem *mmio_base; - struct mutex pwmss_lock; - u16 pwmss_clkconfig; -}; - -u16 pwmss_submodule_state_change(struct device *dev, int set) -{ - struct pwmss_info *info = dev_get_drvdata(dev); - u16 val; - - mutex_lock(&info->pwmss_lock); - val = readw(info->mmio_base + PWMSS_CLKCONFIG); - val |= set; - writew(val , info->mmio_base + PWMSS_CLKCONFIG); - mutex_unlock(&info->pwmss_lock); - - return readw(info->mmio_base + PWMSS_CLKSTATUS); -} -EXPORT_SYMBOL(pwmss_submodule_state_change); - static const struct of_device_id pwmss_of_match[] = { { .compatible = "ti,am33xx-pwmss" }, {}, @@ -57,24 +31,10 @@ MODULE_DEVICE_TABLE(of, pwmss_of_match); static int pwmss_probe(struct platform_device *pdev) { int ret; - struct resource *r; - struct pwmss_info *info; struct device_node *node = pdev->dev.of_node; - info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); - if (!info) - return -ENOMEM; - - mutex_init(&info->pwmss_lock); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - info->mmio_base = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(info->mmio_base)) - return PTR_ERR(info->mmio_base); - pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); - platform_set_drvdata(pdev, info); /* Populate all the child nodes here... */ ret = of_platform_populate(node, NULL, NULL, &pdev->dev); @@ -86,30 +46,21 @@ static int pwmss_probe(struct platform_device *pdev) static int pwmss_remove(struct platform_device *pdev) { - struct pwmss_info *info = platform_get_drvdata(pdev); - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); - mutex_destroy(&info->pwmss_lock); return 0; } #ifdef CONFIG_PM_SLEEP static int pwmss_suspend(struct device *dev) { - struct pwmss_info *info = dev_get_drvdata(dev); - - info->pwmss_clkconfig = readw(info->mmio_base + PWMSS_CLKCONFIG); pm_runtime_put_sync(dev); return 0; } static int pwmss_resume(struct device *dev) { - struct pwmss_info *info = dev_get_drvdata(dev); - pm_runtime_get_sync(dev); - writew(info->pwmss_clkconfig, info->mmio_base + PWMSS_CLKCONFIG); return 0; } #endif diff --git a/drivers/pwm/pwm-tipwmss.h b/drivers/pwm/pwm-tipwmss.h deleted file mode 100644 index 10ad8040408bc944034f2e5314f1e30af0a9ca36..0000000000000000000000000000000000000000 --- a/drivers/pwm/pwm-tipwmss.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * TI PWM Subsystem driver - * - * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef __TIPWMSS_H -#define __TIPWMSS_H - -/* PWM substem clock gating */ -#define PWMSS_ECAPCLK_EN BIT(0) -#define PWMSS_ECAPCLK_STOP_REQ BIT(1) -#define PWMSS_EPWMCLK_EN BIT(8) -#define PWMSS_EPWMCLK_STOP_REQ BIT(9) - -#define PWMSS_ECAPCLK_EN_ACK BIT(0) -#define PWMSS_EPWMCLK_EN_ACK BIT(8) - -#ifdef CONFIG_PWM_TIPWMSS -extern u16 pwmss_submodule_state_change(struct device *dev, int set); -#else -static inline u16 pwmss_submodule_state_change(struct device *dev, int set) -{ - /* return success status value */ - return 0xFFFF; -} -#endif -#endif /* __TIPWMSS_H */ diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c index 01695d48dd543152624b9ed40a9c3afd9de45d0d..18ed725594c316ab71edafdafb5e1912714de7f1 100644 --- a/drivers/pwm/sysfs.c +++ b/drivers/pwm/sysfs.c @@ -208,16 +208,33 @@ static ssize_t polarity_store(struct device *child, return ret ? : size; } +static ssize_t capture_show(struct device *child, + struct device_attribute *attr, + char *buf) +{ + struct pwm_device *pwm = child_to_pwm_device(child); + struct pwm_capture result; + int ret; + + ret = pwm_capture(pwm, &result, jiffies_to_msecs(HZ)); + if (ret) + return ret; + + return sprintf(buf, "%u %u\n", result.period, result.duty_cycle); +} + static DEVICE_ATTR_RW(period); static DEVICE_ATTR_RW(duty_cycle); static DEVICE_ATTR_RW(enable); static DEVICE_ATTR_RW(polarity); +static DEVICE_ATTR_RO(capture); static struct attribute *pwm_attrs[] = { &dev_attr_period.attr, &dev_attr_duty_cycle.attr, &dev_attr_enable.attr, &dev_attr_polarity.attr, + &dev_attr_capture.attr, NULL }; ATTRIBUTE_GROUPS(pwm); diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c index 666bc3bb52ef83790df5df9afab68306a39d766a..c24524242da2163a54d3a308bccb7912802a61ce 100644 --- a/drivers/regulator/pwm-regulator.c +++ b/drivers/regulator/pwm-regulator.c @@ -22,6 +22,12 @@ #include #include +struct pwm_continuous_reg_data { + unsigned int min_uV_dutycycle; + unsigned int max_uV_dutycycle; + unsigned int dutycycle_unit; +}; + struct pwm_regulator_data { /* Shared */ struct pwm_device *pwm; @@ -29,6 +35,9 @@ struct pwm_regulator_data { /* Voltage table */ struct pwm_voltages *duty_cycle_table; + /* Continuous mode info */ + struct pwm_continuous_reg_data continuous; + /* regulator descriptor */ struct regulator_desc desc; @@ -37,9 +46,6 @@ struct pwm_regulator_data { int state; - /* Continuous voltage */ - int volt_uV; - /* Enable GPIO */ struct gpio_desc *enb_gpio; }; @@ -52,10 +58,31 @@ struct pwm_voltages { /** * Voltage table call-backs */ +static void pwm_regulator_init_state(struct regulator_dev *rdev) +{ + struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + struct pwm_state pwm_state; + unsigned int dutycycle; + int i; + + pwm_get_state(drvdata->pwm, &pwm_state); + dutycycle = pwm_get_relative_duty_cycle(&pwm_state, 100); + + for (i = 0; i < rdev->desc->n_voltages; i++) { + if (dutycycle == drvdata->duty_cycle_table[i].dutycycle) { + drvdata->state = i; + return; + } + } +} + static int pwm_regulator_get_voltage_sel(struct regulator_dev *rdev) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + if (drvdata->state < 0) + pwm_regulator_init_state(rdev); + return drvdata->state; } @@ -63,16 +90,14 @@ static int pwm_regulator_set_voltage_sel(struct regulator_dev *rdev, unsigned selector) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); - struct pwm_args pargs; - int dutycycle; + struct pwm_state pstate; int ret; - pwm_get_args(drvdata->pwm, &pargs); - - dutycycle = (pargs.period * - drvdata->duty_cycle_table[selector].dutycycle) / 100; + pwm_init_state(drvdata->pwm, &pstate); + pwm_set_relative_duty_cycle(&pstate, + drvdata->duty_cycle_table[selector].dutycycle, 100); - ret = pwm_config(drvdata->pwm, dutycycle, pargs.period); + ret = pwm_apply_state(drvdata->pwm, &pstate); if (ret) { dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret); return ret; @@ -129,57 +154,90 @@ static int pwm_regulator_is_enabled(struct regulator_dev *dev) static int pwm_regulator_get_voltage(struct regulator_dev *rdev) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + unsigned int min_uV_duty = drvdata->continuous.min_uV_dutycycle; + unsigned int max_uV_duty = drvdata->continuous.max_uV_dutycycle; + unsigned int duty_unit = drvdata->continuous.dutycycle_unit; + int min_uV = rdev->constraints->min_uV; + int max_uV = rdev->constraints->max_uV; + int diff_uV = max_uV - min_uV; + struct pwm_state pstate; + unsigned int diff_duty; + unsigned int voltage; + + pwm_get_state(drvdata->pwm, &pstate); + + voltage = pwm_get_relative_duty_cycle(&pstate, duty_unit); + + /* + * The dutycycle for min_uV might be greater than the one for max_uV. + * This is happening when the user needs an inversed polarity, but the + * PWM device does not support inversing it in hardware. + */ + if (max_uV_duty < min_uV_duty) { + voltage = min_uV_duty - voltage; + diff_duty = min_uV_duty - max_uV_duty; + } else { + voltage = voltage - min_uV_duty; + diff_duty = max_uV_duty - min_uV_duty; + } - return drvdata->volt_uV; + voltage = DIV_ROUND_CLOSEST_ULL((u64)voltage * diff_uV, diff_duty); + + return voltage + min_uV; } static int pwm_regulator_set_voltage(struct regulator_dev *rdev, - int min_uV, int max_uV, - unsigned *selector) + int req_min_uV, int req_max_uV, + unsigned int *selector) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + unsigned int min_uV_duty = drvdata->continuous.min_uV_dutycycle; + unsigned int max_uV_duty = drvdata->continuous.max_uV_dutycycle; + unsigned int duty_unit = drvdata->continuous.dutycycle_unit; unsigned int ramp_delay = rdev->constraints->ramp_delay; - struct pwm_args pargs; - unsigned int req_diff = min_uV - rdev->constraints->min_uV; - unsigned int diff; - unsigned int duty_pulse; - u64 req_period; - u32 rem; + int min_uV = rdev->constraints->min_uV; + int max_uV = rdev->constraints->max_uV; + int diff_uV = max_uV - min_uV; + struct pwm_state pstate; int old_uV = pwm_regulator_get_voltage(rdev); + unsigned int diff_duty; + unsigned int dutycycle; int ret; - pwm_get_args(drvdata->pwm, &pargs); - diff = rdev->constraints->max_uV - rdev->constraints->min_uV; + pwm_init_state(drvdata->pwm, &pstate); - /* First try to find out if we get the iduty cycle time which is - * factor of PWM period time. If (request_diff_to_min * pwm_period) - * is perfect divided by voltage_range_diff then it is possible to - * get duty cycle time which is factor of PWM period. This will help - * to get output voltage nearer to requested value as there is no - * calculation loss. + /* + * The dutycycle for min_uV might be greater than the one for max_uV. + * This is happening when the user needs an inversed polarity, but the + * PWM device does not support inversing it in hardware. */ - req_period = req_diff * pargs.period; - div_u64_rem(req_period, diff, &rem); - if (!rem) { - do_div(req_period, diff); - duty_pulse = (unsigned int)req_period; - } else { - duty_pulse = (pargs.period / 100) * ((req_diff * 100) / diff); - } + if (max_uV_duty < min_uV_duty) + diff_duty = min_uV_duty - max_uV_duty; + else + diff_duty = max_uV_duty - min_uV_duty; + + dutycycle = DIV_ROUND_CLOSEST_ULL((u64)(req_min_uV - min_uV) * + diff_duty, + diff_uV); + + if (max_uV_duty < min_uV_duty) + dutycycle = min_uV_duty - dutycycle; + else + dutycycle = min_uV_duty + dutycycle; + + pwm_set_relative_duty_cycle(&pstate, dutycycle, duty_unit); - ret = pwm_config(drvdata->pwm, duty_pulse, pargs.period); + ret = pwm_apply_state(drvdata->pwm, &pstate); if (ret) { dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret); return ret; } - drvdata->volt_uV = min_uV; - if ((ramp_delay == 0) || !pwm_regulator_is_enabled(rdev)) return 0; /* Ramp delay is in uV/uS. Adjust to uS and delay */ - ramp_delay = DIV_ROUND_UP(abs(min_uV - old_uV), ramp_delay); + ramp_delay = DIV_ROUND_UP(abs(req_min_uV - old_uV), ramp_delay); usleep_range(ramp_delay, ramp_delay + DIV_ROUND_UP(ramp_delay, 10)); return 0; @@ -239,6 +297,7 @@ static int pwm_regulator_init_table(struct platform_device *pdev, return ret; } + drvdata->state = -EINVAL; drvdata->duty_cycle_table = duty_cycle_table; memcpy(&drvdata->ops, &pwm_regulator_voltage_table_ops, sizeof(drvdata->ops)); @@ -251,11 +310,28 @@ static int pwm_regulator_init_table(struct platform_device *pdev, static int pwm_regulator_init_continuous(struct platform_device *pdev, struct pwm_regulator_data *drvdata) { + u32 dutycycle_range[2] = { 0, 100 }; + u32 dutycycle_unit = 100; + memcpy(&drvdata->ops, &pwm_regulator_voltage_continuous_ops, sizeof(drvdata->ops)); drvdata->desc.ops = &drvdata->ops; drvdata->desc.continuous_voltage_range = true; + of_property_read_u32_array(pdev->dev.of_node, + "pwm-dutycycle-range", + dutycycle_range, 2); + of_property_read_u32(pdev->dev.of_node, "pwm-dutycycle-unit", + &dutycycle_unit); + + if (dutycycle_range[0] > dutycycle_unit || + dutycycle_range[1] > dutycycle_unit) + return -EINVAL; + + drvdata->continuous.dutycycle_unit = dutycycle_unit; + drvdata->continuous.min_uV_dutycycle = dutycycle_range[0]; + drvdata->continuous.max_uV_dutycycle = dutycycle_range[1]; + return 0; } @@ -316,11 +392,9 @@ static int pwm_regulator_probe(struct platform_device *pdev) return ret; } - /* - * FIXME: pwm_apply_args() should be removed when switching to the - * atomic PWM API. - */ - pwm_apply_args(drvdata->pwm); + ret = pwm_adjust_config(drvdata->pwm); + if (ret) + return ret; regulator = devm_regulator_register(&pdev->dev, &drvdata->desc, &config); diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index 64184d27e3cd38cb7c2f47a3392df8518581e7b3..d641a18abacb9f1d15b65b1ff5420929a6bd9e92 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h @@ -225,6 +225,21 @@ int cros_ec_check_result(struct cros_ec_device *ec_dev, int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, struct cros_ec_command *msg); +/** + * cros_ec_cmd_xfer_status - Send a command to the ChromeOS EC + * + * This function is identical to cros_ec_cmd_xfer, except it returns success + * status only if both the command was transmitted successfully and the EC + * replied with success status. It's not necessary to check msg->result when + * using this function. + * + * @ec_dev: EC device + * @msg: Message to write + * @return: Num. of bytes transferred on success, <0 on failure + */ +int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg); + /** * cros_ec_remove - Remove a ChromeOS EC * diff --git a/include/linux/mfd/cros_ec_commands.h b/include/linux/mfd/cros_ec_commands.h index 13b630c10d4c07d16e958bff61f1c6f3787142dd..7e7a8d4b4551149c48007b95a2840fdaf0f0719f 100644 --- a/include/linux/mfd/cros_ec_commands.h +++ b/include/linux/mfd/cros_ec_commands.h @@ -949,6 +949,37 @@ struct ec_params_pwm_set_fan_duty { uint32_t percent; } __packed; +#define EC_CMD_PWM_SET_DUTY 0x25 +/* 16 bit duty cycle, 0xffff = 100% */ +#define EC_PWM_MAX_DUTY 0xffff + +enum ec_pwm_type { + /* All types, indexed by board-specific enum pwm_channel */ + EC_PWM_TYPE_GENERIC = 0, + /* Keyboard backlight */ + EC_PWM_TYPE_KB_LIGHT, + /* Display backlight */ + EC_PWM_TYPE_DISPLAY_LIGHT, + EC_PWM_TYPE_COUNT, +}; + +struct ec_params_pwm_set_duty { + uint16_t duty; /* Duty cycle, EC_PWM_MAX_DUTY = 100% */ + uint8_t pwm_type; /* ec_pwm_type */ + uint8_t index; /* Type-specific index, or 0 if unique */ +} __packed; + +#define EC_CMD_PWM_GET_DUTY 0x26 + +struct ec_params_pwm_get_duty { + uint8_t pwm_type; /* ec_pwm_type */ + uint8_t index; /* Type-specific index, or 0 if unique */ +} __packed; + +struct ec_response_pwm_get_duty { + uint16_t duty; /* Duty cycle, EC_PWM_MAX_DUTY = 100% */ +} __packed; + /*****************************************************************************/ /* * Lightbar commands. This looks worse than it is. Since we only use one HOST diff --git a/include/linux/pwm.h b/include/linux/pwm.h index c038ae36b10e7580645520d443d5faa8a792e645..f1bbae014889b67a219a73c1e76156aa53229024 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -5,7 +5,9 @@ #include #include +struct pwm_capture; struct seq_file; + struct pwm_chip; /** @@ -147,12 +149,101 @@ static inline void pwm_get_args(const struct pwm_device *pwm, *args = pwm->args; } +/** + * pwm_init_state() - prepare a new state to be applied with pwm_apply_state() + * @pwm: PWM device + * @state: state to fill with the prepared PWM state + * + * This functions prepares a state that can later be tweaked and applied + * to the PWM device with pwm_apply_state(). This is a convenient function + * that first retrieves the current PWM state and the replaces the period + * and polarity fields with the reference values defined in pwm->args. + * Once the function returns, you can adjust the ->enabled and ->duty_cycle + * fields according to your needs before calling pwm_apply_state(). + * + * ->duty_cycle is initially set to zero to avoid cases where the current + * ->duty_cycle value exceed the pwm_args->period one, which would trigger + * an error if the user calls pwm_apply_state() without adjusting ->duty_cycle + * first. + */ +static inline void pwm_init_state(const struct pwm_device *pwm, + struct pwm_state *state) +{ + struct pwm_args args; + + /* First get the current state. */ + pwm_get_state(pwm, state); + + /* Then fill it with the reference config */ + pwm_get_args(pwm, &args); + + state->period = args.period; + state->polarity = args.polarity; + state->duty_cycle = 0; +} + +/** + * pwm_get_relative_duty_cycle() - Get a relative duty cycle value + * @state: PWM state to extract the duty cycle from + * @scale: target scale of the relative duty cycle + * + * This functions converts the absolute duty cycle stored in @state (expressed + * in nanosecond) into a value relative to the period. + * + * For example if you want to get the duty_cycle expressed in percent, call: + * + * pwm_get_state(pwm, &state); + * duty = pwm_get_relative_duty_cycle(&state, 100); + */ +static inline unsigned int +pwm_get_relative_duty_cycle(const struct pwm_state *state, unsigned int scale) +{ + if (!state->period) + return 0; + + return DIV_ROUND_CLOSEST_ULL((u64)state->duty_cycle * scale, + state->period); +} + +/** + * pwm_set_relative_duty_cycle() - Set a relative duty cycle value + * @state: PWM state to fill + * @duty_cycle: relative duty cycle value + * @scale: scale in which @duty_cycle is expressed + * + * This functions converts a relative into an absolute duty cycle (expressed + * in nanoseconds), and puts the result in state->duty_cycle. + * + * For example if you want to configure a 50% duty cycle, call: + * + * pwm_init_state(pwm, &state); + * pwm_set_relative_duty_cycle(&state, 50, 100); + * pwm_apply_state(pwm, &state); + * + * This functions returns -EINVAL if @duty_cycle and/or @scale are + * inconsistent (@scale == 0 or @duty_cycle > @scale). + */ +static inline int +pwm_set_relative_duty_cycle(struct pwm_state *state, unsigned int duty_cycle, + unsigned int scale) +{ + if (!scale || duty_cycle > scale) + return -EINVAL; + + state->duty_cycle = DIV_ROUND_CLOSEST_ULL((u64)duty_cycle * + state->period, + scale); + + return 0; +} + /** * struct pwm_ops - PWM controller operations * @request: optional hook for requesting a PWM * @free: optional hook for freeing a PWM * @config: configure duty cycles and period length for this PWM * @set_polarity: configure the polarity of this PWM + * @capture: capture and report PWM signal * @enable: enable PWM output toggling * @disable: disable PWM output toggling * @apply: atomically apply a new PWM config. The state argument @@ -172,6 +263,8 @@ struct pwm_ops { int duty_ns, int period_ns); int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity); + int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_capture *result, unsigned long timeout); int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm); void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm); int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm, @@ -212,6 +305,16 @@ struct pwm_chip { bool can_sleep; }; +/** + * struct pwm_capture - PWM capture data + * @period: period of the PWM signal (in nanoseconds) + * @duty_cycle: duty cycle of the PWM signal (in nanoseconds) + */ +struct pwm_capture { + unsigned int period; + unsigned int duty_cycle; +}; + #if IS_ENABLED(CONFIG_PWM) /* PWM user APIs */ struct pwm_device *pwm_request(int pwm_id, const char *label); @@ -323,8 +426,9 @@ static inline void pwm_disable(struct pwm_device *pwm) pwm_apply_state(pwm, &state); } - /* PWM provider APIs */ +int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, + unsigned long timeout); int pwm_set_chip_data(struct pwm_device *pwm, void *data); void *pwm_get_chip_data(struct pwm_device *pwm); @@ -376,6 +480,13 @@ static inline int pwm_config(struct pwm_device *pwm, int duty_ns, return -EINVAL; } +static inline int pwm_capture(struct pwm_device *pwm, + struct pwm_capture *result, + unsigned long timeout) +{ + return -EINVAL; +} + static inline int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) {