Commit 8eca6b0a authored by Linus Torvalds's avatar Linus Torvalds

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

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

Pull pwm updates from Thierry Reding:
 "Quite a large number of conversions this time around, courtesy of Uwe
  who has been working tirelessly on these. No drivers of the legacy API
  are left at this point, so as a next step the old API can be removed.

  Support is added for a few new devices such as the Xilinx AXI timer-
  based PWMs and the PWM IP found on Sunplus SoCs.

  Other than that, there's a number of fixes, cleanups and optimizations"

* tag 'pwm/for-5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (43 commits)
  pwm: pwm-cros-ec: Add channel type support
  dt-bindings: google,cros-ec-pwm: Add the new -type compatible
  dt-bindings: Add mfd/cros_ec definitions
  pwm: Document that the pinstate of a disabled PWM isn't reliable
  pwm: twl-led: Implement .apply() callback
  pwm: lpc18xx: Implement .apply() callback
  pwm: mediatek: Implement .apply() callback
  pwm: lpc32xx: Implement .apply() callback
  pwm: tegra: Implement .apply() callback
  pwm: stmpe: Implement .apply() callback
  pwm: sti: Implement .apply() callback
  pwm: pwm-mediatek: Add support for MediaTek Helio X10 MT6795
  dt-bindings: pwm: pwm-mediatek: Add documentation for MT6795 SoC
  pwm: tegra: Optimize period calculation
  pwm: renesas-tpu: Improve precision of period and duty_cycle calculation
  pwm: renesas-tpu: Improve maths to compute register settings
  pwm: renesas-tpu: Rename variables to match the usual naming
  pwm: renesas-tpu: Implement .apply() callback
  pwm: renesas-tpu: Make use of devm functions
  pwm: renesas-tpu: Make use of dev_err_probe()
  ...
