pwm: Changes for v5.19-rc1
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. -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEEiOrDCAFJzPfAjcif3SOs138+s6EFAmKXeUoZHHRoaWVycnku cmVkaW5nQGdtYWlsLmNvbQAKCRDdI6zXfz6zoQ44D/9ZtpQ30tKUQS4o55iiQZBy 7cfeHK++6oCrcIp57j5TEJaMh+DRz23ga+5/Sp2YeqCCswifrM/Lxm/ys34045oa YVHF1Yz9NbyE7d5W+Jzoo3cFBcne6lFgwcN8qVfpc2O35PwmzWSbU3iBlIq0qxbC etWbDxBzFbsNsvH6TSNP7xYxfRVct+KYBIl4ySswqryHry2Bl9mTV2P9wS3CkZne 4y6RWyYeaNBmqLxCOiw98pXr70uJKRAy1qoEUvFBZGzYsb9kwOG9J7RQJxgtwVFK YJ+l/c/cq1U3LtQQImcPhx8y+jdRpFK3RlcA6cRBHP3PWuTUsKPKYf2DFEee3bYv NhNxkuJhuXhvqpB3Bb5xrnOE34tjF4tLPz74UxaU/DlLqeveyvaqsVuGK7IVbxcU YVsceNJHdTJdkKaomB/OkmNrKEf8XNmCmF+x2FPAMKBnJ+E43jKuPk7TTUJrJLn0 O57VjKmiA3ZsztgWudSOiOvTxmsfRnlHTcPvMO8MbYO9tXVFOBGuzV3RgxKvwpDT EWp5VAQp8JMoLvG3fdB4wbZ8isVooYdYYw/jiUG5MA3MGxW+4zmRR/+BglsKpMU7 AujYBUeInXg1bwezBUW4EieslFVQ/8ctsXj84bbIg3gA5SRSgp/ccH/2ollnAsPU Dw6E+STJiQM95NqfUUb8IQ== =CwnJ -----END PGP SIGNATURE----- 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() ...
This commit is contained in:
commit
8eca6b0a64
|
@ -0,0 +1,47 @@
|
|||
# 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>;
|
||||
};
|
|
@ -1,35 +0,0 @@
|
|||
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
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
# 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.
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
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>;
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
# 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>;
|
||||
};
|
|
@ -0,0 +1,92 @@
|
|||
# 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
|
||||
|
|
14
MAINTAINERS
14
MAINTAINERS
|
@ -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);
|
||||
if (state->period != pwm->args.period)
|
||||
return -EINVAL;
|
||||
|
||||
if (state->enabled)
|
||||
val = mul_u64_u64_div_u64(state->duty_cycle, 0xf, state->period);
|
||||
else
|
||||
val = 0;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
|
||||
pmpcon = readl(priv->pmpcon);
|
||||
pmpcon &= ~(0xf << shift);
|
||||
pmpcon |= val << shift;
|
||||
writel(pmpcon, priv->pmpcon);
|
||||
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clps711x_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct clps711x_chip *priv = to_clps711x_chip(chip);
|
||||
unsigned int duty;
|
||||
|
||||
duty = clps711x_get_duty(pwm, pwm_get_duty_cycle(pwm));
|
||||
clps711x_pwm_update_val(priv, pwm->hwpwm, duty);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void clps711x_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct clps711x_chip *priv = to_clps711x_chip(chip);
|
||||
|
||||
clps711x_pwm_update_val(priv, pwm->hwpwm, 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;
|
||||
params->pwm_type = EC_PWM_TYPE_GENERIC;
|
||||
params->index = index;
|
||||
|
||||
if (ec_pwm->use_pwm_type) {
|
||||
ret = cros_ec_dt_type_to_pwm_type(index, ¶ms->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);
|
||||
|
||||
params->pwm_type = EC_PWM_TYPE_GENERIC;
|
||||
params->index = index;
|
||||
if (ec_pwm->use_pwm_type) {
|
||||
ret = cros_ec_dt_type_to_pwm_type(index, ¶ms->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 (ret < 0) {
|
||||
dev_err(dev, "Couldn't find PWMs: %d\n", ret);
|
||||
return ret;
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
|
|
|
@ -89,71 +89,71 @@ struct tpu_device {
|
|||
|
||||
#define to_tpu_device(c) container_of(c, struct tpu_device, chip)
|
||||
|
||||
static void tpu_pwm_write(struct tpu_pwm_device *pwm, int reg_nr, u16 value)
|
||||
static void tpu_pwm_write(struct tpu_pwm_device *tpd, int reg_nr, u16 value)
|
||||
{
|
||||
void __iomem *base = pwm->tpu->base + TPU_CHANNEL_OFFSET
|
||||
+ pwm->channel * TPU_CHANNEL_SIZE;
|
||||
void __iomem *base = tpd->tpu->base + TPU_CHANNEL_OFFSET
|
||||
+ tpd->channel * TPU_CHANNEL_SIZE;
|
||||
|
||||
iowrite16(value, base + reg_nr);
|
||||
}
|
||||
|
||||
static void tpu_pwm_set_pin(struct tpu_pwm_device *pwm,
|
||||
static void tpu_pwm_set_pin(struct tpu_pwm_device *tpd,
|
||||
enum tpu_pin_state state)
|
||||
{
|
||||
static const char * const states[] = { "inactive", "PWM", "active" };
|
||||
|
||||
dev_dbg(&pwm->tpu->pdev->dev, "%u: configuring pin as %s\n",
|
||||
pwm->channel, states[state]);
|
||||
dev_dbg(&tpd->tpu->pdev->dev, "%u: configuring pin as %s\n",
|
||||
tpd->channel, states[state]);
|
||||
|
||||
switch (state) {
|
||||
case TPU_PIN_INACTIVE:
|
||||
tpu_pwm_write(pwm, TPU_TIORn,
|
||||
pwm->polarity == PWM_POLARITY_INVERSED ?
|
||||
tpu_pwm_write(tpd, TPU_TIORn,
|
||||
tpd->polarity == PWM_POLARITY_INVERSED ?
|
||||
TPU_TIOR_IOA_1 : TPU_TIOR_IOA_0);
|
||||
break;
|
||||
case TPU_PIN_PWM:
|
||||
tpu_pwm_write(pwm, TPU_TIORn,
|
||||
pwm->polarity == PWM_POLARITY_INVERSED ?
|
||||
tpu_pwm_write(tpd, TPU_TIORn,
|
||||
tpd->polarity == PWM_POLARITY_INVERSED ?
|
||||
TPU_TIOR_IOA_0_SET : TPU_TIOR_IOA_1_CLR);
|
||||
break;
|
||||
case TPU_PIN_ACTIVE:
|
||||
tpu_pwm_write(pwm, TPU_TIORn,
|
||||
pwm->polarity == PWM_POLARITY_INVERSED ?
|
||||
tpu_pwm_write(tpd, TPU_TIORn,
|
||||
tpd->polarity == PWM_POLARITY_INVERSED ?
|
||||
TPU_TIOR_IOA_0 : TPU_TIOR_IOA_1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void tpu_pwm_start_stop(struct tpu_pwm_device *pwm, int start)
|
||||
static void tpu_pwm_start_stop(struct tpu_pwm_device *tpd, int start)
|
||||
{
|
||||
unsigned long flags;
|
||||
u16 value;
|
||||
|
||||
spin_lock_irqsave(&pwm->tpu->lock, flags);
|
||||
value = ioread16(pwm->tpu->base + TPU_TSTR);
|
||||
spin_lock_irqsave(&tpd->tpu->lock, flags);
|
||||
value = ioread16(tpd->tpu->base + TPU_TSTR);
|
||||
|
||||
if (start)
|
||||
value |= 1 << pwm->channel;
|
||||
value |= 1 << tpd->channel;
|
||||
else
|
||||
value &= ~(1 << pwm->channel);
|
||||
value &= ~(1 << tpd->channel);
|
||||
|
||||
iowrite16(value, pwm->tpu->base + TPU_TSTR);
|
||||
spin_unlock_irqrestore(&pwm->tpu->lock, flags);
|
||||
iowrite16(value, tpd->tpu->base + TPU_TSTR);
|
||||
spin_unlock_irqrestore(&tpd->tpu->lock, flags);
|
||||
}
|
||||
|
||||
static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm)
|
||||
static int tpu_pwm_timer_start(struct tpu_pwm_device *tpd)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!pwm->timer_on) {
|
||||
if (!tpd->timer_on) {
|
||||
/* Wake up device and enable clock. */
|
||||
pm_runtime_get_sync(&pwm->tpu->pdev->dev);
|
||||
ret = clk_prepare_enable(pwm->tpu->clk);
|
||||
pm_runtime_get_sync(&tpd->tpu->pdev->dev);
|
||||
ret = clk_prepare_enable(tpd->tpu->clk);
|
||||
if (ret) {
|
||||
dev_err(&pwm->tpu->pdev->dev, "cannot enable clock\n");
|
||||
dev_err(&tpd->tpu->pdev->dev, "cannot enable clock\n");
|
||||
return ret;
|
||||
}
|
||||
pwm->timer_on = true;
|
||||
tpd->timer_on = true;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -161,8 +161,8 @@ static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm)
|
|||
* completely. First drive the pin to the inactive state to avoid
|
||||
* glitches.
|
||||
*/
|
||||
tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE);
|
||||
tpu_pwm_start_stop(pwm, false);
|
||||
tpu_pwm_set_pin(tpd, TPU_PIN_INACTIVE);
|
||||
tpu_pwm_start_stop(tpd, false);
|
||||
|
||||
/*
|
||||
* - Clear TCNT on TGRB match
|
||||
|
@ -172,142 +172,168 @@ static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm)
|
|||
* - Output 1 until TGRA, output 0 until TGRB (active high polarity
|
||||
* - PWM mode
|
||||
*/
|
||||
tpu_pwm_write(pwm, TPU_TCRn, TPU_TCR_CCLR_TGRB | TPU_TCR_CKEG_RISING |
|
||||
pwm->prescaler);
|
||||
tpu_pwm_write(pwm, TPU_TMDRn, TPU_TMDR_MD_PWM);
|
||||
tpu_pwm_set_pin(pwm, TPU_PIN_PWM);
|
||||
tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty);
|
||||
tpu_pwm_write(pwm, TPU_TGRBn, pwm->period);
|
||||
tpu_pwm_write(tpd, TPU_TCRn, TPU_TCR_CCLR_TGRB | TPU_TCR_CKEG_RISING |
|
||||
tpd->prescaler);
|
||||
tpu_pwm_write(tpd, TPU_TMDRn, TPU_TMDR_MD_PWM);
|
||||
tpu_pwm_set_pin(tpd, TPU_PIN_PWM);
|
||||
tpu_pwm_write(tpd, TPU_TGRAn, tpd->duty);
|
||||
tpu_pwm_write(tpd, TPU_TGRBn, tpd->period);
|
||||
|
||||
dev_dbg(&pwm->tpu->pdev->dev, "%u: TGRA 0x%04x TGRB 0x%04x\n",
|
||||
pwm->channel, pwm->duty, pwm->period);
|
||||
dev_dbg(&tpd->tpu->pdev->dev, "%u: TGRA 0x%04x TGRB 0x%04x\n",
|
||||
tpd->channel, tpd->duty, tpd->period);
|
||||
|
||||
/* Start the channel. */
|
||||
tpu_pwm_start_stop(pwm, true);
|
||||
tpu_pwm_start_stop(tpd, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tpu_pwm_timer_stop(struct tpu_pwm_device *pwm)
|
||||
static void tpu_pwm_timer_stop(struct tpu_pwm_device *tpd)
|
||||
{
|
||||
if (!pwm->timer_on)
|
||||
if (!tpd->timer_on)
|
||||
return;
|
||||
|
||||
/* Disable channel. */
|
||||
tpu_pwm_start_stop(pwm, false);
|
||||
tpu_pwm_start_stop(tpd, false);
|
||||
|
||||
/* Stop clock and mark device as idle. */
|
||||
clk_disable_unprepare(pwm->tpu->clk);
|
||||
pm_runtime_put(&pwm->tpu->pdev->dev);
|
||||
clk_disable_unprepare(tpd->tpu->clk);
|
||||
pm_runtime_put(&tpd->tpu->pdev->dev);
|
||||
|
||||
pwm->timer_on = false;
|
||||
tpd->timer_on = false;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* PWM API
|
||||
*/
|
||||
|
||||
static int tpu_pwm_request(struct pwm_chip *chip, struct pwm_device *_pwm)
|
||||
static int tpu_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct tpu_device *tpu = to_tpu_device(chip);
|
||||
struct tpu_pwm_device *pwm;
|
||||
struct tpu_pwm_device *tpd;
|
||||
|
||||
if (_pwm->hwpwm >= TPU_CHANNEL_MAX)
|
||||
if (pwm->hwpwm >= TPU_CHANNEL_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
pwm = kzalloc(sizeof(*pwm), GFP_KERNEL);
|
||||
if (pwm == NULL)
|
||||
tpd = kzalloc(sizeof(*tpd), GFP_KERNEL);
|
||||
if (tpd == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
pwm->tpu = tpu;
|
||||
pwm->channel = _pwm->hwpwm;
|
||||
pwm->polarity = PWM_POLARITY_NORMAL;
|
||||
pwm->prescaler = 0;
|
||||
pwm->period = 0;
|
||||
pwm->duty = 0;
|
||||
tpd->tpu = tpu;
|
||||
tpd->channel = pwm->hwpwm;
|
||||
tpd->polarity = PWM_POLARITY_NORMAL;
|
||||
tpd->prescaler = 0;
|
||||
tpd->period = 0;
|
||||
tpd->duty = 0;
|
||||
|
||||
pwm->timer_on = false;
|
||||
tpd->timer_on = false;
|
||||
|
||||
pwm_set_chip_data(_pwm, pwm);
|
||||
pwm_set_chip_data(pwm, tpd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *_pwm)
|
||||
static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
|
||||
struct tpu_pwm_device *tpd = pwm_get_chip_data(pwm);
|
||||
|
||||
tpu_pwm_timer_stop(pwm);
|
||||
kfree(pwm);
|
||||
tpu_pwm_timer_stop(tpd);
|
||||
kfree(tpd);
|
||||
}
|
||||
|
||||
static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm,
|
||||
int duty_ns, int period_ns)
|
||||
static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
u64 duty_ns, u64 period_ns, bool enabled)
|
||||
{
|
||||
static const unsigned int prescalers[] = { 1, 4, 16, 64 };
|
||||
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
|
||||
struct tpu_pwm_device *tpd = pwm_get_chip_data(pwm);
|
||||
struct tpu_device *tpu = to_tpu_device(chip);
|
||||
unsigned int prescaler;
|
||||
bool duty_only = false;
|
||||
u32 clk_rate;
|
||||
u32 period;
|
||||
u64 period;
|
||||
u32 duty;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Pick a prescaler to avoid overflowing the counter.
|
||||
* TODO: Pick the highest acceptable prescaler.
|
||||
*/
|
||||
clk_rate = clk_get_rate(tpu->clk);
|
||||
|
||||
for (prescaler = 0; prescaler < ARRAY_SIZE(prescalers); ++prescaler) {
|
||||
period = clk_rate / prescalers[prescaler]
|
||||
/ (NSEC_PER_SEC / period_ns);
|
||||
if (period <= 0xffff)
|
||||
break;
|
||||
if (unlikely(clk_rate > NSEC_PER_SEC)) {
|
||||
/*
|
||||
* This won't happen in the nearer future, so this is only a
|
||||
* safeguard to prevent the following calculation from
|
||||
* overflowing. With this clk_rate * period_ns / NSEC_PER_SEC is
|
||||
* not greater than period_ns and so fits into an u64.
|
||||
*/
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (prescaler == ARRAY_SIZE(prescalers) || period == 0) {
|
||||
dev_err(&tpu->pdev->dev, "clock rate mismatch\n");
|
||||
return -ENOTSUPP;
|
||||
period = mul_u64_u64_div_u64(clk_rate, period_ns, NSEC_PER_SEC);
|
||||
|
||||
/*
|
||||
* Find the minimal prescaler in [0..3] such that
|
||||
*
|
||||
* period >> (2 * prescaler) < 0x10000
|
||||
*
|
||||
* This could be calculated using something like:
|
||||
*
|
||||
* prescaler = max(ilog2(period) / 2, 7) - 7;
|
||||
*
|
||||
* but given there are only four allowed results and that ilog2 isn't
|
||||
* cheap on all platforms using a switch statement is more effective.
|
||||
*/
|
||||
switch (period) {
|
||||
case 1 ... 0xffff:
|
||||
prescaler = 0;
|
||||
break;
|
||||
|
||||
case 0x10000 ... 0x3ffff:
|
||||
prescaler = 1;
|
||||
break;
|
||||
|
||||
case 0x40000 ... 0xfffff:
|
||||
prescaler = 2;
|
||||
break;
|
||||
|
||||
case 0x100000 ... 0x3fffff:
|
||||
prescaler = 3;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (duty_ns) {
|
||||
duty = clk_rate / prescalers[prescaler]
|
||||
/ (NSEC_PER_SEC / duty_ns);
|
||||
if (duty > period)
|
||||
return -EINVAL;
|
||||
} else {
|
||||
period >>= 2 * prescaler;
|
||||
|
||||
if (duty_ns)
|
||||
duty = mul_u64_u64_div_u64(clk_rate, duty_ns,
|
||||
(u64)NSEC_PER_SEC << (2 * prescaler));
|
||||
else
|
||||
duty = 0;
|
||||
}
|
||||
|
||||
dev_dbg(&tpu->pdev->dev,
|
||||
"rate %u, prescaler %u, period %u, duty %u\n",
|
||||
clk_rate, prescalers[prescaler], period, duty);
|
||||
clk_rate, 1 << (2 * prescaler), (u32)period, duty);
|
||||
|
||||
if (pwm->prescaler == prescaler && pwm->period == period)
|
||||
if (tpd->prescaler == prescaler && tpd->period == period)
|
||||
duty_only = true;
|
||||
|
||||
pwm->prescaler = prescaler;
|
||||
pwm->period = period;
|
||||
pwm->duty = duty;
|
||||
tpd->prescaler = prescaler;
|
||||
tpd->period = period;
|
||||
tpd->duty = duty;
|
||||
|
||||
/* If the channel is disabled we're done. */
|
||||
if (!pwm_is_enabled(_pwm))
|
||||
if (!enabled)
|
||||
return 0;
|
||||
|
||||
if (duty_only && pwm->timer_on) {
|
||||
if (duty_only && tpd->timer_on) {
|
||||
/*
|
||||
* If only the duty cycle changed and the timer is already
|
||||
* running, there's no need to reconfigure it completely, Just
|
||||
* modify the duty cycle.
|
||||
*/
|
||||
tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty);
|
||||
dev_dbg(&tpu->pdev->dev, "%u: TGRA 0x%04x\n", pwm->channel,
|
||||
pwm->duty);
|
||||
tpu_pwm_write(tpd, TPU_TGRAn, tpd->duty);
|
||||
dev_dbg(&tpu->pdev->dev, "%u: TGRA 0x%04x\n", tpd->channel,
|
||||
tpd->duty);
|
||||
} else {
|
||||
/* Otherwise perform a full reconfiguration. */
|
||||
ret = tpu_pwm_timer_start(pwm);
|
||||
ret = tpu_pwm_timer_start(tpd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
@ -317,29 +343,29 @@ static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm,
|
|||
* To avoid running the timer when not strictly required, handle
|
||||
* 0% and 100% duty cycles as fixed levels and stop the timer.
|
||||
*/
|
||||
tpu_pwm_set_pin(pwm, duty ? TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
|
||||
tpu_pwm_timer_stop(pwm);
|
||||
tpu_pwm_set_pin(tpd, duty ? TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
|
||||
tpu_pwm_timer_stop(tpd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpu_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *_pwm,
|
||||
static int tpu_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
enum pwm_polarity polarity)
|
||||
{
|
||||
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
|
||||
struct tpu_pwm_device *tpd = pwm_get_chip_data(pwm);
|
||||
|
||||
pwm->polarity = polarity;
|
||||
tpd->polarity = polarity;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *_pwm)
|
||||
static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
|
||||
struct tpu_pwm_device *tpd = pwm_get_chip_data(pwm);
|
||||
int ret;
|
||||
|
||||
ret = tpu_pwm_timer_start(pwm);
|
||||
ret = tpu_pwm_timer_start(tpd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -347,32 +373,64 @@ static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *_pwm)
|
|||
* To avoid running the timer when not strictly required, handle 0% and
|
||||
* 100% duty cycles as fixed levels and stop the timer.
|
||||
*/
|
||||
if (pwm->duty == 0 || pwm->duty == pwm->period) {
|
||||
tpu_pwm_set_pin(pwm, pwm->duty ?
|
||||
if (tpd->duty == 0 || tpd->duty == tpd->period) {
|
||||
tpu_pwm_set_pin(tpd, tpd->duty ?
|
||||
TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
|
||||
tpu_pwm_timer_stop(pwm);
|
||||
tpu_pwm_timer_stop(tpd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *_pwm)
|
||||
static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
|
||||
struct tpu_pwm_device *tpd = pwm_get_chip_data(pwm);
|
||||
|
||||
/* The timer must be running to modify the pin output configuration. */
|
||||
tpu_pwm_timer_start(pwm);
|
||||
tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE);
|
||||
tpu_pwm_timer_stop(pwm);
|
||||
tpu_pwm_timer_start(tpd);
|
||||
tpu_pwm_set_pin(tpd, TPU_PIN_INACTIVE);
|
||||
tpu_pwm_timer_stop(tpd);
|
||||
}
|
||||
|
||||
static int tpu_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) {
|
||||
if (enabled) {
|
||||
tpu_pwm_disable(chip, pwm);
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
err = tpu_pwm_set_polarity(chip, pwm, state->polarity);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!state->enabled) {
|
||||
if (enabled)
|
||||
tpu_pwm_disable(chip, pwm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = tpu_pwm_config(pwm->chip, pwm,
|
||||
state->duty_cycle, state->period, enabled);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (!enabled)
|
||||
err = tpu_pwm_enable(chip, pwm);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct pwm_ops tpu_pwm_ops = {
|
||||
.request = tpu_pwm_request,
|
||||
.free = tpu_pwm_free,
|
||||
.config = tpu_pwm_config,
|
||||
.set_polarity = tpu_pwm_set_polarity,
|
||||
.enable = tpu_pwm_enable,
|
||||
.disable = tpu_pwm_disable,
|
||||
.apply = tpu_pwm_apply,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
|
@ -398,10 +456,8 @@ static int tpu_probe(struct platform_device *pdev)
|
|||
return PTR_ERR(tpu->base);
|
||||
|
||||
tpu->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(tpu->clk)) {
|
||||
dev_err(&pdev->dev, "cannot get clock\n");
|
||||
return PTR_ERR(tpu->clk);
|
||||
}
|
||||
if (IS_ERR(tpu->clk))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(tpu->clk), "Failed to get clock\n");
|
||||
|
||||
/* Initialize and register the device. */
|
||||
platform_set_drvdata(pdev, tpu);
|
||||
|
@ -410,25 +466,13 @@ static int tpu_probe(struct platform_device *pdev)
|
|||
tpu->chip.ops = &tpu_pwm_ops;
|
||||
tpu->chip.npwm = TPU_CHANNEL_MAX;
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
ret = devm_pm_runtime_enable(&pdev->dev);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&pdev->dev, ret, "Failed to enable runtime PM\n");
|
||||
|
||||
ret = pwmchip_add(&tpu->chip);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to register PWM chip\n");
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpu_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tpu_device *tpu = platform_get_drvdata(pdev);
|
||||
|
||||
pwmchip_remove(&tpu->chip);
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
ret = devm_pwmchip_add(&pdev->dev, &tpu->chip);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(&pdev->dev, ret, "Failed to register PWM chip\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -447,7 +491,6 @@ MODULE_DEVICE_TABLE(of, tpu_of_table);
|
|||
|
||||
static struct platform_driver tpu_driver = {
|
||||
.probe = tpu_probe,
|
||||
.remove = tpu_remove,
|
||||
.driver = {
|
||||
.name = "renesas-tpu-pwm",
|
||||
.of_match_table = of_match_ptr(tpu_of_table),
|
||||
|
|
|
@ -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);
|
||||
if ((delay_us / 500) > MAX_UDELAY_MS)
|
||||
msleep(delay_us / 1000 + 1);
|
||||
else
|
||||
usleep_range(delay_us, delay_us * 2);
|
||||
}
|
||||
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);
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
// 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,
|
||||
|
|
|
@ -0,0 +1,321 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2021 Sean Anderson <sean.anderson@seco.com>
|
||||
*
|
||||
* Limitations:
|
||||
* - When changing both duty cycle and period, we may end up with one cycle
|
||||
* with the old duty cycle and the new period. This is because the counters
|
||||
* may only be reloaded by first stopping them, or by letting them be
|
||||
* automatically reloaded at the end of a cycle. If this automatic reload
|
||||
* happens after we set TLR0 but before we set TLR1 then we will have a
|
||||
* bad cycle. This could probably be fixed by reading TCR0 just before
|
||||
* reprogramming, but I think it would add complexity for little gain.
|
||||
* - Cannot produce 100% duty cycle by configuring the TLRs. This might be
|
||||
* possible by stopping the counters at an appropriate point in the cycle,
|
||||
* but this is not (yet) implemented.
|
||||
* - Only produces "normal" output.
|
||||
* - Always produces low output if disabled.
|
||||
*/
|
||||
|
||||
#include <clocksource/timer-xilinx.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
/*
|
||||
* The following functions are "common" to drivers for this device, and may be
|
||||
* exported at a future date.
|
||||
*/
|
||||
u32 xilinx_timer_tlr_cycles(struct xilinx_timer_priv *priv, u32 tcsr,
|
||||
u64 cycles)
|
||||
{
|
||||
WARN_ON(cycles < 2 || cycles - 2 > priv->max);
|
||||
|
||||
if (tcsr & TCSR_UDT)
|
||||
return cycles - 2;
|
||||
return priv->max - cycles + 2;
|
||||
}
|
||||
|
||||
unsigned int xilinx_timer_get_period(struct xilinx_timer_priv *priv,
|
||||
u32 tlr, u32 tcsr)
|
||||
{
|
||||
u64 cycles;
|
||||
|
||||
if (tcsr & TCSR_UDT)
|
||||
cycles = tlr + 2;
|
||||
else
|
||||
cycles = (u64)priv->max - tlr + 2;
|
||||
|
||||
/* cycles has a max of 2^32 + 2, so we can't overflow */
|
||||
return DIV64_U64_ROUND_UP(cycles * NSEC_PER_SEC,
|
||||
clk_get_rate(priv->clk));
|
||||
}
|
||||
|
||||
/*
|
||||
* The idea here is to capture whether the PWM is actually running (e.g.
|
||||
* because we or the bootloader set it up) and we need to be careful to ensure
|
||||
* we don't cause a glitch. According to the data sheet, to enable the PWM we
|
||||
* need to
|
||||
*
|
||||
* - Set both timers to generate mode (MDT=1)
|
||||
* - Set both timers to PWM mode (PWMA=1)
|
||||
* - Enable the generate out signals (GENT=1)
|
||||
*
|
||||
* In addition,
|
||||
*
|
||||
* - The timer must be running (ENT=1)
|
||||
* - The timer must auto-reload TLR into TCR (ARHT=1)
|
||||
* - We must not be in the process of loading TLR into TCR (LOAD=0)
|
||||
* - Cascade mode must be disabled (CASC=0)
|
||||
*
|
||||
* If any of these differ from usual, then the PWM is either disabled, or is
|
||||
* running in a mode that this driver does not support.
|
||||
*/
|
||||
#define TCSR_PWM_SET (TCSR_GENT | TCSR_ARHT | TCSR_ENT | TCSR_PWMA)
|
||||
#define TCSR_PWM_CLEAR (TCSR_MDT | TCSR_LOAD)
|
||||
#define TCSR_PWM_MASK (TCSR_PWM_SET | TCSR_PWM_CLEAR)
|
||||
|
||||
struct xilinx_pwm_device {
|
||||
struct pwm_chip chip;
|
||||
struct xilinx_timer_priv priv;
|
||||
};
|
||||
|
||||
static inline struct xilinx_timer_priv
|
||||
*xilinx_pwm_chip_to_priv(struct pwm_chip *chip)
|
||||
{
|
||||
return &container_of(chip, struct xilinx_pwm_device, chip)->priv;
|
||||
}
|
||||
|
||||
static bool xilinx_timer_pwm_enabled(u32 tcsr0, u32 tcsr1)
|
||||
{
|
||||
return ((TCSR_PWM_MASK | TCSR_CASC) & tcsr0) == TCSR_PWM_SET &&
|
||||
(TCSR_PWM_MASK & tcsr1) == TCSR_PWM_SET;
|
||||
}
|
||||
|
||||
static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
|
||||
const struct pwm_state *state)
|
||||
{
|
||||
struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
|
||||
u32 tlr0, tlr1, tcsr0, tcsr1;
|
||||
u64 period_cycles, duty_cycles;
|
||||
unsigned long rate;
|
||||
|
||||
if (state->polarity != PWM_POLARITY_NORMAL)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* To be representable by TLR, cycles must be between 2 and
|
||||
* priv->max + 2. To enforce this we can reduce the cycles, but we may
|
||||
* not increase them. Caveat emptor: while this does result in more
|
||||
* predictable rounding, it may also result in a completely different
|
||||
* duty cycle (% high time) than what was requested.
|
||||
*/
|
||||
rate = clk_get_rate(priv->clk);
|
||||
/* Avoid overflow */
|
||||
period_cycles = min_t(u64, state->period, U32_MAX * NSEC_PER_SEC);
|
||||
period_cycles = mul_u64_u32_div(period_cycles, rate, NSEC_PER_SEC);
|
||||
period_cycles = min_t(u64, period_cycles, priv->max + 2);
|
||||
if (period_cycles < 2)
|
||||
return -ERANGE;
|
||||
|
||||
/* Same thing for duty cycles */
|
||||
duty_cycles = min_t(u64, state->duty_cycle, U32_MAX * NSEC_PER_SEC);
|
||||
duty_cycles = mul_u64_u32_div(duty_cycles, rate, NSEC_PER_SEC);
|
||||
duty_cycles = min_t(u64, duty_cycles, priv->max + 2);
|
||||
|
||||
/*
|
||||
* If we specify 100% duty cycle, we will get 0% instead, so decrease
|
||||
* the duty cycle count by one.
|
||||
*/
|
||||
if (duty_cycles >= period_cycles)
|
||||
duty_cycles = period_cycles - 1;
|
||||
|
||||
/* Round down to 0% duty cycle for unrepresentable duty cycles */
|
||||
if (duty_cycles < 2)
|
||||
duty_cycles = period_cycles;
|
||||
|
||||
regmap_read(priv->map, TCSR0, &tcsr0);
|
||||
regmap_read(priv->map, TCSR1, &tcsr1);
|
||||
tlr0 = xilinx_timer_tlr_cycles(priv, tcsr0, period_cycles);
|
||||
tlr1 = xilinx_timer_tlr_cycles(priv, tcsr1, duty_cycles);
|
||||
regmap_write(priv->map, TLR0, tlr0);
|
||||
regmap_write(priv->map, TLR1, tlr1);
|
||||
|
||||
if (state->enabled) {
|
||||
/*
|
||||
* If the PWM is already running, then the counters will be
|
||||
* reloaded at the end of the current cycle.
|
||||
*/
|
||||
if (!xilinx_timer_pwm_enabled(tcsr0, tcsr1)) {
|
||||
/* Load TLR into TCR */
|
||||
regmap_write(priv->map, TCSR0, tcsr0 | TCSR_LOAD);
|
||||
regmap_write(priv->map, TCSR1, tcsr1 | TCSR_LOAD);
|
||||
/* Enable timers all at once with ENALL */
|
||||
tcsr0 = (TCSR_PWM_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT);
|
||||
tcsr1 = TCSR_PWM_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT);
|
||||
regmap_write(priv->map, TCSR0, tcsr0);
|
||||
regmap_write(priv->map, TCSR1, tcsr1);
|
||||
}
|
||||
} else {
|
||||
regmap_write(priv->map, TCSR0, 0);
|
||||
regmap_write(priv->map, TCSR1, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void xilinx_pwm_get_state(struct pwm_chip *chip,
|
||||
struct pwm_device *unused,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
|
||||
u32 tlr0, tlr1, tcsr0, tcsr1;
|
||||
|
||||
regmap_read(priv->map, TLR0, &tlr0);
|
||||
regmap_read(priv->map, TLR1, &tlr1);
|
||||
regmap_read(priv->map, TCSR0, &tcsr0);
|
||||
regmap_read(priv->map, TCSR1, &tcsr1);
|
||||
state->period = xilinx_timer_get_period(priv, tlr0, tcsr0);
|
||||
state->duty_cycle = xilinx_timer_get_period(priv, tlr1, tcsr1);
|
||||
state->enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
|
||||
state->polarity = PWM_POLARITY_NORMAL;
|
||||
|
||||
/*
|
||||
* 100% duty cycle results in constant low output. This may be (very)
|
||||
* wrong if rate > 1 GHz, so fix this if you have such hardware :)
|
||||
*/
|
||||
if (state->period == state->duty_cycle)
|
||||
state->duty_cycle = 0;
|
||||
}
|
||||
|
||||
static const struct pwm_ops xilinx_pwm_ops = {
|
||||
.apply = xilinx_pwm_apply,
|
||||
.get_state = xilinx_pwm_get_state,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct regmap_config xilinx_pwm_regmap_config = {
|
||||
.reg_bits = 32,
|
||||
.reg_stride = 4,
|
||||
.val_bits = 32,
|
||||
.val_format_endian = REGMAP_ENDIAN_LITTLE,
|
||||
.max_register = TCR1,
|
||||
};
|
||||
|
||||
static int xilinx_pwm_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct xilinx_timer_priv *priv;
|
||||
struct xilinx_pwm_device *xilinx_pwm;
|
||||
u32 pwm_cells, one_timer, width;
|
||||
void __iomem *regs;
|
||||
|
||||
/* If there are no PWM cells, this binding is for a timer */
|
||||
ret = of_property_read_u32(np, "#pwm-cells", &pwm_cells);
|
||||
if (ret == -EINVAL)
|
||||
return -ENODEV;
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "could not read #pwm-cells\n");
|
||||
|
||||
xilinx_pwm = devm_kzalloc(dev, sizeof(*xilinx_pwm), GFP_KERNEL);
|
||||
if (!xilinx_pwm)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, xilinx_pwm);
|
||||
priv = &xilinx_pwm->priv;
|
||||
|
||||
regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(regs))
|
||||
return PTR_ERR(regs);
|
||||
|
||||
priv->map = devm_regmap_init_mmio(dev, regs,
|
||||
&xilinx_pwm_regmap_config);
|
||||
if (IS_ERR(priv->map))
|
||||
return dev_err_probe(dev, PTR_ERR(priv->map),
|
||||
"Could not create regmap\n");
|
||||
|
||||
ret = of_property_read_u32(np, "xlnx,one-timer-only", &one_timer);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Could not read xlnx,one-timer-only\n");
|
||||
|
||||
if (one_timer)
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"Two timers required for PWM mode\n");
|
||||
|
||||
ret = of_property_read_u32(np, "xlnx,count-width", &width);
|
||||
if (ret == -EINVAL)
|
||||
width = 32;
|
||||
else if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Could not read xlnx,count-width\n");
|
||||
|
||||
if (width != 8 && width != 16 && width != 32)
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"Invalid counter width %d\n", width);
|
||||
priv->max = BIT_ULL(width) - 1;
|
||||
|
||||
/*
|
||||
* The polarity of the Generate Out signals must be active high for PWM
|
||||
* mode to work. We could determine this from the device tree, but
|
||||
* alas, such properties are not allowed to be used.
|
||||
*/
|
||||
|
||||
priv->clk = devm_clk_get(dev, "s_axi_aclk");
|
||||
if (IS_ERR(priv->clk))
|
||||
return dev_err_probe(dev, PTR_ERR(priv->clk),
|
||||
"Could not get clock\n");
|
||||
|
||||
ret = clk_prepare_enable(priv->clk);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Clock enable failed\n");
|
||||
clk_rate_exclusive_get(priv->clk);
|
||||
|
||||
xilinx_pwm->chip.dev = dev;
|
||||
xilinx_pwm->chip.ops = &xilinx_pwm_ops;
|
||||
xilinx_pwm->chip.npwm = 1;
|
||||
ret = pwmchip_add(&xilinx_pwm->chip);
|
||||
if (ret) {
|
||||
clk_rate_exclusive_put(priv->clk);
|
||||
clk_disable_unprepare(priv->clk);
|
||||
return dev_err_probe(dev, ret, "Could not register PWM chip\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xilinx_pwm_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct xilinx_pwm_device *xilinx_pwm = platform_get_drvdata(pdev);
|
||||
|
||||
pwmchip_remove(&xilinx_pwm->chip);
|
||||
clk_rate_exclusive_put(xilinx_pwm->priv.clk);
|
||||
clk_disable_unprepare(xilinx_pwm->priv.clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id xilinx_pwm_of_match[] = {
|
||||
{ .compatible = "xlnx,xps-timer-1.00.a", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, xilinx_pwm_of_match);
|
||||
|
||||
static struct platform_driver xilinx_pwm_driver = {
|
||||
.probe = xilinx_pwm_probe,
|
||||
.remove = xilinx_pwm_remove,
|
||||
.driver = {
|
||||
.name = "xilinx-pwm",
|
||||
.of_match_table = of_match_ptr(xilinx_pwm_of_match),
|
||||
},
|
||||
};
|
||||
module_platform_driver(xilinx_pwm_driver);
|
||||
|
||||
MODULE_ALIAS("platform:xilinx-pwm");
|
||||
MODULE_DESCRIPTION("PWM driver for Xilinx LogiCORE IP AXI Timer");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,73 @@
|
|||
/* 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 */
|
|
@ -0,0 +1,18 @@
|
|||
/* 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
|
Loading…
Reference in New Issue