Commit a157b3aa authored by Linus Torvalds's avatar Linus Torvalds

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

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

Pull pwm updates from Thierry Reding:
 "This set of changes improve some aspects of the atomic API as well as
  make use of this new API in the regulator framework to allow properly
  dealing with critical regulators controlled by a PWM.

  Aside from that there's a bunch of updates and cleanups for existing
  drivers, as well as the addition of new drivers for the Broadcom
  iProc, STMPE and ChromeOS EC controllers"

* tag 'pwm/for-4.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (44 commits)
  regulator: pwm: Document pwm-dutycycle-unit and pwm-dutycycle-range
  regulator: pwm: Support extra continuous mode cases
  pwm: Add ChromeOS EC PWM driver
  dt-bindings: pwm: Add binding for ChromeOS EC PWM
  mfd: cros_ec: Add EC_PWM function definitions
  mfd: cros_ec: Add cros_ec_cmd_xfer_status() helper
  pwm: atmel: Use of_device_get_match_data()
  pwm: atmel: Fix checkpatch warnings
  pwm: atmel: Fix disabling of PWM channels
  dt-bindings: pwm: Add R-Car H3 device tree bindings
  pwm: rcar: Use ARCH_RENESAS
  pwm: tegra: Add support for Tegra186
  dt-bindings: pwm: tegra: Add compatible string for Tegra186
  pwm: tegra: Avoid overflow when calculating duty cycle
  pwm: tegra: Allow 100 % duty cycle
  pwm: tegra: Add support for reset control
  pwm: tegra: Rename mmio_base to regs
  pwm: tegra: Remove useless padding
  pwm: tegra: Drop NUM_PWM macro
  pwm: lpc32xx: Set PWM_PIN_LEVEL bit to default value
  ...
parents 32199ec3 53de7c26
......@@ -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.
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>;
};
* 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>;
};
};
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.
......
......@@ -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.
......
== 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>;
};
......@@ -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:
......
......@@ -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);
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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)
......
/*
* 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");
/*
* 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;
}