diff mbox series

[v2,2/2] pwm: Add a driver for Chrome OS EC PWM

Message ID 20200926200851.4388-2-alpernebiyasak@gmail.com
State Superseded
Delegated to: Anatolij Gustschin
Headers show
Series [v2,1/2] video: backlight: Support PWMs without a known period_ns | expand

Commit Message

Alper Nebi Yasak Sept. 26, 2020, 8:08 p.m. UTC
This PWM is used in rk3399-gru-bob and rk3399-gru-kevin to control
the display brightness. We can only change the duty cycle, so on
set_config() we just try to match the duty cycle that dividing duty_ns
by period_ns gives us. To disable, we set the duty cycle to zero while
keeping the old value for when we want to re-enable it.

The cros_ec_set_pwm_duty() function is taken from Depthcharge's
cros_ec_set_bl_pwm_duty() but modified to use the generic pwm type.
The driver itself is very loosely based on rk_pwm.c for the general pwm
driver structure.

The devicetree binding file is from Linux, before it was converted to
YAML at 5df5a577a6b4 ("dt-bindings: pwm: Convert google,cros-ec-pwm.txt
to YAML format") in their repo.

Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
---
I'm testing on a rk3399-gru-kevin with a lot of other patches to get it
(and it's screen) barely working, but using something like the following
in a rk_board_late_init() added to gru.c gets the backlight to change:

    struct *udevice panel;
    uclass_first_device_err(UCLASS_PANEL, &panel);
    panel_enable_backlight(panel);
    panel_set_backlight(panel, 50);
    panel_set_backlight(panel, 100);

The actual tree I'm testing and how I'm getting it to chainload from the
vendor firmware (see commit message there) is available here:

    https://github.com/alpernebbi/u-boot/tree/rk3399-gru-kevin/wip
    (currently at commit 4b5a8441303facb6e419da9f5e86980de3eb090a)

Changes in v2:
- Add device-tree-binding/pwm/cros-ec-pwm.txt adapted from Linux
- Return ec_command() errors correctly in cros_ec_set_pwm_duty()
- Add newlines before last returns in cros_ec_pwm.c

v1: https://patchwork.ozlabs.org/project/uboot/patch/20200923165231.18188-2-alpernebiyasak@gmail.com/

 doc/device-tree-bindings/pwm/cros-ec-pwm.txt | 23 ++++++
 drivers/misc/cros_ec.c                       | 17 ++++
 drivers/pwm/Kconfig                          |  9 +++
 drivers/pwm/Makefile                         |  1 +
 drivers/pwm/cros_ec_pwm.c                    | 84 ++++++++++++++++++++
 include/cros_ec.h                            | 13 +++
 6 files changed, 147 insertions(+)
 create mode 100644 doc/device-tree-bindings/pwm/cros-ec-pwm.txt
 create mode 100644 drivers/pwm/cros_ec_pwm.c

Comments

Peter Robinson Sept. 27, 2020, 7:27 a.m. UTC | #1
On Sat, Sep 26, 2020 at 9:09 PM Alper Nebi Yasak
<alpernebiyasak@gmail.com> wrote:
>
> This PWM is used in rk3399-gru-bob and rk3399-gru-kevin to control
> the display brightness. We can only change the duty cycle, so on
> set_config() we just try to match the duty cycle that dividing duty_ns
> by period_ns gives us. To disable, we set the duty cycle to zero while
> keeping the old value for when we want to re-enable it.
>
> The cros_ec_set_pwm_duty() function is taken from Depthcharge's
> cros_ec_set_bl_pwm_duty() but modified to use the generic pwm type.
> The driver itself is very loosely based on rk_pwm.c for the general pwm
> driver structure.
>
> The devicetree binding file is from Linux, before it was converted to
> YAML at 5df5a577a6b4 ("dt-bindings: pwm: Convert google,cros-ec-pwm.txt
> to YAML format") in their repo.
>
> Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
> ---
> I'm testing on a rk3399-gru-kevin with a lot of other patches to get it
> (and it's screen) barely working, but using something like the following
> in a rk_board_late_init() added to gru.c gets the backlight to change:

A quick look at the Linux DT for the GRU Chromebooks and it looks like
the panels are also attached via EDP like on the Pinebook Pro so this
patch series adding initial support for the rk3399 EDP might be of use
here as well:

https://lists.denx.de/pipermail/u-boot/2020-September/427790.html

>     struct *udevice panel;
>     uclass_first_device_err(UCLASS_PANEL, &panel);
>     panel_enable_backlight(panel);
>     panel_set_backlight(panel, 50);
>     panel_set_backlight(panel, 100);
>
> The actual tree I'm testing and how I'm getting it to chainload from the
> vendor firmware (see commit message there) is available here:
>
>     https://github.com/alpernebbi/u-boot/tree/rk3399-gru-kevin/wip
>     (currently at commit 4b5a8441303facb6e419da9f5e86980de3eb090a)
>
> Changes in v2:
> - Add device-tree-binding/pwm/cros-ec-pwm.txt adapted from Linux
> - Return ec_command() errors correctly in cros_ec_set_pwm_duty()
> - Add newlines before last returns in cros_ec_pwm.c
>
> v1: https://patchwork.ozlabs.org/project/uboot/patch/20200923165231.18188-2-alpernebiyasak@gmail.com/
>
>  doc/device-tree-bindings/pwm/cros-ec-pwm.txt | 23 ++++++
>  drivers/misc/cros_ec.c                       | 17 ++++
>  drivers/pwm/Kconfig                          |  9 +++
>  drivers/pwm/Makefile                         |  1 +
>  drivers/pwm/cros_ec_pwm.c                    | 84 ++++++++++++++++++++
>  include/cros_ec.h                            | 13 +++
>  6 files changed, 147 insertions(+)
>  create mode 100644 doc/device-tree-bindings/pwm/cros-ec-pwm.txt
>  create mode 100644 drivers/pwm/cros_ec_pwm.c
>
> diff --git a/doc/device-tree-bindings/pwm/cros-ec-pwm.txt b/doc/device-tree-bindings/pwm/cros-ec-pwm.txt
> new file mode 100644
> index 0000000000..f198d08891
> --- /dev/null
> +++ b/doc/device-tree-bindings/pwm/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
> +doc/device-tree-bindings/misc/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/drivers/misc/cros_ec.c b/drivers/misc/cros_ec.c
> index a5534b1667..3be2dd6f14 100644
> --- a/drivers/misc/cros_ec.c
> +++ b/drivers/misc/cros_ec.c
> @@ -1110,6 +1110,23 @@ int cros_ec_battery_cutoff(struct udevice *dev, uint8_t flags)
>         return 0;
>  }
>
> +int cros_ec_set_pwm_duty(struct udevice *dev, uint8_t index, uint16_t duty)
> +{
> +       struct ec_params_pwm_set_duty p;
> +       int ret;
> +
> +       p.duty = duty;
> +       p.pwm_type = EC_PWM_TYPE_GENERIC;
> +       p.index = index;
> +
> +       ret = ec_command(dev, EC_CMD_PWM_SET_DUTY, 0, &p, sizeof(p),
> +                        NULL, 0);
> +       if (ret < 0)
> +               return ret;
> +
> +       return 0;
> +}
> +
>  int cros_ec_set_ldo(struct udevice *dev, uint8_t index, uint8_t state)
>  {
>         struct ec_params_ldo_set params;
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 61eb468cde..73b0e5ed1e 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -9,6 +9,15 @@ config DM_PWM
>           frequency/period can be controlled along with the proportion of that
>           time that the signal is high.
>
> +config PWM_CROS_EC
> +       bool "Enable support for the Chrome OS EC PWM"
> +       depends on DM_PWM
> +       help
> +         This PWM is found on several Chrome OS devices and controlled by
> +         the Chrome OS embedded controller. It may be used to control the
> +         screen brightness and/or the keyboard backlight depending on the
> +         device.
> +
>  config PWM_EXYNOS
>         bool "Enable support for the Exynos PWM"
>         depends on DM_PWM
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 0f4e84b04d..7af13538de 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -10,6 +10,7 @@
>
>  obj-$(CONFIG_DM_PWM)           += pwm-uclass.o
>
> +obj-$(CONFIG_PWM_CROS_EC)      += cros_ec_pwm.o
>  obj-$(CONFIG_PWM_EXYNOS)       += exynos_pwm.o
>  obj-$(CONFIG_PWM_IMX)          += pwm-imx.o pwm-imx-util.o
>  obj-$(CONFIG_PWM_MTK)          += pwm-mtk.o
> diff --git a/drivers/pwm/cros_ec_pwm.c b/drivers/pwm/cros_ec_pwm.c
> new file mode 100644
> index 0000000000..44f4105dfd
> --- /dev/null
> +++ b/drivers/pwm/cros_ec_pwm.c
> @@ -0,0 +1,84 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +
> +#include <common.h>
> +#include <cros_ec.h>
> +#include <dm.h>
> +#include <errno.h>
> +#include <log.h>
> +#include <pwm.h>
> +
> +struct cros_ec_pwm_priv {
> +       bool enabled;
> +       uint duty;
> +};
> +
> +static int cros_ec_pwm_set_config(struct udevice *dev, uint channel,
> +                                 uint period_ns, uint duty_ns)
> +{
> +       struct cros_ec_pwm_priv *priv = dev_get_priv(dev);
> +       uint duty;
> +       int ret;
> +
> +       debug("%s: period_ns=%u, duty_ns=%u asked\n", __func__,
> +             period_ns, duty_ns);
> +
> +       /* No way to set the period, only a relative duty cycle */
> +       duty = EC_PWM_MAX_DUTY * duty_ns / period_ns;
> +       if (duty > EC_PWM_MAX_DUTY)
> +               duty = EC_PWM_MAX_DUTY;
> +
> +       if (!priv->enabled) {
> +               priv->duty = duty;
> +               debug("%s: duty=%#x to-be-set\n", __func__, duty);
> +               return 0;
> +       }
> +
> +       ret = cros_ec_set_pwm_duty(dev->parent, channel, duty);
> +       if (ret) {
> +               debug("%s: duty=%#x failed\n", __func__, duty);
> +               return ret;
> +       }
> +
> +       priv->duty = duty;
> +       debug("%s: duty=%#x set\n", __func__, duty);
> +
> +       return 0;
> +}
> +
> +static int cros_ec_pwm_set_enable(struct udevice *dev, uint channel,
> +                                 bool enable)
> +{
> +       struct cros_ec_pwm_priv *priv = dev_get_priv(dev);
> +       int ret;
> +
> +       ret = cros_ec_set_pwm_duty(dev->parent, channel,
> +                                  enable ? priv->duty : 0);
> +       if (ret) {
> +               debug("%s: enable=%d failed\n", __func__, enable);
> +               return ret;
> +       }
> +
> +       priv->enabled = enable;
> +       debug("%s: enable=%d (duty=%#x) set\n", __func__,
> +             enable, priv->duty);
> +
> +       return 0;
> +}
> +
> +static const struct pwm_ops cros_ec_pwm_ops = {
> +       .set_config     = cros_ec_pwm_set_config,
> +       .set_enable     = cros_ec_pwm_set_enable,
> +};
> +
> +static const struct udevice_id cros_ec_pwm_ids[] = {
> +       { .compatible = "google,cros-ec-pwm" },
> +       { }
> +};
> +
> +U_BOOT_DRIVER(cros_ec_pwm) = {
> +       .name   = "cros_ec_pwm",
> +       .id     = UCLASS_PWM,
> +       .of_match = cros_ec_pwm_ids,
> +       .ops    = &cros_ec_pwm_ops,
> +       .priv_auto_alloc_size   = sizeof(struct cros_ec_pwm_priv),
> +};
> diff --git a/include/cros_ec.h b/include/cros_ec.h
> index f4b9b7a5c2..202e5c2c74 100644
> --- a/include/cros_ec.h
> +++ b/include/cros_ec.h
> @@ -443,6 +443,19 @@ int cros_ec_efs_verify(struct udevice *dev, enum ec_flash_region region);
>   */
>  int cros_ec_battery_cutoff(struct udevice *dev, uint8_t flags);
>
> +/**
> + * cros_ec_set_pwm_duty() - Set duty cycle of a generic pwm
> + *
> + * Note that duty value needs to be passed to the EC as a 16 bit number
> + * for increased precision.
> + *
> + * @param dev          CROS-EC device
> + * @param index                Index of the pwm
> + * @param duty         Desired duty cycle, in 0..EC_PWM_MAX_DUTY range.
> + * @return 0 if OK, -ve on error
> + */
> +int cros_ec_set_pwm_duty(struct udevice *dev, uint8_t index, uint16_t duty);
> +
>  /**
>   * cros_ec_read_limit_power() - Check if power is limited by batter/charger
>   *
> --
> 2.28.0
>
Arnaud Patard (Rtp) Sept. 27, 2020, 9:29 a.m. UTC | #2
Peter Robinson <pbrobinson@gmail.com> writes:

> On Sat, Sep 26, 2020 at 9:09 PM Alper Nebi Yasak
> <alpernebiyasak@gmail.com> wrote:
>>
>> This PWM is used in rk3399-gru-bob and rk3399-gru-kevin to control
>> the display brightness. We can only change the duty cycle, so on
>> set_config() we just try to match the duty cycle that dividing duty_ns
>> by period_ns gives us. To disable, we set the duty cycle to zero while
>> keeping the old value for when we want to re-enable it.
>>
>> The cros_ec_set_pwm_duty() function is taken from Depthcharge's
>> cros_ec_set_bl_pwm_duty() but modified to use the generic pwm type.
>> The driver itself is very loosely based on rk_pwm.c for the general pwm
>> driver structure.
>>
>> The devicetree binding file is from Linux, before it was converted to
>> YAML at 5df5a577a6b4 ("dt-bindings: pwm: Convert google,cros-ec-pwm.txt
>> to YAML format") in their repo.
>>
>> Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
>> ---
>> I'm testing on a rk3399-gru-kevin with a lot of other patches to get it
>> (and it's screen) barely working, but using something like the following
>> in a rk_board_late_init() added to gru.c gets the backlight to change:
>
> A quick look at the Linux DT for the GRU Chromebooks and it looks like
> the panels are also attached via EDP like on the Pinebook Pro so this
> patch series adding initial support for the rk3399 EDP might be of use
> here as well:
>
> https://lists.denx.de/pipermail/u-boot/2020-September/427790.html
>

If this platform has a screen on rk3399 edp, I'll be happy to get tests /
comments / ... of my patches on it. I've only a pinebook pro, so it's
hard to know if something is platform specific or not, I'm thinking of
the hack I'm using for resetting the display [1] on reboot but there may
be other things.

Arnaud


[1] http://people.hupstream.com/~rtp/pbp/20200911/hack-reset.patch
Alper Nebi Yasak Sept. 27, 2020, 3:49 p.m. UTC | #3
On 27/09/2020 12:29, Arnaud Patard (Rtp) wrote:
> Peter Robinson <pbrobinson@gmail.com> writes:
>> A quick look at the Linux DT for the GRU Chromebooks and it looks like
>> the panels are also attached via EDP like on the Pinebook Pro so this
>> patch series adding initial support for the rk3399 EDP might be of use
>> here as well:
>>
>> https://lists.denx.de/pipermail/u-boot/2020-September/427790.html
>>
> 
> If this platform has a screen on rk3399 edp, I'll be happy to get tests /
> comments / ... of my patches on it. I've only a pinebook pro, so it's
> hard to know if something is platform specific or not, I'm thinking of
> the hack I'm using for resetting the display [1] on reboot but there may
> be other things.

I was already including some version of your patches in my test builds.
I'm having a mixed experience with them (might be something wrong on my
side), I'll send a reply to that thread about my observations.
Simon Glass Sept. 28, 2020, 4:24 a.m. UTC | #4
On Sat, 26 Sep 2020 at 14:09, Alper Nebi Yasak <alpernebiyasak@gmail.com> wrote:
>
> This PWM is used in rk3399-gru-bob and rk3399-gru-kevin to control
> the display brightness. We can only change the duty cycle, so on
> set_config() we just try to match the duty cycle that dividing duty_ns
> by period_ns gives us. To disable, we set the duty cycle to zero while
> keeping the old value for when we want to re-enable it.
>
> The cros_ec_set_pwm_duty() function is taken from Depthcharge's
> cros_ec_set_bl_pwm_duty() but modified to use the generic pwm type.
> The driver itself is very loosely based on rk_pwm.c for the general pwm
> driver structure.
>
> The devicetree binding file is from Linux, before it was converted to
> YAML at 5df5a577a6b4 ("dt-bindings: pwm: Convert google,cros-ec-pwm.txt
> to YAML format") in their repo.
>
> Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
> ---
> I'm testing on a rk3399-gru-kevin with a lot of other patches to get it
> (and it's screen) barely working, but using something like the following
> in a rk_board_late_init() added to gru.c gets the backlight to change:
>
>     struct *udevice panel;
>     uclass_first_device_err(UCLASS_PANEL, &panel);
>     panel_enable_backlight(panel);
>     panel_set_backlight(panel, 50);
>     panel_set_backlight(panel, 100);
>
> The actual tree I'm testing and how I'm getting it to chainload from the
> vendor firmware (see commit message there) is available here:
>
>     https://github.com/alpernebbi/u-boot/tree/rk3399-gru-kevin/wip
>     (currently at commit 4b5a8441303facb6e419da9f5e86980de3eb090a)
>
> Changes in v2:
> - Add device-tree-binding/pwm/cros-ec-pwm.txt adapted from Linux
> - Return ec_command() errors correctly in cros_ec_set_pwm_duty()
> - Add newlines before last returns in cros_ec_pwm.c
>
> v1: https://patchwork.ozlabs.org/project/uboot/patch/20200923165231.18188-2-alpernebiyasak@gmail.com/
>
>  doc/device-tree-bindings/pwm/cros-ec-pwm.txt | 23 ++++++
>  drivers/misc/cros_ec.c                       | 17 ++++
>  drivers/pwm/Kconfig                          |  9 +++
>  drivers/pwm/Makefile                         |  1 +
>  drivers/pwm/cros_ec_pwm.c                    | 84 ++++++++++++++++++++
>  include/cros_ec.h                            | 13 +++
>  6 files changed, 147 insertions(+)
>  create mode 100644 doc/device-tree-bindings/pwm/cros-ec-pwm.txt
>  create mode 100644 drivers/pwm/cros_ec_pwm.c

Reviewed-by: Simon Glass <sjg@chromium.org>
diff mbox series

Patch

diff --git a/doc/device-tree-bindings/pwm/cros-ec-pwm.txt b/doc/device-tree-bindings/pwm/cros-ec-pwm.txt
new file mode 100644
index 0000000000..f198d08891
--- /dev/null
+++ b/doc/device-tree-bindings/pwm/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
+doc/device-tree-bindings/misc/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/drivers/misc/cros_ec.c b/drivers/misc/cros_ec.c
index a5534b1667..3be2dd6f14 100644
--- a/drivers/misc/cros_ec.c
+++ b/drivers/misc/cros_ec.c
@@ -1110,6 +1110,23 @@  int cros_ec_battery_cutoff(struct udevice *dev, uint8_t flags)
 	return 0;
 }
 
+int cros_ec_set_pwm_duty(struct udevice *dev, uint8_t index, uint16_t duty)
+{
+	struct ec_params_pwm_set_duty p;
+	int ret;
+
+	p.duty = duty;
+	p.pwm_type = EC_PWM_TYPE_GENERIC;
+	p.index = index;
+
+	ret = ec_command(dev, EC_CMD_PWM_SET_DUTY, 0, &p, sizeof(p),
+			 NULL, 0);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
 int cros_ec_set_ldo(struct udevice *dev, uint8_t index, uint8_t state)
 {
 	struct ec_params_ldo_set params;
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 61eb468cde..73b0e5ed1e 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -9,6 +9,15 @@  config DM_PWM
 	  frequency/period can be controlled along with the proportion of that
 	  time that the signal is high.
 
+config PWM_CROS_EC
+	bool "Enable support for the Chrome OS EC PWM"
+	depends on DM_PWM
+	help
+	  This PWM is found on several Chrome OS devices and controlled by
+	  the Chrome OS embedded controller. It may be used to control the
+	  screen brightness and/or the keyboard backlight depending on the
+	  device.
+
 config PWM_EXYNOS
 	bool "Enable support for the Exynos PWM"
 	depends on DM_PWM
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 0f4e84b04d..7af13538de 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -10,6 +10,7 @@ 
 
 obj-$(CONFIG_DM_PWM)		+= pwm-uclass.o
 
+obj-$(CONFIG_PWM_CROS_EC)	+= cros_ec_pwm.o
 obj-$(CONFIG_PWM_EXYNOS)	+= exynos_pwm.o
 obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o pwm-imx-util.o
 obj-$(CONFIG_PWM_MTK)		+= pwm-mtk.o
diff --git a/drivers/pwm/cros_ec_pwm.c b/drivers/pwm/cros_ec_pwm.c
new file mode 100644
index 0000000000..44f4105dfd
--- /dev/null
+++ b/drivers/pwm/cros_ec_pwm.c
@@ -0,0 +1,84 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <common.h>
+#include <cros_ec.h>
+#include <dm.h>
+#include <errno.h>
+#include <log.h>
+#include <pwm.h>
+
+struct cros_ec_pwm_priv {
+	bool enabled;
+	uint duty;
+};
+
+static int cros_ec_pwm_set_config(struct udevice *dev, uint channel,
+				  uint period_ns, uint duty_ns)
+{
+	struct cros_ec_pwm_priv *priv = dev_get_priv(dev);
+	uint duty;
+	int ret;
+
+	debug("%s: period_ns=%u, duty_ns=%u asked\n", __func__,
+	      period_ns, duty_ns);
+
+	/* No way to set the period, only a relative duty cycle */
+	duty = EC_PWM_MAX_DUTY * duty_ns / period_ns;
+	if (duty > EC_PWM_MAX_DUTY)
+		duty = EC_PWM_MAX_DUTY;
+
+	if (!priv->enabled) {
+		priv->duty = duty;
+		debug("%s: duty=%#x to-be-set\n", __func__, duty);
+		return 0;
+	}
+
+	ret = cros_ec_set_pwm_duty(dev->parent, channel, duty);
+	if (ret) {
+		debug("%s: duty=%#x failed\n", __func__, duty);
+		return ret;
+	}
+
+	priv->duty = duty;
+	debug("%s: duty=%#x set\n", __func__, duty);
+
+	return 0;
+}
+
+static int cros_ec_pwm_set_enable(struct udevice *dev, uint channel,
+				  bool enable)
+{
+	struct cros_ec_pwm_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = cros_ec_set_pwm_duty(dev->parent, channel,
+				   enable ? priv->duty : 0);
+	if (ret) {
+		debug("%s: enable=%d failed\n", __func__, enable);
+		return ret;
+	}
+
+	priv->enabled = enable;
+	debug("%s: enable=%d (duty=%#x) set\n", __func__,
+	      enable, priv->duty);
+
+	return 0;
+}
+
+static const struct pwm_ops cros_ec_pwm_ops = {
+	.set_config	= cros_ec_pwm_set_config,
+	.set_enable	= cros_ec_pwm_set_enable,
+};
+
+static const struct udevice_id cros_ec_pwm_ids[] = {
+	{ .compatible = "google,cros-ec-pwm" },
+	{ }
+};
+
+U_BOOT_DRIVER(cros_ec_pwm) = {
+	.name	= "cros_ec_pwm",
+	.id	= UCLASS_PWM,
+	.of_match = cros_ec_pwm_ids,
+	.ops	= &cros_ec_pwm_ops,
+	.priv_auto_alloc_size	= sizeof(struct cros_ec_pwm_priv),
+};
diff --git a/include/cros_ec.h b/include/cros_ec.h
index f4b9b7a5c2..202e5c2c74 100644
--- a/include/cros_ec.h
+++ b/include/cros_ec.h
@@ -443,6 +443,19 @@  int cros_ec_efs_verify(struct udevice *dev, enum ec_flash_region region);
  */
 int cros_ec_battery_cutoff(struct udevice *dev, uint8_t flags);
 
+/**
+ * cros_ec_set_pwm_duty() - Set duty cycle of a generic pwm
+ *
+ * Note that duty value needs to be passed to the EC as a 16 bit number
+ * for increased precision.
+ *
+ * @param dev		CROS-EC device
+ * @param index		Index of the pwm
+ * @param duty		Desired duty cycle, in 0..EC_PWM_MAX_DUTY range.
+ * @return 0 if OK, -ve on error
+ */
+int cros_ec_set_pwm_duty(struct udevice *dev, uint8_t index, uint16_t duty);
+
 /**
  * cros_ec_read_limit_power() - Check if power is limited by batter/charger
  *