parents 68e6134b 3d593b6e
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
# Copyright (C) 2022 Microchip Technology, Inc. and its subsidiaries
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/atmel,at91sam-pwm.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Atmel/Microchip PWM controller
maintainers:
- Claudiu Beznea <claudiu.beznea@microchip.com>
allOf:
- $ref: "pwm.yaml#"
properties:
compatible:
oneOf:
- items:
- enum:
- atmel,at91sam9rl-pwm
- atmel,sama5d3-pwm
- atmel,sama5d2-pwm
- microchip,sam9x60-pwm
- items:
- const: microchip,sama7g5-pwm
- const: atmel,sama5d2-pwm
reg:
maxItems: 1
"#pwm-cells":
const: 3
required:
- compatible
- reg
unevaluatedProperties: false
examples:
- |
pwm0: pwm@f8034000 {
compatible = "atmel,at91sam9rl-pwm";
reg = <0xf8034000 0x400>;
#pwm-cells = <3>;
};
Atmel PWM controller
Required properties:
- compatible: should be one of:
- "atmel,at91sam9rl-pwm"
- "atmel,sama5d3-pwm"
- "atmel,sama5d2-pwm"
- "microchip,sam9x60-pwm"
- reg: physical base address and length of the controller's registers
- #pwm-cells: Should be 3. See pwm.yaml in this directory for a
description of the cells format.
Example:
pwm0: pwm@f8034000 {
compatible = "atmel,at91sam9rl-pwm";
reg = <0xf8034000 0x400>;
#pwm-cells = <3>;
};
pwmleds {
compatible = "pwm-leds";
d1 {
label = "d1";
pwms = <&pwm0 3 5000 0>
max-brightness = <255>;
};
d2 {
label = "d2";
pwms = <&pwm0 1 5000 1>
max-brightness = <255>;
};
};
......@@ -21,7 +21,14 @@ allOf:
properties:
compatible:
const: google,cros-ec-pwm
oneOf:
- description: PWM controlled using EC_PWM_TYPE_GENERIC channels.
items:
- const: google,cros-ec-pwm
- description: PWM controlled using CROS_EC_PWM_DT_<...> types.
items:
- const: google,cros-ec-pwm-type
"#pwm-cells":
description: The cell specifies the PWM index.
const: 1
......
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/mediatek,pwm-disp.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: MediaTek DISP_PWM Controller Device Tree Bindings
maintainers:
- Jitao Shi <jitao.shi@mediatek.com>
- Xinlei Lee <xinlei.lee@mediatek.com>
allOf:
- $ref: pwm.yaml#
properties:
compatible:
oneOf:
- enum:
- mediatek,mt2701-disp-pwm
- mediatek,mt6595-disp-pwm
- mediatek,mt8173-disp-pwm
- mediatek,mt8183-disp-pwm
- items:
- const: mediatek,mt8167-disp-pwm
- const: mediatek,mt8173-disp-pwm
- items:
- enum:
- mediatek,mt8186-disp-pwm
- mediatek,mt8192-disp-pwm
- mediatek,mt8195-disp-pwm
- const: mediatek,mt8183-disp-pwm
reg:
maxItems: 1
"#pwm-cells":
const: 2
interrupts:
maxItems: 1
clocks:
items:
- description: Main Clock
- description: Mm Clock
clock-names:
items:
- const: main
- const: mm
required:
- compatible
- reg
- "#pwm-cells"
- clocks
- clock-names
additionalProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/clock/mt8173-clk.h>
#include <dt-bindings/interrupt-controller/irq.h>
pwm0: pwm@1401e000 {
compatible = "mediatek,mt8173-disp-pwm";
reg = <0x1401e000 0x1000>;
#pwm-cells = <2>;
clocks = <&mmsys CLK_MM_DISP_PWM026M>,
<&mmsys CLK_MM_DISP_PWM0MM>;
clock-names = "main", "mm";
};
......@@ -3,6 +3,7 @@ MediaTek PWM controller
Required properties:
- compatible: should be "mediatek,<name>-pwm":
- "mediatek,mt2712-pwm": found on mt2712 SoC.
- "mediatek,mt6795-pwm": found on mt6795 SoC.
- "mediatek,mt7622-pwm": found on mt7622 SoC.
- "mediatek,mt7623-pwm": found on mt7623 SoC.
- "mediatek,mt7628-pwm": found on mt7628 SoC.
......
MediaTek display PWM controller
Required properties:
- compatible: should be "mediatek,<name>-disp-pwm":
- "mediatek,mt2701-disp-pwm": found on mt2701 SoC.
- "mediatek,mt6595-disp-pwm": found on mt6595 SoC.
- "mediatek,mt8167-disp-pwm", "mediatek,mt8173-disp-pwm": found on mt8167 SoC.
- "mediatek,mt8173-disp-pwm": found on mt8173 SoC.
- "mediatek,mt8183-disp-pwm": found on mt8183 SoC.$
- reg: physical base address and length of the controller's registers.
- #pwm-cells: must be 2. See pwm.yaml in this directory for a description of
the cell format.
- clocks: phandle and clock specifier of the PWM reference clock.
- clock-names: must contain the following:
- "main": clock used to generate PWM signals.
- "mm": sync signals from the modules of mmsys.
- pinctrl-names: Must contain a "default" entry.
- pinctrl-0: One property must exist for each entry in pinctrl-names.
See pinctrl/pinctrl-bindings.txt for details of the property values.
Example:
pwm0: pwm@1401e000 {
compatible = "mediatek,mt8173-disp-pwm",
"mediatek,mt6595-disp-pwm";
reg = <0 0x1401e000 0 0x1000>;
#pwm-cells = <2>;
clocks = <&mmsys CLK_MM_DISP_PWM026M>,
<&mmsys CLK_MM_DISP_PWM0MM>;
clock-names = "main", "mm";
pinctrl-names = "default";
pinctrl-0 = <&disp_pwm0_pins>;
};
backlight_lcd: backlight_lcd {
compatible = "pwm-backlight";
pwms = <&pwm0 0 1000000>;
brightness-levels = <
0 16 32 48 64 80 96 112
128 144 160 176 192 208 224 240
255
>;
default-brightness-level = <9>;
power-supply = <&mt6397_vio18_reg>;
enable-gpios = <&pio 95 GPIO_ACTIVE_HIGH>;
};
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
# Copyright (C) Sunplus Co., Ltd. 2021
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/sunplus,sp7021-pwm.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Sunplus SoC SP7021 PWM Controller
maintainers:
- Hammer Hsieh <hammerh0314@gmail.com>
allOf:
- $ref: pwm.yaml#
properties:
compatible:
const: sunplus,sp7021-pwm
reg:
maxItems: 1
clocks:
maxItems: 1
'#pwm-cells':
const: 2
unevaluatedProperties: false
required:
- reg
- clocks
examples:
- |
pwm: pwm@9c007a00 {
compatible = "sunplus,sp7021-pwm";
reg = <0x9c007a00 0x80>;
clocks = <&clkc 0xa2>;
#pwm-cells = <2>;
};
# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/timer/xlnx,xps-timer.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Xilinx LogiCORE IP AXI Timer Device Tree Binding
maintainers:
- Sean Anderson <sean.anderson@seco.com>
properties:
compatible:
contains:
const: xlnx,xps-timer-1.00.a
clocks:
maxItems: 1
clock-names:
const: s_axi_aclk
interrupts:
maxItems: 1
reg:
maxItems: 1
'#pwm-cells': true
xlnx,count-width:
$ref: /schemas/types.yaml#/definitions/uint32
enum: [8, 16, 32]
default: 32
description:
The width of the counter(s), in bits.
xlnx,one-timer-only:
$ref: /schemas/types.yaml#/definitions/uint32
enum: [ 0, 1 ]
description:
Whether only one timer is present in this block.
required:
- compatible
- reg
- xlnx,one-timer-only
allOf:
- if:
required:
- '#pwm-cells'
then:
allOf:
- required:
- clocks
- properties:
xlnx,one-timer-only:
const: 0
else:
required:
- interrupts
- if:
required:
- clocks
then:
required:
- clock-names
additionalProperties: false
examples:
- |
timer@800e0000 {
clock-names = "s_axi_aclk";
clocks = <&zynqmp_clk 71>;
compatible = "xlnx,xps-timer-1.00.a";
reg = <0x800e0000 0x10000>;
interrupts = <0 39 2>;
xlnx,count-width = <16>;
xlnx,one-timer-only = <0x0>;
};
timer@800f0000 {
#pwm-cells = <0>;
clock-names = "s_axi_aclk";
clocks = <&zynqmp_clk 71>;
compatible = "xlnx,xps-timer-1.00.a";
reg = <0x800e0000 0x10000>;
xlnx,count-width = <32>;
xlnx,one-timer-only = <0x0>;
};
......@@ -49,6 +49,12 @@ After being requested, a PWM has to be configured using::
This API controls both the PWM period/duty_cycle config and the
enable/disable state.
As a consumer, don't rely on the output's state for a disabled PWM. If it's
easily possible, drivers are supposed to emit the inactive state, but some
drivers cannot. If you rely on getting the inactive state, use .duty_cycle=0,
.enabled=true.
There is also a usage_power setting: If set, the PWM driver is only required to
maintain the power output but has more freedom regarding signal form.
If supported by the driver, the signal can be optimized, for example to improve
......
......@@ -13062,7 +13062,7 @@ M: Claudiu Beznea <claudiu.beznea@microchip.com>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
L: linux-pwm@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/pwm/atmel-pwm.txt
F: Documentation/devicetree/bindings/pwm/atmel,at91sam-pwm.yaml
F: drivers/pwm/pwm-atmel.c
MICROCHIP SAMA5D2-COMPATIBLE ADC DRIVER
......@@ -19031,6 +19031,12 @@ S: Maintained
F: Documentation/devicetree/bindings/nvmem/sunplus,sp7021-ocotp.yaml
F: drivers/nvmem/sunplus-ocotp.c
SUNPLUS PWM DRIVER
M: Hammer Hsieh <hammerh0314@gmail.com>
S: Maintained
F: Documentation/devicetree/bindings/pwm/sunplus,sp7021-pwm.yaml
F: drivers/pwm/pwm-sunplus.c
SUNPLUS RTC DRIVER
M: Vincent Shih <vincent.sunplus@gmail.com>
L: linux-rtc@vger.kernel.org
......@@ -21820,6 +21826,12 @@ F: drivers/misc/Makefile
F: drivers/misc/xilinx_sdfec.c
F: include/uapi/misc/xilinx_sdfec.h
XILINX PWM DRIVER
M: Sean Anderson <sean.anderson@seco.com>
S: Maintained
F: drivers/pwm/pwm-xilinx.c
F: include/clocksource/timer-xilinx.h
XILINX UARTLITE SERIAL DRIVER
M: Peter Korsgaard <jacmet@sunsite.dk>
L: linux-serial@vger.kernel.org
......
......@@ -251,6 +251,10 @@ static int __init xilinx_timer_init(struct device_node *timer)
u32 timer_num = 1;
int ret;
/* If this property is present, the device is a PWM and not a timer */
if (of_property_read_bool(timer, "#pwm-cells"))
return 0;
if (initialized)
return -EINVAL;
......
......@@ -572,6 +572,17 @@ config PWM_SUN4I
To compile this driver as a module, choose M here: the module
will be called pwm-sun4i.
config PWM_SUNPLUS
tristate "Sunplus PWM support"
depends on ARCH_SUNPLUS || COMPILE_TEST
depends on HAS_IOMEM && OF
help
Generic PWM framework driver for the PWM controller on
Sunplus SoCs.
To compile this driver as a module, choose M here: the module
will be called pwm-sunplus.
config PWM_TEGRA
tristate "NVIDIA Tegra PWM support"
depends on ARCH_TEGRA || COMPILE_TEST
......@@ -640,4 +651,18 @@ config PWM_VT8500
To compile this driver as a module, choose M here: the module
will be called pwm-vt8500.
config PWM_XILINX
tristate "Xilinx AXI Timer PWM support"
depends on OF_ADDRESS
depends on COMMON_CLK
select REGMAP_MMIO
help
PWM driver for Xilinx LogiCORE IP AXI timers. This timer is
typically a soft core which may be present in Xilinx FPGAs.
This device may also be present in Microblaze soft processors.
If you don't have this IP in your design, choose N.
To compile this driver as a module, choose M here: the module
will be called pwm-xilinx.
endif
......@@ -53,6 +53,7 @@ obj-$(CONFIG_PWM_STM32) += pwm-stm32.o
obj-$(CONFIG_PWM_STM32_LP) += pwm-stm32-lp.o
obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o
obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o
obj-$(CONFIG_PWM_SUNPLUS) += pwm-sunplus.o
obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o
obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o
......@@ -60,3 +61,4 @@ obj-$(CONFIG_PWM_TWL) += pwm-twl.o
obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
obj-$(CONFIG_PWM_VISCONTI) += pwm-visconti.o
obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
obj-$(CONFIG_PWM_XILINX) += pwm-xilinx.o
......@@ -61,7 +61,7 @@ struct atmel_tcb_pwm_chip {
struct atmel_tcb_channel bkup;
};
const u8 atmel_tcb_divisors[] = { 2, 8, 32, 128, 0, };
static const u8 atmel_tcb_divisors[] = { 2, 8, 32, 128, 0, };
static inline struct atmel_tcb_pwm_chip *to_tcb_chip(struct pwm_chip *chip)
{
......@@ -72,7 +72,8 @@ static int atmel_tcb_pwm_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = tcbpwmc->pwms[pwm->hwpwm];
tcbpwm->polarity = polarity;
......@@ -97,7 +98,6 @@ static int atmel_tcb_pwm_request(struct pwm_chip *chip,
return ret;
}
pwm_set_chip_data(pwm, tcbpwm);
tcbpwm->polarity = PWM_POLARITY_NORMAL;
tcbpwm->duty = 0;
tcbpwm->period = 0;
......@@ -139,7 +139,7 @@ static int atmel_tcb_pwm_request(struct pwm_chip *chip,
static void atmel_tcb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tcb_pwm_device *tcbpwm = tcbpwmc->pwms[pwm->hwpwm];
clk_disable_unprepare(tcbpwmc->clk);
tcbpwmc->pwms[pwm->hwpwm] = NULL;
......@@ -149,7 +149,7 @@ static void atmel_tcb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tcb_pwm_device *tcbpwm = tcbpwmc->pwms[pwm->hwpwm];
unsigned cmr;
enum pwm_polarity polarity = tcbpwm->polarity;
......@@ -206,7 +206,7 @@ static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tcb_pwm_device *tcbpwm = tcbpwmc->pwms[pwm->hwpwm];
u32 cmr;
enum pwm_polarity polarity = tcbpwm->polarity;
......@@ -291,7 +291,7 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
struct atmel_tcb_pwm_device *tcbpwm = tcbpwmc->pwms[pwm->hwpwm];
struct atmel_tcb_pwm_device *atcbpwm = NULL;
int i = 0;
int slowclk = 0;
......
......@@ -23,29 +23,6 @@ static inline struct clps711x_chip *to_clps711x_chip(struct pwm_chip *chip)
return container_of(chip, struct clps711x_chip, chip);
}
static void clps711x_pwm_update_val(struct clps711x_chip *priv, u32 n, u32 v)
{
/* PWM0 - bits 4..7, PWM1 - bits 8..11 */
u32 shift = (n + 1) * 4;
unsigned long flags;
u32 tmp;
spin_lock_irqsave(&priv->lock, flags);
tmp = readl(priv->pmpcon);
tmp &= ~(0xf << shift);
tmp |= v << shift;
writel(tmp, priv->pmpcon);
spin_unlock_irqrestore(&priv->lock, flags);
}
static unsigned int clps711x_get_duty(struct pwm_device *pwm, unsigned int v)
{
/* Duty cycle 0..15 max */
return DIV64_U64_ROUND_CLOSEST(v * 0xf, pwm->args.period);
}
static int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
......@@ -60,44 +37,41 @@ static int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
return 0;
}
static int clps711x_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
static int clps711x_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
unsigned int duty;
/* PWM0 - bits 4..7, PWM1 - bits 8..11 */
u32 shift = (pwm->hwpwm + 1) * 4;
unsigned long flags;
u32 pmpcon, val;
if (period_ns != pwm->args.period)
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
duty = clps711x_get_duty(pwm, duty_ns);
clps711x_pwm_update_val(priv, pwm->hwpwm, duty);
return 0;
}
if (state->period != pwm->args.period)
return -EINVAL;
static int clps711x_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
unsigned int duty;
if (state->enabled)
val = mul_u64_u64_div_u64(state->duty_cycle, 0xf, state->period);
else
val = 0;
duty = clps711x_get_duty(pwm, pwm_get_duty_cycle(pwm));
clps711x_pwm_update_val(priv, pwm->hwpwm, duty);
spin_lock_irqsave(&priv->lock, flags);
return 0;
}
pmpcon = readl(priv->pmpcon);
pmpcon &= ~(0xf << shift);
pmpcon |= val << shift;
writel(pmpcon, priv->pmpcon);
static void clps711x_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct clps711x_chip *priv = to_clps711x_chip(chip);
spin_unlock_irqrestore(&priv->lock, flags);
clps711x_pwm_update_val(priv, pwm->hwpwm, 0);
return 0;
}
static const struct pwm_ops clps711x_pwm_ops = {
.request = clps711x_pwm_request,
.config = clps711x_pwm_config,
.enable = clps711x_pwm_enable,
.disable = clps711x_pwm_disable,
.apply = clps711x_pwm_apply,
.owner = THIS_MODULE,
};
......
......@@ -12,17 +12,21 @@
#include <linux/pwm.h>
#include <linux/slab.h>
#include <dt-bindings/mfd/cros_ec.h>
/**
* struct cros_ec_pwm_device - Driver data for EC PWM
*
* @dev: Device node
* @ec: Pointer to EC device
* @chip: PWM controller chip
* @use_pwm_type: Use PWM types instead of generic channels
*/
struct cros_ec_pwm_device {
struct device *dev;
struct cros_ec_device *ec;
struct pwm_chip chip;
bool use_pwm_type;
};
/**
......@@ -58,14 +62,31 @@ static void cros_ec_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
kfree(channel);
}
static int cros_ec_pwm_set_duty(struct cros_ec_device *ec, u8 index, u16 duty)
static int cros_ec_dt_type_to_pwm_type(u8 dt_index, u8 *pwm_type)
{
switch (dt_index) {
case CROS_EC_PWM_DT_KB_LIGHT:
*pwm_type = EC_PWM_TYPE_KB_LIGHT;
return 0;
case CROS_EC_PWM_DT_DISPLAY_LIGHT:
*pwm_type = EC_PWM_TYPE_DISPLAY_LIGHT;
return 0;
default:
return -EINVAL;
}
}
static int cros_ec_pwm_set_duty(struct cros_ec_pwm_device *ec_pwm, u8 index,
u16 duty)
{
struct cros_ec_device *ec = ec_pwm->ec;
struct {
struct cros_ec_command msg;
struct ec_params_pwm_set_duty params;
} __packed buf;
struct ec_params_pwm_set_duty *params = &buf.params;
struct cros_ec_command *msg = &buf.msg;
int ret;
memset(&buf, 0, sizeof(buf));
......@@ -75,14 +96,25 @@ static int cros_ec_pwm_set_duty(struct cros_ec_device *ec, u8 index, u16 duty)
msg->outsize = sizeof(*params);
params->duty = duty;
if (ec_pwm->use_pwm_type) {
ret = cros_ec_dt_type_to_pwm_type(index, &params->pwm_type);
if (ret) {
dev_err(ec->dev, "Invalid PWM type index: %d\n", index);
return ret;
}
params->index = 0;
} else {
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)
static int cros_ec_pwm_get_duty(struct cros_ec_pwm_device *ec_pwm, u8 index)
{
struct cros_ec_device *ec = ec_pwm->ec;
struct {
struct cros_ec_command msg;
union {
......@@ -102,8 +134,17 @@ static int cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index)
msg->insize = sizeof(*resp);
msg->outsize = sizeof(*params);
if (ec_pwm->use_pwm_type) {
ret = cros_ec_dt_type_to_pwm_type(index, &params->pwm_type);
if (ret) {
dev_err(ec->dev, "Invalid PWM type index: %d\n", index);
return ret;
}
params->index = 0;
} else {
params->pwm_type = EC_PWM_TYPE_GENERIC;
params->index = index;
}
ret = cros_ec_cmd_xfer_status(ec, msg);
if (ret < 0)
......@@ -133,7 +174,7 @@ static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
*/
duty_cycle = state->enabled ? state->duty_cycle : 0;
ret = cros_ec_pwm_set_duty(ec_pwm->ec, pwm->hwpwm, duty_cycle);
ret = cros_ec_pwm_set_duty(ec_pwm, pwm->hwpwm, duty_cycle);
if (ret < 0)
return ret;
......@@ -149,7 +190,7 @@ static void cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct cros_ec_pwm *channel = pwm_get_chip_data(pwm);
int ret;
ret = cros_ec_pwm_get_duty(ec_pwm->ec, pwm->hwpwm);
ret = cros_ec_pwm_get_duty(ec_pwm, pwm->hwpwm);
if (ret < 0) {
dev_err(chip->dev, "error getting initial duty: %d\n", ret);
return;
......@@ -204,13 +245,13 @@ static const struct pwm_ops cros_ec_pwm_ops = {
* of PWMs it supports directly, so we have to read the pwm duty cycle for
* subsequent channels until we get an error.
*/
static int cros_ec_num_pwms(struct cros_ec_device *ec)
static int cros_ec_num_pwms(struct cros_ec_pwm_device *ec_pwm)
{
int i, ret;
/* The index field is only 8 bits */
for (i = 0; i <= U8_MAX; i++) {
ret = cros_ec_pwm_get_duty(ec, i);
ret = cros_ec_pwm_get_duty(ec_pwm, i);
/*
* We look for SUCCESS, INVALID_COMMAND, or INVALID_PARAM
* responses; everything else is treated as an error.
......@@ -236,6 +277,7 @@ 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 device_node *np = pdev->dev.of_node;
struct cros_ec_pwm_device *ec_pwm;
struct pwm_chip *chip;
int ret;
......@@ -251,17 +293,26 @@ static int cros_ec_pwm_probe(struct platform_device *pdev)
chip = &ec_pwm->chip;
ec_pwm->ec = ec;
if (of_device_is_compatible(np, "google,cros-ec-pwm-type"))
ec_pwm->use_pwm_type = true;
/* PWM chip */
chip->dev = dev;
chip->ops = &cros_ec_pwm_ops;
chip->of_xlate = cros_ec_pwm_xlate;
chip->of_pwm_n_cells = 1;
ret = cros_ec_num_pwms(ec);
if (ec_pwm->use_pwm_type) {
chip->npwm = CROS_EC_PWM_DT_COUNT;
} else {
ret = cros_ec_num_pwms(ec_pwm);
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);
......@@ -288,6 +339,7 @@ static int cros_ec_pwm_remove(struct platform_device *dev)
#ifdef CONFIG_OF
static const struct of_device_id cros_ec_pwm_of_match[] = {
{ .compatible = "google,cros-ec-pwm" },
{ .compatible = "google,cros-ec-pwm-type" },
{},
};
MODULE_DEVICE_TABLE(of, cros_ec_pwm_of_match);
......
......@@ -93,7 +93,7 @@ static void lp3943_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
}
static int lp3943_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
u64 duty_ns, u64 period_ns)
{
struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip);
struct lp3943 *lp3943 = lp3943_pwm->lp3943;
......@@ -118,14 +118,20 @@ static int lp3943_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
reg_duty = LP3943_REG_PWM1;
}
period_ns = clamp(period_ns, LP3943_MIN_PERIOD, LP3943_MAX_PERIOD);
val = (u8)(period_ns / LP3943_MIN_PERIOD - 1);
/*
* Note that after this clamping, period_ns fits into an int. This is
* helpful because we can resort to integer division below instead of
* the (more expensive) 64 bit division.
*/
period_ns = clamp(period_ns, (u64)LP3943_MIN_PERIOD, (u64)LP3943_MAX_PERIOD);
val = (u8)((int)period_ns / LP3943_MIN_PERIOD - 1);
err = lp3943_write_byte(lp3943, reg_prescale, val);
if (err)
return err;
val = (u8)(duty_ns * LP3943_MAX_DUTY / period_ns);
duty_ns = min(duty_ns, period_ns);
val = (u8)((int)duty_ns * LP3943_MAX_DUTY / (int)period_ns);
return lp3943_write_byte(lp3943, reg_duty, val);
}
......@@ -182,12 +188,34 @@ static void lp3943_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
lp3943_pwm_set_mode(lp3943_pwm, pwm_map, LP3943_GPIO_OUT_HIGH);
}
static int lp3943_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
lp3943_pwm_disable(chip, pwm);
return 0;
}
err = lp3943_pwm_config(chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = lp3943_pwm_enable(chip, pwm);
return err;
}
static const struct pwm_ops lp3943_pwm_ops = {
.request = lp3943_pwm_request,
.free = lp3943_pwm_free,
.config = lp3943_pwm_config,
.enable = lp3943_pwm_enable,
.disable = lp3943_pwm_disable,
.apply = lp3943_pwm_apply,
.owner = THIS_MODULE,
};
......
......@@ -226,14 +226,7 @@ static int lpc18xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
return 0;
}
static int lpc18xx_pwm_set_polarity(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity)
{
return 0;
}
static int lpc18xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
static int lpc18xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity)
{
struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip);
struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm];
......@@ -249,7 +242,7 @@ static int lpc18xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
LPC18XX_PWM_EVSTATEMSK(lpc18xx_data->duty_event),
LPC18XX_PWM_EVSTATEMSK_ALL);
if (pwm_get_polarity(pwm) == PWM_POLARITY_NORMAL) {
if (polarity == PWM_POLARITY_NORMAL) {
set_event = lpc18xx_pwm->period_event;
clear_event = lpc18xx_data->duty_event;
res_action = LPC18XX_PWM_RES_SET;
......@@ -308,11 +301,35 @@ static void lpc18xx_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
clear_bit(lpc18xx_data->duty_event, &lpc18xx_pwm->event_map);
}
static int lpc18xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
bool enabled = pwm->state.enabled;
if (state->polarity != pwm->state.polarity && pwm->state.enabled) {
lpc18xx_pwm_disable(chip, pwm);
enabled = false;
}
if (!state->enabled) {
if (enabled)
lpc18xx_pwm_disable(chip, pwm);
return 0;
}
err = lpc18xx_pwm_config(pwm->chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!enabled)
err = lpc18xx_pwm_enable(chip, pwm, state->polarity);
return err;
}
static const struct pwm_ops lpc18xx_pwm_ops = {
.config = lpc18xx_pwm_config,
.set_polarity = lpc18xx_pwm_set_polarity,
.enable = lpc18xx_pwm_enable,
.disable = lpc18xx_pwm_disable,
.apply = lpc18xx_pwm_apply,
.request = lpc18xx_pwm_request,
.free = lpc18xx_pwm_free,
.owner = THIS_MODULE,
......
......@@ -88,10 +88,33 @@ static void lpc32xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
clk_disable_unprepare(lpc32xx->clk);
}
static int lpc32xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
lpc32xx_pwm_disable(chip, pwm);
return 0;
}
err = lpc32xx_pwm_config(pwm->chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = lpc32xx_pwm_enable(chip, pwm);
return err;
}
static const struct pwm_ops lpc32xx_pwm_ops = {
.config = lpc32xx_pwm_config,
.enable = lpc32xx_pwm_enable,
.disable = lpc32xx_pwm_disable,
.apply = lpc32xx_pwm_apply,
.owner = THIS_MODULE,
};
......
......@@ -198,10 +198,33 @@ static void pwm_mediatek_disable(struct pwm_chip *chip, struct pwm_device *pwm)
pwm_mediatek_clk_disable(chip, pwm);
}
static int pwm_mediatek_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
pwm_mediatek_disable(chip, pwm);
return 0;
}
err = pwm_mediatek_config(pwm->chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = pwm_mediatek_enable(chip, pwm);
return err;
}
static const struct pwm_ops pwm_mediatek_ops = {
.config = pwm_mediatek_config,
.enable = pwm_mediatek_enable,
.disable = pwm_mediatek_disable,
.apply = pwm_mediatek_apply,
.owner = THIS_MODULE,
};
......@@ -264,6 +287,12 @@ static const struct pwm_mediatek_of_data mt2712_pwm_data = {
.has_ck_26m_sel = false,
};
static const struct pwm_mediatek_of_data mt6795_pwm_data = {
.num_pwms = 7,
.pwm45_fixup = false,
.has_ck_26m_sel = false,
};
static const struct pwm_mediatek_of_data mt7622_pwm_data = {
.num_pwms = 6,
.pwm45_fixup = false,
......@@ -302,6 +331,7 @@ static const struct pwm_mediatek_of_data mt8516_pwm_data = {
static const struct of_device_id pwm_mediatek_of_match[] = {
{ .compatible = "mediatek,mt2712-pwm", .data = &mt2712_pwm_data },
{ .compatible = "mediatek,mt6795-pwm", .data = &mt6795_pwm_data },
{ .compatible = "mediatek,mt7622-pwm", .data = &mt7622_pwm_data },
{ .compatible = "mediatek,mt7623-pwm", .data = &mt7623_pwm_data },
{ .compatible = "mediatek,mt7628-pwm", .data = &mt7628_pwm_data },
......
......@@ -66,7 +66,7 @@ static int raspberrypi_pwm_get_property(struct rpi_firmware *firmware,
u32 reg, u32 *val)
{
struct raspberrypi_pwm_prop msg = {
.reg = reg
.reg = cpu_to_le32(reg),
};
int ret;
......
This diff is collapsed.
......@@ -321,14 +321,6 @@ static int __pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm);
u32 tin_ns = chan->tin_ns, tcnt, tcmp, oldtcmp;
/*
* We currently avoid using 64bit arithmetic by using the
* fact that anything faster than 1Hz is easily representable
* by 32bits.
*/
if (period_ns > NSEC_PER_SEC)
return -ERANGE;
tcnt = readl(our_chip->base + REG_TCNTB(pwm->hwpwm));
oldtcmp = readl(our_chip->base + REG_TCMPB(pwm->hwpwm));
......@@ -438,13 +430,51 @@ static int pwm_samsung_set_polarity(struct pwm_chip *chip,
return 0;
}
static int pwm_samsung_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err, enabled = pwm->state.enabled;
if (state->polarity != pwm->state.polarity) {
if (enabled) {
pwm_samsung_disable(chip, pwm);
enabled = false;
}
err = pwm_samsung_set_polarity(chip, pwm, state->polarity);
if (err)
return err;
}
if (!state->enabled) {
if (enabled)
pwm_samsung_disable(chip, pwm);
return 0;
}
/*
* We currently avoid using 64bit arithmetic by using the
* fact that anything faster than 1Hz is easily representable
* by 32bits.
*/
if (state->period > NSEC_PER_SEC)
return -ERANGE;
err = pwm_samsung_config(chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = pwm_samsung_enable(chip, pwm);
return err;
}
static const struct pwm_ops pwm_samsung_ops = {
.request = pwm_samsung_request,
.free = pwm_samsung_free,
.enable = pwm_samsung_enable,
.disable = pwm_samsung_disable,
.config = pwm_samsung_config,
.set_polarity = pwm_samsung_set_polarity,
.apply = pwm_samsung_apply,
.owner = THIS_MODULE,
};
......
......@@ -138,10 +138,9 @@ static int pwm_sifive_enable(struct pwm_chip *chip, bool enable)
dev_err(ddata->chip.dev, "Enable clk failed\n");
return ret;
}
}
if (!enable)
} else {
clk_disable(ddata->clk);
}
return 0;
}
......
......@@ -391,11 +391,34 @@ static int sti_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm,
return ret;
}
static int sti_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
sti_pwm_disable(chip, pwm);
return 0;
}
err = sti_pwm_config(pwm->chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = sti_pwm_enable(chip, pwm);
return err;
}
static const struct pwm_ops sti_pwm_ops = {
.capture = sti_pwm_capture,
.config = sti_pwm_config,
.enable = sti_pwm_enable,
.disable = sti_pwm_disable,
.apply = sti_pwm_apply,
.free = sti_pwm_free,
.owner = THIS_MODULE,
};
......
......@@ -259,10 +259,33 @@ static int stmpe_24xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
return 0;
}
static int stmpe_24xx_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
stmpe_24xx_pwm_disable(chip, pwm);
return 0;
}
err = stmpe_24xx_pwm_config(pwm->chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = stmpe_24xx_pwm_enable(chip, pwm);
return err;
}
static const struct pwm_ops stmpe_24xx_pwm_ops = {
.config = stmpe_24xx_pwm_config,
.enable = stmpe_24xx_pwm_enable,
.disable = stmpe_24xx_pwm_disable,
.apply = stmpe_24xx_pwm_apply,
.owner = THIS_MODULE,
};
......
......@@ -89,7 +89,6 @@ struct sun4i_pwm_chip {
void __iomem *base;
spinlock_t ctrl_lock;
const struct sun4i_pwm_data *data;
unsigned long next_period[2];
};
static inline struct sun4i_pwm_chip *to_sun4i_pwm_chip(struct pwm_chip *chip)
......@@ -236,7 +235,6 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
u32 ctrl, duty = 0, period = 0, val;
int ret;
unsigned int delay_us, prescaler = 0;
unsigned long now;
bool bypass;
pwm_get_state(pwm, &cstate);
......@@ -284,8 +282,6 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
val = (duty & PWM_DTY_MASK) | PWM_PRD(period);
sun4i_pwm_writel(sun4i_pwm, val, PWM_CH_PRD(pwm->hwpwm));
sun4i_pwm->next_period[pwm->hwpwm] = jiffies +
nsecs_to_jiffies(cstate.period + 1000);
if (state->polarity != PWM_POLARITY_NORMAL)
ctrl &= ~BIT_CH(PWM_ACT_STATE, pwm->hwpwm);
......@@ -305,15 +301,11 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
return 0;
/* We need a full period to elapse before disabling the channel. */
now = jiffies;
if (time_before(now, sun4i_pwm->next_period[pwm->hwpwm])) {
delay_us = jiffies_to_usecs(sun4i_pwm->next_period[pwm->hwpwm] -
now);
delay_us = DIV_ROUND_UP_ULL(cstate.period, NSEC_PER_USEC);
if ((delay_us / 500) > MAX_UDELAY_MS)
msleep(delay_us / 1000 + 1);
else
usleep_range(delay_us, delay_us * 2);
}
spin_lock(&sun4i_pwm->ctrl_lock);
ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
......
// SPDX-License-Identifier: GPL-2.0
/*
* PWM device driver for SUNPLUS SP7021 SoC
*
* Links:
* Reference Manual:
* https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
*
* Reference Manual(PWM module):
* https://sunplus.atlassian.net/wiki/spaces/doc/pages/461144198/12.+Pulse+Width+Modulation+PWM
*
* Limitations:
* - Only supports normal polarity.
* - It output low when PWM channel disabled.
* - When the parameters change, current running period will not be completed
* and run new settings immediately.
* - In .apply() PWM output need to write register FREQ and DUTY. When first write FREQ
* done and not yet write DUTY, it has short timing gap use new FREQ and old DUTY.
*
* Author: Hammer Hsieh <hammerh0314@gmail.com>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#define SP7021_PWM_MODE0 0x000
#define SP7021_PWM_MODE0_PWMEN(ch) BIT(ch)
#define SP7021_PWM_MODE0_BYPASS(ch) BIT(8 + (ch))
#define SP7021_PWM_MODE1 0x004
#define SP7021_PWM_MODE1_CNT_EN(ch) BIT(ch)
#define SP7021_PWM_FREQ(ch) (0x008 + 4 * (ch))
#define SP7021_PWM_FREQ_MAX GENMASK(15, 0)
#define SP7021_PWM_DUTY(ch) (0x018 + 4 * (ch))
#define SP7021_PWM_DUTY_DD_SEL(ch) FIELD_PREP(GENMASK(9, 8), ch)
#define SP7021_PWM_DUTY_MAX GENMASK(7, 0)
#define SP7021_PWM_DUTY_MASK SP7021_PWM_DUTY_MAX
#define SP7021_PWM_FREQ_SCALER 256
#define SP7021_PWM_NUM 4
struct sunplus_pwm {
struct pwm_chip chip;
void __iomem *base;
struct clk *clk;
};
static inline struct sunplus_pwm *to_sunplus_pwm(struct pwm_chip *chip)
{
return container_of(chip, struct sunplus_pwm, chip);
}
static int sunplus_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct sunplus_pwm *priv = to_sunplus_pwm(chip);
u32 dd_freq, duty, mode0, mode1;
u64 clk_rate;
if (state->polarity != pwm->state.polarity)
return -EINVAL;
if (!state->enabled) {
/* disable pwm channel output */
mode0 = readl(priv->base + SP7021_PWM_MODE0);
mode0 &= ~SP7021_PWM_MODE0_PWMEN(pwm->hwpwm);
writel(mode0, priv->base + SP7021_PWM_MODE0);
/* disable pwm channel clk source */
mode1 = readl(priv->base + SP7021_PWM_MODE1);
mode1 &= ~SP7021_PWM_MODE1_CNT_EN(pwm->hwpwm);
writel(mode1, priv->base + SP7021_PWM_MODE1);
return 0;
}
clk_rate = clk_get_rate(priv->clk);
/*
* The following calculations might overflow if clk is bigger
* than 256 GHz. In practise it's 202.5MHz, so this limitation
* is only theoretic.
*/
if (clk_rate > (u64)SP7021_PWM_FREQ_SCALER * NSEC_PER_SEC)
return -EINVAL;
/*
* With clk_rate limited above we have dd_freq <= state->period,
* so this cannot overflow.
*/
dd_freq = mul_u64_u64_div_u64(clk_rate, state->period, (u64)SP7021_PWM_FREQ_SCALER
* NSEC_PER_SEC);
if (dd_freq == 0)
return -EINVAL;
if (dd_freq > SP7021_PWM_FREQ_MAX)
dd_freq = SP7021_PWM_FREQ_MAX;
writel(dd_freq, priv->base + SP7021_PWM_FREQ(pwm->hwpwm));
/* cal and set pwm duty */
mode0 = readl(priv->base + SP7021_PWM_MODE0);
mode0 |= SP7021_PWM_MODE0_PWMEN(pwm->hwpwm);
mode1 = readl(priv->base + SP7021_PWM_MODE1);
mode1 |= SP7021_PWM_MODE1_CNT_EN(pwm->hwpwm);
if (state->duty_cycle == state->period) {
/* PWM channel output = high */
mode0 |= SP7021_PWM_MODE0_BYPASS(pwm->hwpwm);
duty = SP7021_PWM_DUTY_DD_SEL(pwm->hwpwm) | SP7021_PWM_DUTY_MAX;
} else {
mode0 &= ~SP7021_PWM_MODE0_BYPASS(pwm->hwpwm);
/*
* duty_ns <= period_ns 27 bits, clk_rate 28 bits, won't overflow.
*/
duty = mul_u64_u64_div_u64(state->duty_cycle, clk_rate,
(u64)dd_freq * NSEC_PER_SEC);
duty = SP7021_PWM_DUTY_DD_SEL(pwm->hwpwm) | duty;
}
writel(duty, priv->base + SP7021_PWM_DUTY(pwm->hwpwm));
writel(mode1, priv->base + SP7021_PWM_MODE1);
writel(mode0, priv->base + SP7021_PWM_MODE0);
return 0;
}
static void sunplus_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct sunplus_pwm *priv = to_sunplus_pwm(chip);
u32 mode0, dd_freq, duty;
u64 clk_rate;
mode0 = readl(priv->base + SP7021_PWM_MODE0);
if (mode0 & BIT(pwm->hwpwm)) {
clk_rate = clk_get_rate(priv->clk);
dd_freq = readl(priv->base + SP7021_PWM_FREQ(pwm->hwpwm));
duty = readl(priv->base + SP7021_PWM_DUTY(pwm->hwpwm));
duty = FIELD_GET(SP7021_PWM_DUTY_MASK, duty);
/*
* dd_freq 16 bits, SP7021_PWM_FREQ_SCALER 8 bits
* NSEC_PER_SEC 30 bits, won't overflow.
*/
state->period = DIV64_U64_ROUND_UP((u64)dd_freq * (u64)SP7021_PWM_FREQ_SCALER
* NSEC_PER_SEC, clk_rate);
/*
* dd_freq 16 bits, duty 8 bits, NSEC_PER_SEC 30 bits, won't overflow.
*/
state->duty_cycle = DIV64_U64_ROUND_UP((u64)dd_freq * (u64)duty * NSEC_PER_SEC,
clk_rate);
state->enabled = true;
} else {
state->enabled = false;
}
state->polarity = PWM_POLARITY_NORMAL;
}
static const struct pwm_ops sunplus_pwm_ops = {
.apply = sunplus_pwm_apply,
.get_state = sunplus_pwm_get_state,
.owner = THIS_MODULE,
};
static void sunplus_pwm_clk_release(void *data)
{
struct clk *clk = data;
clk_disable_unprepare(clk);
}
static int sunplus_pwm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sunplus_pwm *priv;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
priv->clk = devm_clk_get(dev, NULL);
if (IS_ERR(priv->clk))
return dev_err_probe(dev, PTR_ERR(priv->clk),
"get pwm clock failed\n");
ret = clk_prepare_enable(priv->clk);
if (ret < 0) {
dev_err(dev, "failed to enable clock: %d\n", ret);
return ret;
}
ret = devm_add_action_or_reset(dev, sunplus_pwm_clk_release, priv->clk);
if (ret < 0) {
dev_err(dev, "failed to release clock: %d\n", ret);
return ret;
}
priv->chip.dev = dev;
priv->chip.ops = &sunplus_pwm_ops;
priv->chip.npwm = SP7021_PWM_NUM;
ret = devm_pwmchip_add(dev, &priv->chip);
if (ret < 0)
return dev_err_probe(dev, ret, "Cannot register sunplus PWM\n");
return 0;
}
static const struct of_device_id sunplus_pwm_of_match[] = {
{ .compatible = "sunplus,sp7021-pwm", },
{}
};
MODULE_DEVICE_TABLE(of, sunplus_pwm_of_match);
static struct platform_driver sunplus_pwm_driver = {
.probe = sunplus_pwm_probe,
.driver = {
.name = "sunplus-pwm",
.of_match_table = sunplus_pwm_of_match,
},
};
module_platform_driver(sunplus_pwm_driver);
MODULE_DESCRIPTION("Sunplus SoC PWM Driver");
MODULE_AUTHOR("Hammer Hsieh <hammerh0314@gmail.com>");
MODULE_LICENSE("GPL");
......@@ -99,7 +99,7 @@ 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 = duty_ns, hz;
unsigned long long c = duty_ns;
unsigned long rate, required_clk_rate;
u32 val = 0;
int err;
......@@ -156,11 +156,9 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
pc->clk_rate = clk_get_rate(pc->clk);
}
rate = pc->clk_rate >> PWM_DUTY_WIDTH;
/* Consider precision in PWM_SCALE_WIDTH rate calculation */
hz = DIV_ROUND_CLOSEST_ULL(100ULL * NSEC_PER_SEC, period_ns);
rate = DIV_ROUND_CLOSEST_ULL(100ULL * rate, hz);
rate = mul_u64_u64_div_u64(pc->clk_rate, period_ns,
(u64)NSEC_PER_SEC << PWM_DUTY_WIDTH);
/*
* Since the actual PWM divider is the register's frequency divider
......@@ -169,6 +167,8 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
*/
if (rate > 0)
rate--;
else
return -EINVAL;
/*
* Make sure that the rate will fit in the register's frequency
......@@ -230,10 +230,34 @@ static void tegra_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
pm_runtime_put_sync(pc->dev);
}
static int tegra_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
bool enabled = pwm->state.enabled;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (enabled)
tegra_pwm_disable(chip, pwm);
return 0;
}
err = tegra_pwm_config(pwm->chip, pwm, state->duty_cycle, state->period);
if (err)
return err;
if (!enabled)
err = tegra_pwm_enable(chip, pwm);
return err;
}
static const struct pwm_ops tegra_pwm_ops = {
.config = tegra_pwm_config,
.enable = tegra_pwm_enable,
.disable = tegra_pwm_disable,
.apply = tegra_pwm_apply,
.owner = THIS_MODULE,
};
......
......@@ -137,6 +137,45 @@ static void twl4030_pwmled_disable(struct pwm_chip *chip,
mutex_unlock(&twl->mutex);
}
static int twl4030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int ret;
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
twl4030_pwmled_disable(chip, pwm);
return 0;
}
/*
* We cannot skip calling ->config even if state->period ==
* pwm->state.period && state->duty_cycle == pwm->state.duty_cycle
* because we might have exited early in the last call to
* pwm_apply_state because of !state->enabled and so the two values in
* pwm->state might not be configured in hardware.
*/
ret = twl4030_pwmled_config(pwm->chip, pwm,
state->duty_cycle, state->period);
if (ret)
return ret;
if (!pwm->state.enabled)
ret = twl4030_pwmled_enable(chip, pwm);
return ret;
}
static const struct pwm_ops twl4030_pwmled_ops = {
.apply = twl4030_pwmled_apply,
.owner = THIS_MODULE,
};
static int twl6030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
......@@ -206,6 +245,32 @@ static void twl6030_pwmled_disable(struct pwm_chip *chip,
mutex_unlock(&twl->mutex);
}
static int twl6030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
if (state->polarity != pwm->state.polarity)
return -EINVAL;
if (!state->enabled) {
if (pwm->state.enabled)
twl6030_pwmled_disable(chip, pwm);
return 0;
}
err = twl6030_pwmled_config(pwm->chip, pwm,
state->duty_cycle, state->period);
if (err)
return err;
if (!pwm->state.enabled)
err = twl6030_pwmled_enable(chip, pwm);
return err;
}
static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct twl_pwmled_chip *twl = to_twl(chip);
......@@ -257,17 +322,8 @@ static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm)
mutex_unlock(&twl->mutex);
}
static const struct pwm_ops twl4030_pwmled_ops = {
.enable = twl4030_pwmled_enable,
.disable = twl4030_pwmled_disable,
.config = twl4030_pwmled_config,
.owner = THIS_MODULE,
};
static const struct pwm_ops twl6030_pwmled_ops = {
.enable = twl6030_pwmled_enable,
.disable = twl6030_pwmled_disable,
.config = twl6030_pwmled_config,
.apply = twl6030_pwmled_apply,
.request = twl6030_pwmled_request,
.free = twl6030_pwmled_free,
.owner = THIS_MODULE,
......
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2021 Sean Anderson <sean.anderson@seco.com>
*/
#ifndef XILINX_TIMER_H
#define XILINX_TIMER_H
#include <linux/compiler.h>
#define TCSR0 0x00
#define TLR0 0x04
#define TCR0 0x08
#define TCSR1 0x10
#define TLR1 0x14
#define TCR1 0x18
#define TCSR_MDT BIT(0)
#define TCSR_UDT BIT(1)
#define TCSR_GENT BIT(2)
#define TCSR_CAPT BIT(3)
#define TCSR_ARHT BIT(4)
#define TCSR_LOAD BIT(5)
#define TCSR_ENIT BIT(6)
#define TCSR_ENT BIT(7)
#define TCSR_TINT BIT(8)
#define TCSR_PWMA BIT(9)
#define TCSR_ENALL BIT(10)
#define TCSR_CASC BIT(11)
struct clk;
struct device_node;
struct regmap;
/**
* struct xilinx_timer_priv - Private data for Xilinx AXI timer drivers
* @map: Regmap of the device, possibly with an offset
* @clk: Parent clock
* @max: Maximum value of the counters
*/
struct xilinx_timer_priv {
struct regmap *map;
struct clk *clk;
u32 max;
};
/**
* xilinx_timer_tlr_cycles() - Calculate the TLR for a period specified
* in clock cycles
* @priv: The timer's private data
* @tcsr: The value of the TCSR register for this counter
* @cycles: The number of cycles in this period
*
* Callers of this function MUST ensure that @cycles is representable as
* a TLR.
*
* Return: The calculated value for TLR
*/
u32 xilinx_timer_tlr_cycles(struct xilinx_timer_priv *priv, u32 tcsr,
u64 cycles);
/**
* xilinx_timer_get_period() - Get the current period of a counter
* @priv: The timer's private data
* @tlr: The value of TLR for this counter
* @tcsr: The value of TCSR for this counter
*
* Return: The period, in ns
*/
unsigned int xilinx_timer_get_period(struct xilinx_timer_priv *priv,
u32 tlr, u32 tcsr);
#endif /* XILINX_TIMER_H */
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
/*
* DTS binding definitions used for the Chromium OS Embedded Controller.
*
* Copyright (c) 2022 The Chromium OS Authors. All rights reserved.
*/
#ifndef _DT_BINDINGS_MFD_CROS_EC_H
#define _DT_BINDINGS_MFD_CROS_EC_H
/* Typed channel for keyboard backlight. */
#define CROS_EC_PWM_DT_KB_LIGHT 0
/* Typed channel for display backlight. */
#define CROS_EC_PWM_DT_DISPLAY_LIGHT 1
/* Number of typed channels. */
#define CROS_EC_PWM_DT_COUNT 2
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment