mirror of
https://github.com/torvalds/linux.git
synced 2026-05-12 16:18:45 +02:00
pwm: Changes for v6.17-rc1
Apart from the usual mix of new drivers (pwm-argon-fan-hat), adding
support for variants to existing drivers, minor improvements to both
drivers and docs, device tree documenation updates, the noteworthy
changes are:
- A pull of pm-runtime-6.17-rc1 to make it possible to apply
a582469541 ("pwm: img: Remove redundant pm_runtime_mark_last_busy()
calls"). Note this updates the base for my tree to 6.16-rc2.
- A hwmon companion driver to pwm-mc33xs2410 living in drivers/hwmon
and acked by Guenter Roeck
- chardev support for PWM devices
This leverages atomic PWM updates to userspace and at the same time
simplifies and accelerates PWM configuration changes.
-----BEGIN PGP SIGNATURE-----
iQEzBAABCgAdFiEEP4GsaTp6HlmJrf7Tj4D7WH0S/k4FAmiHQoMACgkQj4D7WH0S
/k4vGgf+P74bLox7nuZH3lE+D9+HIsfNafELW3TdbmaOF4/iTWOs5/8tUqig3Joc
b82BHjiuxC0mlHYFC0JcCdW29SKjFUWwZMkB4p0R1vICmQ9MVGC89ABvkTx6mVuF
Cse8vuQcRXMqt/oo9tcqsJbJmvpLCV+AlD4rdFDTWplVNmrEX2kG5qmdiUmMjjMW
QNpOxUvdQcKYwrrQjQlEMc+4VmlUN7yDhaw04FzAXg9PPuFfoJEN51PJRdZKDRzP
ScdViYj3tSpQAXb/mJGKWR5brJw+d4BRzFk3kJR5Sj1WAYSQNpKyhAEGXOX6YteT
4eehd1xeUG9a8rBctV3CP+38RxcNew==
=KEjP
-----END PGP SIGNATURE-----
Merge tag 'pwm/for-6.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux
Pull pwm updates from Uwe Kleine-König:
"Apart from the usual mix of new drivers (pwm-argon-fan-hat), adding
support for variants to existing drivers, minor improvements to both
drivers and docs, device tree documenation updates, the noteworthy
changes are:
- A hwmon companion driver to pwm-mc33xs2410 living in drivers/hwmon
and acked by Guenter Roeck
- chardev support for PWM devices. This leverages atomic PWM updates
to userspace and at the same time simplifies and accelerates PWM
configuration changes"
* tag 'pwm/for-6.17-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux: (35 commits)
pwm: raspberrypi-poe: Fix spelling mistake "Firwmware" -> "Firmware"
hwmon: add support for MC33XS2410 hardware monitoring
pwm: mc33xs2410: add hwmon support
pwm: img: Remove redundant pm_runtime_mark_last_busy() calls
pwm: Expose PWM_WFHWSIZE in public header
dt-bindings: pwm: Convert lpc32xx-pwm.txt to yaml format
docs: pwm: Adapt Locking paragraph to reality
pwm: twl-led: Drop driver local locking
pwm: sun4i: Drop driver local locking
pwm: sti: Drop driver local locking
pwm: microchip-core: Drop driver local locking
pwm: lpc18xx-sct: Drop driver local locking
pwm: fsl-ftm: Drop driver local locking
pwm: clps711x: Drop driver local locking
pwm: atmel: Drop driver local locking
pwm: argon-fan-hat: Add Argon40 Fan HAT support
dt-bindings: pwm: argon40,fan-hat: Document Argon40 Fan HAT
dt-bindings: vendor-prefixes: Document Argon40
pwm: pwm-mediatek: Add support for PWM IP V3.0.2 in MT6991/MT8196
pwm: pwm-mediatek: Pass PWM_CK_26M_SEL from platform data
...
This commit is contained in:
commit
f38b751290
|
|
@ -14,7 +14,7 @@ description:
|
|||
The Analog Devices AXI PWM generator can generate PWM signals
|
||||
with variable pulse width and period.
|
||||
|
||||
https://wiki.analog.com/resources/fpga/docs/axi_pwm_gen
|
||||
https://analogdevicesinc.github.io/hdl/library/axi_pwm_gen/index.html
|
||||
|
||||
allOf:
|
||||
- $ref: pwm.yaml#
|
||||
|
|
|
|||
48
Documentation/devicetree/bindings/pwm/argon40,fan-hat.yaml
Normal file
48
Documentation/devicetree/bindings/pwm/argon40,fan-hat.yaml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/pwm/argon40,fan-hat.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Argon40 Fan HAT PWM controller
|
||||
|
||||
maintainers:
|
||||
- Marek Vasut <marek.vasut+renesas@mailbox.org>
|
||||
|
||||
description:
|
||||
The trivial PWM on Argon40 Fan HAT, which is a RaspberryPi blower fan
|
||||
hat which can be controlled over I2C, generates a fixed 30 kHz period
|
||||
PWM signal with configurable 0..100% duty cycle to control the fan
|
||||
speed.
|
||||
|
||||
allOf:
|
||||
- $ref: pwm.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: argon40,fan-hat
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
"#pwm-cells":
|
||||
const: 3
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
pwm@1a {
|
||||
compatible = "argon40,fan-hat";
|
||||
reg = <0x1a>;
|
||||
#pwm-cells = <3>;
|
||||
};
|
||||
};
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
* NXP LPC18xx State Configurable Timer - Pulse Width Modulator driver
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "nxp,lpc1850-sct-pwm"
|
||||
- reg: Should contain physical base address and length of pwm registers.
|
||||
- clocks: Must contain an entry for each entry in clock-names.
|
||||
See ../clock/clock-bindings.txt for details.
|
||||
- clock-names: Must include the following entries.
|
||||
- pwm: PWM operating clock.
|
||||
- #pwm-cells: Should be 3. See pwm.yaml in this directory for the description
|
||||
of the cells format.
|
||||
|
||||
Example:
|
||||
pwm: pwm@40000000 {
|
||||
compatible = "nxp,lpc1850-sct-pwm";
|
||||
reg = <0x40000000 0x1000>;
|
||||
clocks =<&ccu1 CLK_CPU_SCT>;
|
||||
clock-names = "pwm";
|
||||
#pwm-cells = <3>;
|
||||
};
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
LPC32XX PWM controller
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "nxp,lpc3220-pwm"
|
||||
- reg: physical base address and length of the controller's registers
|
||||
|
||||
Examples:
|
||||
|
||||
pwm@4005c000 {
|
||||
compatible = "nxp,lpc3220-pwm";
|
||||
reg = <0x4005c000 0x4>;
|
||||
};
|
||||
|
||||
pwm@4005c004 {
|
||||
compatible = "nxp,lpc3220-pwm";
|
||||
reg = <0x4005c004 0x4>;
|
||||
};
|
||||
|
|
@ -11,26 +11,47 @@ maintainers:
|
|||
|
||||
allOf:
|
||||
- $ref: pwm.yaml#
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: spacemit,k1-pwm
|
||||
then:
|
||||
properties:
|
||||
"#pwm-cells":
|
||||
const: 3
|
||||
else:
|
||||
properties:
|
||||
"#pwm-cells":
|
||||
const: 1
|
||||
description: |
|
||||
Used for specifying the period length in nanoseconds.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- marvell,pxa250-pwm
|
||||
- marvell,pxa270-pwm
|
||||
- marvell,pxa168-pwm
|
||||
- marvell,pxa910-pwm
|
||||
oneOf:
|
||||
- enum:
|
||||
- marvell,pxa250-pwm
|
||||
- marvell,pxa270-pwm
|
||||
- marvell,pxa168-pwm
|
||||
- marvell,pxa910-pwm
|
||||
- items:
|
||||
- const: spacemit,k1-pwm
|
||||
- const: marvell,pxa910-pwm
|
||||
|
||||
reg:
|
||||
# Length should be 0x10
|
||||
maxItems: 1
|
||||
|
||||
"#pwm-cells":
|
||||
# Used for specifying the period length in nanoseconds
|
||||
const: 1
|
||||
description: Number of cells in a pwm specifier.
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ properties:
|
|||
- enum:
|
||||
- mediatek,mt2712-pwm
|
||||
- mediatek,mt6795-pwm
|
||||
- mediatek,mt6991-pwm
|
||||
- mediatek,mt7622-pwm
|
||||
- mediatek,mt7623-pwm
|
||||
- mediatek,mt7628-pwm
|
||||
|
|
@ -32,6 +33,10 @@ properties:
|
|||
- enum:
|
||||
- mediatek,mt8195-pwm
|
||||
- const: mediatek,mt8183-pwm
|
||||
- items:
|
||||
- enum:
|
||||
- mediatek,mt8196-pwm
|
||||
- const: mediatek,mt6991-pwm
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/pwm/nxp,lpc1850-sct-pwm.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: NXP LPC18xx State Configurable Timer
|
||||
|
||||
maintainers:
|
||||
- Frank Li <Frank.Li@nxp.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: nxp,lpc1850-sct-pwm
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: pwm
|
||||
|
||||
'#pwm-cells':
|
||||
const: 3
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- clocks
|
||||
- clock-names
|
||||
- '#pwm-cells'
|
||||
|
||||
allOf:
|
||||
- $ref: pwm.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/lpc18xx-ccu.h>
|
||||
|
||||
pwm@40000000 {
|
||||
compatible = "nxp,lpc1850-sct-pwm";
|
||||
reg = <0x40000000 0x1000>;
|
||||
clocks =<&ccu1 CLK_CPU_SCT>;
|
||||
clock-names = "pwm";
|
||||
#pwm-cells = <3>;
|
||||
};
|
||||
44
Documentation/devicetree/bindings/pwm/nxp,lpc3220-pwm.yaml
Normal file
44
Documentation/devicetree/bindings/pwm/nxp,lpc3220-pwm.yaml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/pwm/nxp,lpc3220-pwm.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: NXP LPC32XX PWM controller
|
||||
|
||||
maintainers:
|
||||
- Frank Li <Frank.Li@nxp.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- nxp,lpc3220-pwm
|
||||
- nxp,lpc3220-motor-pwm
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
'#pwm-cells':
|
||||
const: 3
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- '#pwm-cells'
|
||||
|
||||
allOf:
|
||||
- $ref: pwm.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
pwm@4005c000 {
|
||||
compatible = "nxp,lpc3220-pwm";
|
||||
reg = <0x4005c000 0x4>;
|
||||
#pwm-cells = <3>;
|
||||
};
|
||||
|
||||
|
|
@ -17,7 +17,9 @@ allOf:
|
|||
|
||||
properties:
|
||||
compatible:
|
||||
const: sophgo,sg2042-pwm
|
||||
enum:
|
||||
- sophgo,sg2042-pwm
|
||||
- sophgo,sg2044-pwm
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
|
|
|||
|
|
@ -149,6 +149,8 @@ patternProperties:
|
|||
description: Arctic Sand
|
||||
"^arcx,.*":
|
||||
description: arcx Inc. / Archronix Inc.
|
||||
"^argon40,.*":
|
||||
description: Argon 40 Technologies Limited
|
||||
"^ariaboard,.*":
|
||||
description: Shanghai Novotech Co., Ltd. (Ariaboard)
|
||||
"^aries,.*":
|
||||
|
|
|
|||
|
|
@ -173,10 +173,15 @@ Locking
|
|||
-------
|
||||
|
||||
The PWM core list manipulations are protected by a mutex, so pwm_get()
|
||||
and pwm_put() may not be called from an atomic context. Currently the
|
||||
PWM core does not enforce any locking to pwm_enable(), pwm_disable() and
|
||||
pwm_config(), so the calling context is currently driver specific. This
|
||||
is an issue derived from the former barebone API and should be fixed soon.
|
||||
and pwm_put() may not be called from an atomic context.
|
||||
Most functions in the PWM consumer API might sleep and so must not be called
|
||||
from atomic context. The notable exception is pwm_apply_atomic() which has the
|
||||
same semantics as pwm_apply_might_sleep() but can be called from atomic context.
|
||||
(The price for that is that it doesn't work for all PWM devices, use
|
||||
pwm_might_sleep() to check if a given PWM supports atomic operation.
|
||||
|
||||
Locking in the PWM core ensures that callbacks related to a single chip are
|
||||
serialized.
|
||||
|
||||
Helpers
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ Hardware Monitoring Kernel Drivers
|
|||
max77705
|
||||
max8688
|
||||
mc13783-adc
|
||||
mc33xs2410_hwmon
|
||||
mc34vr500
|
||||
mcp3021
|
||||
menf21bmc
|
||||
|
|
|
|||
34
Documentation/hwmon/mc33xs2410_hwmon.rst
Normal file
34
Documentation/hwmon/mc33xs2410_hwmon.rst
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver mc33xs2410_hwmon
|
||||
==============================
|
||||
|
||||
Supported devices:
|
||||
|
||||
* NXPs MC33XS2410
|
||||
|
||||
Datasheet: https://www.nxp.com/docs/en/data-sheet/MC33XS2410.pdf
|
||||
|
||||
Authors:
|
||||
|
||||
Dimitri Fedrau <dimitri.fedrau@liebherr.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The MC33XS2410 is a four channel self-protected high-side switch featuring
|
||||
hardware monitoring functions such as temperature, current and voltages for each
|
||||
of the four channels.
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
======================= ======================================================
|
||||
temp1_label "Central die temperature"
|
||||
temp1_input Measured temperature of central die
|
||||
|
||||
temp[2-5]_label "Channel [1-4] temperature"
|
||||
temp[2-5]_input Measured temperature of a single channel
|
||||
temp[2-5]_alarm Temperature alarm
|
||||
temp[2-5]_max Maximal temperature
|
||||
======================= ======================================================
|
||||
|
|
@ -49,32 +49,28 @@ led-controller {
|
|||
compatible = "pwm-leds";
|
||||
|
||||
led-d1 {
|
||||
pwms = <&pwm0 0 7812500 PWM_POLARITY_INVERTED>;
|
||||
active-low;
|
||||
pwms = <&pwm0 0 7812500 0>;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
max-brightness = <255>;
|
||||
label = "d1";
|
||||
};
|
||||
|
||||
led-d2 {
|
||||
pwms = <&pwm0 1 7812500 PWM_POLARITY_INVERTED>;
|
||||
active-low;
|
||||
pwms = <&pwm0 1 7812500 0>;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
max-brightness = <255>;
|
||||
label = "d2";
|
||||
};
|
||||
|
||||
led-d3 {
|
||||
pwms = <&pwm0 2 7812500 PWM_POLARITY_INVERTED>;
|
||||
active-low;
|
||||
pwms = <&pwm0 2 7812500 0>;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
max-brightness = <255>;
|
||||
label = "d3";
|
||||
};
|
||||
|
||||
led-d4 {
|
||||
pwms = <&pwm0 3 7812500 PWM_POLARITY_INVERTED>;
|
||||
active-low;
|
||||
pwms = <&pwm0 3 7812500 0>;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
max-brightness = <255>;
|
||||
label = "d4";
|
||||
|
|
|
|||
|
|
@ -51,8 +51,7 @@ led-controller-1 {
|
|||
compatible = "pwm-leds";
|
||||
|
||||
led-d12 {
|
||||
pwms = <&pwm0 0 7812500 PWM_POLARITY_INVERTED>;
|
||||
active-low;
|
||||
pwms = <&pwm0 0 7812500 0>;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
max-brightness = <255>;
|
||||
label = "d12";
|
||||
|
|
@ -68,20 +67,17 @@ multi-led {
|
|||
label = "d2";
|
||||
|
||||
led-red {
|
||||
pwms = <&pwm0 2 7812500 PWM_POLARITY_INVERTED>;
|
||||
active-low;
|
||||
pwms = <&pwm0 2 7812500 0>;
|
||||
color = <LED_COLOR_ID_RED>;
|
||||
};
|
||||
|
||||
led-green {
|
||||
pwms = <&pwm0 1 7812500 PWM_POLARITY_INVERTED>;
|
||||
active-low;
|
||||
pwms = <&pwm0 1 7812500 0>;
|
||||
color = <LED_COLOR_ID_GREEN>;
|
||||
};
|
||||
|
||||
led-blue {
|
||||
pwms = <&pwm0 3 7812500 PWM_POLARITY_INVERTED>;
|
||||
active-low;
|
||||
pwms = <&pwm0 3 7812500 0>;
|
||||
color = <LED_COLOR_ID_BLUE>;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -700,6 +700,16 @@ config SENSORS_MC13783_ADC
|
|||
help
|
||||
Support for the A/D converter on MC13783 and MC13892 PMIC.
|
||||
|
||||
config SENSORS_MC33XS2410
|
||||
tristate "MC33XS2410 HWMON support"
|
||||
depends on PWM_MC33XS2410
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for
|
||||
MC33XS2410.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called mc33xs2410_hwmon.
|
||||
|
||||
config SENSORS_FSCHMD
|
||||
tristate "Fujitsu Siemens Computers sensor chips"
|
||||
depends on (X86 || COMPILE_TEST) && I2C
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ obj-$(CONFIG_SENSORS_MAX31790) += max31790.o
|
|||
obj-$(CONFIG_MAX31827) += max31827.o
|
||||
obj-$(CONFIG_SENSORS_MAX77705) += max77705-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
|
||||
obj-$(CONFIG_SENSORS_MC33XS2410) += mc33xs2410_hwmon.o
|
||||
obj-$(CONFIG_SENSORS_MC34VR500) += mc34vr500.o
|
||||
obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o
|
||||
obj-$(CONFIG_SENSORS_TC654) += tc654.o
|
||||
|
|
|
|||
178
drivers/hwmon/mc33xs2410_hwmon.c
Normal file
178
drivers/hwmon/mc33xs2410_hwmon.c
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2025 Liebherr-Electronics and Drives GmbH
|
||||
*/
|
||||
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/mc33xs2410.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
/* ctrl registers */
|
||||
|
||||
#define MC33XS2410_TEMP_WT 0x29
|
||||
#define MC33XS2410_TEMP_WT_MASK GENMASK(7, 0)
|
||||
|
||||
/* diag registers */
|
||||
|
||||
/* chan in { 1 ... 4 } */
|
||||
#define MC33XS2410_OUT_STA(chan) (0x02 + (chan) - 1)
|
||||
#define MC33XS2410_OUT_STA_OTW BIT(8)
|
||||
|
||||
#define MC33XS2410_TS_TEMP_DIE 0x26
|
||||
#define MC33XS2410_TS_TEMP_MASK GENMASK(9, 0)
|
||||
|
||||
/* chan in { 1 ... 4 } */
|
||||
#define MC33XS2410_TS_TEMP(chan) (0x2f + (chan) - 1)
|
||||
|
||||
static const struct hwmon_channel_info * const mc33xs2410_hwmon_info[] = {
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_LABEL | HWMON_T_INPUT,
|
||||
HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX |
|
||||
HWMON_T_ALARM,
|
||||
HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX |
|
||||
HWMON_T_ALARM,
|
||||
HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX |
|
||||
HWMON_T_ALARM,
|
||||
HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX |
|
||||
HWMON_T_ALARM),
|
||||
NULL,
|
||||
};
|
||||
|
||||
static umode_t mc33xs2410_hwmon_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
case hwmon_temp_alarm:
|
||||
case hwmon_temp_label:
|
||||
return 0444;
|
||||
case hwmon_temp_max:
|
||||
return 0644;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int mc33xs2410_hwmon_read(struct device *dev,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct spi_device *spi = dev_get_drvdata(dev);
|
||||
u16 reg_val;
|
||||
int ret;
|
||||
u8 reg;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
reg = (channel == 0) ? MC33XS2410_TS_TEMP_DIE :
|
||||
MC33XS2410_TS_TEMP(channel);
|
||||
ret = mc33xs2410_read_reg_diag(spi, reg, ®_val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* LSB is 0.25 degree celsius */
|
||||
*val = FIELD_GET(MC33XS2410_TS_TEMP_MASK, reg_val) * 250 - 40000;
|
||||
return 0;
|
||||
case hwmon_temp_alarm:
|
||||
ret = mc33xs2410_read_reg_diag(spi, MC33XS2410_OUT_STA(channel),
|
||||
®_val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = FIELD_GET(MC33XS2410_OUT_STA_OTW, reg_val);
|
||||
return 0;
|
||||
case hwmon_temp_max:
|
||||
ret = mc33xs2410_read_reg_ctrl(spi, MC33XS2410_TEMP_WT, ®_val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* LSB is 1 degree celsius */
|
||||
*val = FIELD_GET(MC33XS2410_TEMP_WT_MASK, reg_val) * 1000 - 40000;
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int mc33xs2410_hwmon_write(struct device *dev,
|
||||
enum hwmon_sensor_types type, u32 attr,
|
||||
int channel, long val)
|
||||
{
|
||||
struct spi_device *spi = dev_get_drvdata(dev);
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_temp_max:
|
||||
val = clamp_val(val, -40000, 215000);
|
||||
|
||||
/* LSB is 1 degree celsius */
|
||||
val = (val / 1000) + 40;
|
||||
return mc33xs2410_modify_reg(spi, MC33XS2410_TEMP_WT,
|
||||
MC33XS2410_TEMP_WT_MASK, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *const mc33xs2410_temp_label[] = {
|
||||
"Central die temperature",
|
||||
"Channel 1 temperature",
|
||||
"Channel 2 temperature",
|
||||
"Channel 3 temperature",
|
||||
"Channel 4 temperature",
|
||||
};
|
||||
|
||||
static int mc33xs2410_read_string(struct device *dev,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, const char **str)
|
||||
{
|
||||
*str = mc33xs2410_temp_label[channel];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_ops mc33xs2410_hwmon_hwmon_ops = {
|
||||
.is_visible = mc33xs2410_hwmon_is_visible,
|
||||
.read = mc33xs2410_hwmon_read,
|
||||
.read_string = mc33xs2410_read_string,
|
||||
.write = mc33xs2410_hwmon_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info mc33xs2410_hwmon_chip_info = {
|
||||
.ops = &mc33xs2410_hwmon_hwmon_ops,
|
||||
.info = mc33xs2410_hwmon_info,
|
||||
};
|
||||
|
||||
static int mc33xs2410_hwmon_probe(struct auxiliary_device *adev,
|
||||
const struct auxiliary_device_id *id)
|
||||
{
|
||||
struct device *dev = &adev->dev;
|
||||
struct spi_device *spi = container_of(dev->parent, struct spi_device, dev);
|
||||
struct device *hwmon;
|
||||
|
||||
hwmon = devm_hwmon_device_register_with_info(dev, NULL, spi,
|
||||
&mc33xs2410_hwmon_chip_info,
|
||||
NULL);
|
||||
return PTR_ERR_OR_ZERO(hwmon);
|
||||
}
|
||||
|
||||
static const struct auxiliary_device_id mc33xs2410_hwmon_ids[] = {
|
||||
{
|
||||
.name = "pwm_mc33xs2410.hwmon",
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(auxiliary, mc33xs2410_hwmon_ids);
|
||||
|
||||
static struct auxiliary_driver mc33xs2410_hwmon_driver = {
|
||||
.probe = mc33xs2410_hwmon_probe,
|
||||
.id_table = mc33xs2410_hwmon_ids,
|
||||
};
|
||||
module_auxiliary_driver(mc33xs2410_hwmon_driver);
|
||||
|
||||
MODULE_DESCRIPTION("NXP MC33XS2410 hwmon driver");
|
||||
MODULE_AUTHOR("Dimitri Fedrau <dimitri.fedrau@liebherr.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -66,6 +66,15 @@ config PWM_APPLE
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called pwm-apple.
|
||||
|
||||
config PWM_ARGON_FAN_HAT
|
||||
tristate "Argon40 Fan HAT support"
|
||||
depends on I2C && OF
|
||||
help
|
||||
Generic PWM framework driver for Argon40 Fan HAT.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called pwm-argon-fan-hat.
|
||||
|
||||
config PWM_ATMEL
|
||||
tristate "Atmel PWM support"
|
||||
depends on ARCH_AT91 || COMPILE_TEST
|
||||
|
|
@ -427,6 +436,7 @@ config PWM_MC33XS2410
|
|||
tristate "MC33XS2410 PWM support"
|
||||
depends on OF
|
||||
depends on SPI
|
||||
select AUXILIARY_BUS
|
||||
help
|
||||
NXP MC33XS2410 high-side switch driver. The MC33XS2410 is a four
|
||||
channel high-side switch. The device is operational from 3.0 V
|
||||
|
|
@ -517,7 +527,7 @@ config PWM_PCA9685
|
|||
|
||||
config PWM_PXA
|
||||
tristate "PXA PWM support"
|
||||
depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST
|
||||
depends on ARCH_PXA || ARCH_MMP || ARCH_SPACEMIT || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
Generic PWM framework driver for PXA.
|
||||
|
|
@ -526,7 +536,7 @@ config PWM_PXA
|
|||
will be called pwm-pxa.
|
||||
|
||||
config PWM_RASPBERRYPI_POE
|
||||
tristate "Raspberry Pi Firwmware PoE Hat PWM support"
|
||||
tristate "Raspberry Pi Firmware PoE Hat PWM support"
|
||||
# Make sure not 'y' when RASPBERRYPI_FIRMWARE is 'm'. This can only
|
||||
# happen when COMPILE_TEST=y, hence the added !RASPBERRYPI_FIRMWARE.
|
||||
depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ obj-$(CONFIG_PWM) += core.o
|
|||
obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
|
||||
obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o
|
||||
obj-$(CONFIG_PWM_APPLE) += pwm-apple.o
|
||||
obj-$(CONFIG_PWM_ARGON_FAN_HAT) += pwm-argon-fan-hat.o
|
||||
obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o
|
||||
obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o
|
||||
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
|
||||
|
|
|
|||
|
|
@ -23,9 +23,13 @@
|
|||
|
||||
#include <dt-bindings/pwm/pwm.h>
|
||||
|
||||
#include <uapi/linux/pwm.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/pwm.h>
|
||||
|
||||
#define PWM_MINOR_COUNT 256
|
||||
|
||||
/* protects access to pwm_chips */
|
||||
static DEFINE_MUTEX(pwm_lock);
|
||||
|
||||
|
|
@ -206,8 +210,6 @@ static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, c
|
|||
return ret;
|
||||
}
|
||||
|
||||
#define WFHWSIZE 20
|
||||
|
||||
/**
|
||||
* pwm_round_waveform_might_sleep - Query hardware capabilities
|
||||
* Cannot be used in atomic context.
|
||||
|
|
@ -244,10 +246,10 @@ int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *
|
|||
struct pwm_chip *chip = pwm->chip;
|
||||
const struct pwm_ops *ops = chip->ops;
|
||||
struct pwm_waveform wf_req = *wf;
|
||||
char wfhw[WFHWSIZE];
|
||||
char wfhw[PWM_WFHWSIZE];
|
||||
int ret_tohw, ret_fromhw;
|
||||
|
||||
BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
|
||||
BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
|
||||
|
||||
if (!pwmchip_supports_waveform(chip))
|
||||
return -EOPNOTSUPP;
|
||||
|
|
@ -302,10 +304,10 @@ int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf
|
|||
{
|
||||
struct pwm_chip *chip = pwm->chip;
|
||||
const struct pwm_ops *ops = chip->ops;
|
||||
char wfhw[WFHWSIZE];
|
||||
char wfhw[PWM_WFHWSIZE];
|
||||
int err;
|
||||
|
||||
BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
|
||||
BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
|
||||
|
||||
if (!pwmchip_supports_waveform(chip) || !ops->read_waveform)
|
||||
return -EOPNOTSUPP;
|
||||
|
|
@ -330,11 +332,11 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
|
|||
{
|
||||
struct pwm_chip *chip = pwm->chip;
|
||||
const struct pwm_ops *ops = chip->ops;
|
||||
char wfhw[WFHWSIZE];
|
||||
char wfhw[PWM_WFHWSIZE];
|
||||
struct pwm_waveform wf_rounded;
|
||||
int err, ret_tohw;
|
||||
|
||||
BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
|
||||
BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
|
||||
|
||||
if (!pwmchip_supports_waveform(chip))
|
||||
return -EOPNOTSUPP;
|
||||
|
|
@ -646,9 +648,9 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
|
|||
|
||||
if (pwmchip_supports_waveform(chip)) {
|
||||
struct pwm_waveform wf;
|
||||
char wfhw[WFHWSIZE];
|
||||
char wfhw[PWM_WFHWSIZE];
|
||||
|
||||
BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
|
||||
BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
|
||||
|
||||
pwm_state2wf(state, &wf);
|
||||
|
||||
|
|
@ -805,10 +807,10 @@ int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
|
|||
return -ENODEV;
|
||||
|
||||
if (pwmchip_supports_waveform(chip) && ops->read_waveform) {
|
||||
char wfhw[WFHWSIZE];
|
||||
char wfhw[PWM_WFHWSIZE];
|
||||
struct pwm_waveform wf;
|
||||
|
||||
BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
|
||||
BUG_ON(PWM_WFHWSIZE < ops->sizeof_wfhw);
|
||||
|
||||
ret = __pwm_read_waveform(chip, pwm, &wfhw);
|
||||
if (ret)
|
||||
|
|
@ -1692,8 +1694,8 @@ static bool pwm_ops_check(const struct pwm_chip *chip)
|
|||
!ops->write_waveform)
|
||||
return false;
|
||||
|
||||
if (WFHWSIZE < ops->sizeof_wfhw) {
|
||||
dev_warn(pwmchip_parent(chip), "WFHWSIZE < %zu\n", ops->sizeof_wfhw);
|
||||
if (PWM_WFHWSIZE < ops->sizeof_wfhw) {
|
||||
dev_warn(pwmchip_parent(chip), "PWM_WFHWSIZE < %zu\n", ops->sizeof_wfhw);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -2007,20 +2009,9 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(pwm_get);
|
||||
|
||||
/**
|
||||
* pwm_put() - release a PWM device
|
||||
* @pwm: PWM device
|
||||
*/
|
||||
void pwm_put(struct pwm_device *pwm)
|
||||
static void __pwm_put(struct pwm_device *pwm)
|
||||
{
|
||||
struct pwm_chip *chip;
|
||||
|
||||
if (!pwm)
|
||||
return;
|
||||
|
||||
chip = pwm->chip;
|
||||
|
||||
guard(mutex)(&pwm_lock);
|
||||
struct pwm_chip *chip = pwm->chip;
|
||||
|
||||
/*
|
||||
* Trigger a warning if a consumer called pwm_put() twice.
|
||||
|
|
@ -2041,6 +2032,20 @@ void pwm_put(struct pwm_device *pwm)
|
|||
|
||||
module_put(chip->owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* pwm_put() - release a PWM device
|
||||
* @pwm: PWM device
|
||||
*/
|
||||
void pwm_put(struct pwm_device *pwm)
|
||||
{
|
||||
if (!pwm)
|
||||
return;
|
||||
|
||||
guard(mutex)(&pwm_lock);
|
||||
|
||||
__pwm_put(pwm);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pwm_put);
|
||||
|
||||
static void devm_pwm_release(void *pwm)
|
||||
|
|
@ -2110,6 +2115,274 @@ struct pwm_device *devm_fwnode_pwm_get(struct device *dev,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get);
|
||||
|
||||
struct pwm_cdev_data {
|
||||
struct pwm_chip *chip;
|
||||
struct pwm_device *pwm[];
|
||||
};
|
||||
|
||||
static int pwm_cdev_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct pwm_chip *chip = container_of(inode->i_cdev, struct pwm_chip, cdev);
|
||||
struct pwm_cdev_data *cdata;
|
||||
|
||||
guard(mutex)(&pwm_lock);
|
||||
|
||||
if (!chip->operational)
|
||||
return -ENXIO;
|
||||
|
||||
cdata = kzalloc(struct_size(cdata, pwm, chip->npwm), GFP_KERNEL);
|
||||
if (!cdata)
|
||||
return -ENOMEM;
|
||||
|
||||
cdata->chip = chip;
|
||||
|
||||
file->private_data = cdata;
|
||||
|
||||
return nonseekable_open(inode, file);
|
||||
}
|
||||
|
||||
static int pwm_cdev_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct pwm_cdev_data *cdata = file->private_data;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < cdata->chip->npwm; ++i) {
|
||||
struct pwm_device *pwm = cdata->pwm[i];
|
||||
|
||||
if (pwm) {
|
||||
const char *label = pwm->label;
|
||||
|
||||
pwm_put(cdata->pwm[i]);
|
||||
kfree(label);
|
||||
}
|
||||
}
|
||||
kfree(cdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwm_cdev_request(struct pwm_cdev_data *cdata, unsigned int hwpwm)
|
||||
{
|
||||
struct pwm_chip *chip = cdata->chip;
|
||||
|
||||
if (hwpwm >= chip->npwm)
|
||||
return -EINVAL;
|
||||
|
||||
if (!cdata->pwm[hwpwm]) {
|
||||
struct pwm_device *pwm = &chip->pwms[hwpwm];
|
||||
const char *label;
|
||||
int ret;
|
||||
|
||||
label = kasprintf(GFP_KERNEL, "pwm-cdev (pid=%d)", current->pid);
|
||||
if (!label)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = pwm_device_request(pwm, label);
|
||||
if (ret < 0) {
|
||||
kfree(label);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cdata->pwm[hwpwm] = pwm;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pwm_cdev_free(struct pwm_cdev_data *cdata, unsigned int hwpwm)
|
||||
{
|
||||
struct pwm_chip *chip = cdata->chip;
|
||||
|
||||
if (hwpwm >= chip->npwm)
|
||||
return -EINVAL;
|
||||
|
||||
if (cdata->pwm[hwpwm]) {
|
||||
struct pwm_device *pwm = cdata->pwm[hwpwm];
|
||||
const char *label = pwm->label;
|
||||
|
||||
__pwm_put(pwm);
|
||||
|
||||
kfree(label);
|
||||
|
||||
cdata->pwm[hwpwm] = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pwm_device *pwm_cdev_get_requested_pwm(struct pwm_cdev_data *cdata,
|
||||
u32 hwpwm)
|
||||
{
|
||||
struct pwm_chip *chip = cdata->chip;
|
||||
|
||||
if (hwpwm >= chip->npwm)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (cdata->pwm[hwpwm])
|
||||
return cdata->pwm[hwpwm];
|
||||
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
static long pwm_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int ret = 0;
|
||||
struct pwm_cdev_data *cdata = file->private_data;
|
||||
struct pwm_chip *chip = cdata->chip;
|
||||
|
||||
guard(mutex)(&pwm_lock);
|
||||
|
||||
if (!chip->operational)
|
||||
return -ENODEV;
|
||||
|
||||
switch (cmd) {
|
||||
case PWM_IOCTL_REQUEST:
|
||||
{
|
||||
unsigned int hwpwm = arg;
|
||||
|
||||
return pwm_cdev_request(cdata, hwpwm);
|
||||
}
|
||||
|
||||
case PWM_IOCTL_FREE:
|
||||
{
|
||||
unsigned int hwpwm = arg;
|
||||
|
||||
return pwm_cdev_free(cdata, hwpwm);
|
||||
}
|
||||
|
||||
case PWM_IOCTL_ROUNDWF:
|
||||
{
|
||||
struct pwmchip_waveform cwf;
|
||||
struct pwm_waveform wf;
|
||||
struct pwm_device *pwm;
|
||||
|
||||
ret = copy_from_user(&cwf,
|
||||
(struct pwmchip_waveform __user *)arg,
|
||||
sizeof(cwf));
|
||||
if (ret)
|
||||
return -EFAULT;
|
||||
|
||||
if (cwf.__pad != 0)
|
||||
return -EINVAL;
|
||||
|
||||
pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm);
|
||||
if (IS_ERR(pwm))
|
||||
return PTR_ERR(pwm);
|
||||
|
||||
wf = (struct pwm_waveform) {
|
||||
.period_length_ns = cwf.period_length_ns,
|
||||
.duty_length_ns = cwf.duty_length_ns,
|
||||
.duty_offset_ns = cwf.duty_offset_ns,
|
||||
};
|
||||
|
||||
ret = pwm_round_waveform_might_sleep(pwm, &wf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
cwf = (struct pwmchip_waveform) {
|
||||
.hwpwm = cwf.hwpwm,
|
||||
.period_length_ns = wf.period_length_ns,
|
||||
.duty_length_ns = wf.duty_length_ns,
|
||||
.duty_offset_ns = wf.duty_offset_ns,
|
||||
};
|
||||
|
||||
return copy_to_user((struct pwmchip_waveform __user *)arg,
|
||||
&cwf, sizeof(cwf));
|
||||
}
|
||||
|
||||
case PWM_IOCTL_GETWF:
|
||||
{
|
||||
struct pwmchip_waveform cwf;
|
||||
struct pwm_waveform wf;
|
||||
struct pwm_device *pwm;
|
||||
|
||||
ret = copy_from_user(&cwf,
|
||||
(struct pwmchip_waveform __user *)arg,
|
||||
sizeof(cwf));
|
||||
if (ret)
|
||||
return -EFAULT;
|
||||
|
||||
if (cwf.__pad != 0)
|
||||
return -EINVAL;
|
||||
|
||||
pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm);
|
||||
if (IS_ERR(pwm))
|
||||
return PTR_ERR(pwm);
|
||||
|
||||
ret = pwm_get_waveform_might_sleep(pwm, &wf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
cwf = (struct pwmchip_waveform) {
|
||||
.hwpwm = cwf.hwpwm,
|
||||
.period_length_ns = wf.period_length_ns,
|
||||
.duty_length_ns = wf.duty_length_ns,
|
||||
.duty_offset_ns = wf.duty_offset_ns,
|
||||
};
|
||||
|
||||
return copy_to_user((struct pwmchip_waveform __user *)arg,
|
||||
&cwf, sizeof(cwf));
|
||||
}
|
||||
|
||||
case PWM_IOCTL_SETROUNDEDWF:
|
||||
case PWM_IOCTL_SETEXACTWF:
|
||||
{
|
||||
struct pwmchip_waveform cwf;
|
||||
struct pwm_waveform wf;
|
||||
struct pwm_device *pwm;
|
||||
|
||||
ret = copy_from_user(&cwf,
|
||||
(struct pwmchip_waveform __user *)arg,
|
||||
sizeof(cwf));
|
||||
if (ret)
|
||||
return -EFAULT;
|
||||
|
||||
if (cwf.__pad != 0)
|
||||
return -EINVAL;
|
||||
|
||||
wf = (struct pwm_waveform){
|
||||
.period_length_ns = cwf.period_length_ns,
|
||||
.duty_length_ns = cwf.duty_length_ns,
|
||||
.duty_offset_ns = cwf.duty_offset_ns,
|
||||
};
|
||||
|
||||
if (!pwm_wf_valid(&wf))
|
||||
return -EINVAL;
|
||||
|
||||
pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm);
|
||||
if (IS_ERR(pwm))
|
||||
return PTR_ERR(pwm);
|
||||
|
||||
ret = pwm_set_waveform_might_sleep(pwm, &wf,
|
||||
cmd == PWM_IOCTL_SETEXACTWF);
|
||||
|
||||
/*
|
||||
* If userspace cares about rounding deviations it has
|
||||
* to check the values anyhow, so simplify handling for
|
||||
* them and don't signal uprounding. This matches the
|
||||
* behaviour of PWM_IOCTL_ROUNDWF which also returns 0
|
||||
* in that case.
|
||||
*/
|
||||
if (ret == 1)
|
||||
ret = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct file_operations pwm_cdev_fileops = {
|
||||
.open = pwm_cdev_open,
|
||||
.release = pwm_cdev_release,
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = pwm_cdev_ioctl,
|
||||
};
|
||||
|
||||
static dev_t pwm_devt;
|
||||
|
||||
/**
|
||||
* __pwmchip_add() - register a new PWM chip
|
||||
* @chip: the PWM chip to add
|
||||
|
|
@ -2162,7 +2435,17 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
|
|||
scoped_guard(pwmchip, chip)
|
||||
chip->operational = true;
|
||||
|
||||
ret = device_add(&chip->dev);
|
||||
if (chip->ops->write_waveform) {
|
||||
if (chip->id < PWM_MINOR_COUNT)
|
||||
chip->dev.devt = MKDEV(MAJOR(pwm_devt), chip->id);
|
||||
else
|
||||
dev_warn(&chip->dev, "chip id too high to create a chardev\n");
|
||||
}
|
||||
|
||||
cdev_init(&chip->cdev, &pwm_cdev_fileops);
|
||||
chip->cdev.owner = owner;
|
||||
|
||||
ret = cdev_device_add(&chip->cdev, &chip->dev);
|
||||
if (ret)
|
||||
goto err_device_add;
|
||||
|
||||
|
|
@ -2213,7 +2496,7 @@ void pwmchip_remove(struct pwm_chip *chip)
|
|||
idr_remove(&pwm_chips, chip->id);
|
||||
}
|
||||
|
||||
device_del(&chip->dev);
|
||||
cdev_device_del(&chip->cdev, &chip->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pwmchip_remove);
|
||||
|
||||
|
|
@ -2357,9 +2640,16 @@ static int __init pwm_init(void)
|
|||
{
|
||||
int ret;
|
||||
|
||||
ret = alloc_chrdev_region(&pwm_devt, 0, PWM_MINOR_COUNT, "pwm");
|
||||
if (ret) {
|
||||
pr_err("Failed to initialize chrdev region for PWM usage\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = class_register(&pwm_class);
|
||||
if (ret) {
|
||||
pr_err("Failed to initialize PWM class (%pe)\n", ERR_PTR(ret));
|
||||
unregister_chrdev_region(pwm_devt, 256);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
109
drivers/pwm/pwm-argon-fan-hat.c
Normal file
109
drivers/pwm/pwm-argon-fan-hat.c
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2025 Marek Vasut
|
||||
*
|
||||
* Limitations:
|
||||
* - no support for offset/polarity
|
||||
* - fixed 30 kHz period
|
||||
*
|
||||
* Argon Fan HAT https://argon40.com/products/argon-fan-hat
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pwm.h>
|
||||
|
||||
#define ARGON40_FAN_HAT_PERIOD_NS 33333 /* ~30 kHz */
|
||||
|
||||
#define ARGON40_FAN_HAT_REG_DUTY_CYCLE 0x80
|
||||
|
||||
static int argon_fan_hat_round_waveform_tohw(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
const struct pwm_waveform *wf,
|
||||
void *_wfhw)
|
||||
{
|
||||
u8 *wfhw = _wfhw;
|
||||
|
||||
if (wf->duty_length_ns > ARGON40_FAN_HAT_PERIOD_NS)
|
||||
*wfhw = 100;
|
||||
else
|
||||
*wfhw = mul_u64_u64_div_u64(wf->duty_length_ns, 100, ARGON40_FAN_HAT_PERIOD_NS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int argon_fan_hat_round_waveform_fromhw(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
const void *_wfhw,
|
||||
struct pwm_waveform *wf)
|
||||
{
|
||||
const u8 *wfhw = _wfhw;
|
||||
|
||||
wf->period_length_ns = ARGON40_FAN_HAT_PERIOD_NS;
|
||||
wf->duty_length_ns = DIV64_U64_ROUND_UP(wf->period_length_ns * *wfhw, 100);
|
||||
wf->duty_offset_ns = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int argon_fan_hat_write_waveform(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
const void *_wfhw)
|
||||
{
|
||||
struct i2c_client *i2c = pwmchip_get_drvdata(chip);
|
||||
const u8 *wfhw = _wfhw;
|
||||
|
||||
return i2c_smbus_write_byte_data(i2c, ARGON40_FAN_HAT_REG_DUTY_CYCLE, *wfhw);
|
||||
}
|
||||
|
||||
static const struct pwm_ops argon_fan_hat_pwm_ops = {
|
||||
.sizeof_wfhw = sizeof(u8),
|
||||
.round_waveform_fromhw = argon_fan_hat_round_waveform_fromhw,
|
||||
.round_waveform_tohw = argon_fan_hat_round_waveform_tohw,
|
||||
.write_waveform = argon_fan_hat_write_waveform,
|
||||
/*
|
||||
* The controller does not provide any way to read info back,
|
||||
* reading from the controller stops the fan, therefore there
|
||||
* is no .read_waveform here.
|
||||
*/
|
||||
};
|
||||
|
||||
static int argon_fan_hat_i2c_probe(struct i2c_client *i2c)
|
||||
{
|
||||
struct pwm_chip *chip = devm_pwmchip_alloc(&i2c->dev, 1, 0);
|
||||
int ret;
|
||||
|
||||
if (IS_ERR(chip))
|
||||
return PTR_ERR(chip);
|
||||
|
||||
chip->ops = &argon_fan_hat_pwm_ops;
|
||||
pwmchip_set_drvdata(chip, i2c);
|
||||
|
||||
ret = devm_pwmchip_add(&i2c->dev, chip);
|
||||
if (ret)
|
||||
return dev_err_probe(&i2c->dev, ret, "Could not add PWM chip\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id argon_fan_hat_dt_ids[] = {
|
||||
{ .compatible = "argon40,fan-hat" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, argon_fan_hat_dt_ids);
|
||||
|
||||
static struct i2c_driver argon_fan_hat_driver = {
|
||||
.driver = {
|
||||
.name = "argon-fan-hat",
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
.of_match_table = argon_fan_hat_dt_ids,
|
||||
},
|
||||
.probe = argon_fan_hat_i2c_probe,
|
||||
};
|
||||
|
||||
module_i2c_driver(argon_fan_hat_driver);
|
||||
|
||||
MODULE_AUTHOR("Marek Vasut <marek.vasut+renesas@mailbox.org>");
|
||||
MODULE_DESCRIPTION("Argon40 Fan HAT");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -91,9 +91,6 @@ struct atmel_pwm_chip {
|
|||
* hardware.
|
||||
*/
|
||||
u32 update_pending;
|
||||
|
||||
/* Protects .update_pending */
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
static inline struct atmel_pwm_chip *to_atmel_pwm_chip(struct pwm_chip *chip)
|
||||
|
|
@ -145,8 +142,6 @@ static void atmel_pwm_update_pending(struct atmel_pwm_chip *chip)
|
|||
|
||||
static void atmel_pwm_set_pending(struct atmel_pwm_chip *chip, unsigned int ch)
|
||||
{
|
||||
spin_lock(&chip->lock);
|
||||
|
||||
/*
|
||||
* Clear pending flags in hardware because otherwise there might still
|
||||
* be a stale flag in ISR.
|
||||
|
|
@ -154,16 +149,12 @@ static void atmel_pwm_set_pending(struct atmel_pwm_chip *chip, unsigned int ch)
|
|||
atmel_pwm_update_pending(chip);
|
||||
|
||||
chip->update_pending |= (1 << ch);
|
||||
|
||||
spin_unlock(&chip->lock);
|
||||
}
|
||||
|
||||
static int atmel_pwm_test_pending(struct atmel_pwm_chip *chip, unsigned int ch)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
spin_lock(&chip->lock);
|
||||
|
||||
if (chip->update_pending & (1 << ch)) {
|
||||
atmel_pwm_update_pending(chip);
|
||||
|
||||
|
|
@ -171,8 +162,6 @@ static int atmel_pwm_test_pending(struct atmel_pwm_chip *chip, unsigned int ch)
|
|||
ret = 1;
|
||||
}
|
||||
|
||||
spin_unlock(&chip->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -509,7 +498,6 @@ static int atmel_pwm_probe(struct platform_device *pdev)
|
|||
atmel_pwm->data = of_device_get_match_data(&pdev->dev);
|
||||
|
||||
atmel_pwm->update_pending = 0;
|
||||
spin_lock_init(&atmel_pwm->lock);
|
||||
|
||||
atmel_pwm->base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(atmel_pwm->base))
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
struct clps711x_chip {
|
||||
void __iomem *pmpcon;
|
||||
struct clk *clk;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
static inline struct clps711x_chip *to_clps711x_chip(struct pwm_chip *chip)
|
||||
|
|
@ -42,7 +41,6 @@ static int clps711x_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
struct clps711x_chip *priv = to_clps711x_chip(chip);
|
||||
/* PWM0 - bits 4..7, PWM1 - bits 8..11 */
|
||||
u32 shift = (pwm->hwpwm + 1) * 4;
|
||||
unsigned long flags;
|
||||
u32 pmpcon, val;
|
||||
|
||||
if (state->polarity != PWM_POLARITY_NORMAL)
|
||||
|
|
@ -56,15 +54,11 @@ static int clps711x_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
@ -93,8 +87,6 @@ static int clps711x_pwm_probe(struct platform_device *pdev)
|
|||
|
||||
chip->ops = &clps711x_pwm_ops;
|
||||
|
||||
spin_lock_init(&priv->lock);
|
||||
|
||||
return devm_pwmchip_add(&pdev->dev, chip);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
|
|
@ -40,7 +39,6 @@ struct fsl_pwm_periodcfg {
|
|||
};
|
||||
|
||||
struct fsl_pwm_chip {
|
||||
struct mutex lock;
|
||||
struct regmap *regmap;
|
||||
|
||||
/* This value is valid iff a pwm is running */
|
||||
|
|
@ -89,11 +87,8 @@ static int fsl_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
|
||||
|
||||
ret = clk_prepare_enable(fpc->ipg_clk);
|
||||
if (!ret && fpc->soc->has_enable_bits) {
|
||||
mutex_lock(&fpc->lock);
|
||||
if (!ret && fpc->soc->has_enable_bits)
|
||||
regmap_set_bits(fpc->regmap, FTM_SC, BIT(pwm->hwpwm + 16));
|
||||
mutex_unlock(&fpc->lock);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -102,11 +97,8 @@ static void fsl_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
{
|
||||
struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
|
||||
|
||||
if (fpc->soc->has_enable_bits) {
|
||||
mutex_lock(&fpc->lock);
|
||||
if (fpc->soc->has_enable_bits)
|
||||
regmap_clear_bits(fpc->regmap, FTM_SC, BIT(pwm->hwpwm + 16));
|
||||
mutex_unlock(&fpc->lock);
|
||||
}
|
||||
|
||||
clk_disable_unprepare(fpc->ipg_clk);
|
||||
}
|
||||
|
|
@ -304,7 +296,7 @@ static int fsl_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
{
|
||||
struct fsl_pwm_chip *fpc = to_fsl_chip(chip);
|
||||
struct pwm_state *oldstate = &pwm->state;
|
||||
int ret = 0;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* oldstate to newstate : action
|
||||
|
|
@ -315,8 +307,6 @@ static int fsl_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
* disabled to enabled : update settings + enable
|
||||
*/
|
||||
|
||||
mutex_lock(&fpc->lock);
|
||||
|
||||
if (!newstate->enabled) {
|
||||
if (oldstate->enabled) {
|
||||
regmap_set_bits(fpc->regmap, FTM_OUTMASK,
|
||||
|
|
@ -325,30 +315,28 @@ static int fsl_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
clk_disable_unprepare(fpc->clk[fpc->period.clk_select]);
|
||||
}
|
||||
|
||||
goto end_mutex;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = fsl_pwm_apply_config(chip, pwm, newstate);
|
||||
if (ret)
|
||||
goto end_mutex;
|
||||
return ret;
|
||||
|
||||
/* check if need to enable */
|
||||
if (!oldstate->enabled) {
|
||||
ret = clk_prepare_enable(fpc->clk[fpc->period.clk_select]);
|
||||
if (ret)
|
||||
goto end_mutex;
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]);
|
||||
if (ret) {
|
||||
clk_disable_unprepare(fpc->clk[fpc->period.clk_select]);
|
||||
goto end_mutex;
|
||||
return ret;
|
||||
}
|
||||
|
||||
regmap_clear_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm));
|
||||
}
|
||||
|
||||
end_mutex:
|
||||
mutex_unlock(&fpc->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -408,8 +396,6 @@ static int fsl_pwm_probe(struct platform_device *pdev)
|
|||
return PTR_ERR(chip);
|
||||
fpc = to_fsl_chip(chip);
|
||||
|
||||
mutex_init(&fpc->lock);
|
||||
|
||||
fpc->soc = of_device_get_match_data(&pdev->dev);
|
||||
|
||||
base = devm_platform_ioremap_resource(pdev, 0);
|
||||
|
|
|
|||
|
|
@ -139,7 +139,6 @@ static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
(timebase << PWM_CH_CFG_TMBASE_SHIFT);
|
||||
img_pwm_writel(imgchip, PWM_CH_CFG(pwm->hwpwm), val);
|
||||
|
||||
pm_runtime_mark_last_busy(pwmchip_parent(chip));
|
||||
pm_runtime_put_autosuspend(pwmchip_parent(chip));
|
||||
|
||||
return 0;
|
||||
|
|
@ -175,7 +174,6 @@ static void img_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
val &= ~BIT(pwm->hwpwm);
|
||||
img_pwm_writel(imgchip, PWM_CTRL_CFG, val);
|
||||
|
||||
pm_runtime_mark_last_busy(pwmchip_parent(chip));
|
||||
pm_runtime_put_autosuspend(pwmchip_parent(chip));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,8 +100,6 @@ struct lpc18xx_pwm_chip {
|
|||
u64 max_period_ns;
|
||||
unsigned int period_event;
|
||||
unsigned long event_map;
|
||||
struct mutex res_lock;
|
||||
struct mutex period_lock;
|
||||
struct lpc18xx_pwm_data channeldata[LPC18XX_NUM_PWMS];
|
||||
};
|
||||
|
||||
|
|
@ -129,8 +127,6 @@ static void lpc18xx_pwm_set_conflict_res(struct lpc18xx_pwm_chip *lpc18xx_pwm,
|
|||
{
|
||||
u32 val;
|
||||
|
||||
mutex_lock(&lpc18xx_pwm->res_lock);
|
||||
|
||||
/*
|
||||
* Simultaneous set and clear may happen on an output, that is the case
|
||||
* when duty_ns == period_ns. LPC18xx SCT allows to set a conflict
|
||||
|
|
@ -140,8 +136,6 @@ static void lpc18xx_pwm_set_conflict_res(struct lpc18xx_pwm_chip *lpc18xx_pwm,
|
|||
val &= ~LPC18XX_PWM_RES_MASK(pwm->hwpwm);
|
||||
val |= LPC18XX_PWM_RES(pwm->hwpwm, action);
|
||||
lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_RES_BASE, val);
|
||||
|
||||
mutex_unlock(&lpc18xx_pwm->res_lock);
|
||||
}
|
||||
|
||||
static void lpc18xx_pwm_config_period(struct pwm_chip *chip, u64 period_ns)
|
||||
|
|
@ -200,8 +194,6 @@ static int lpc18xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
return -ERANGE;
|
||||
}
|
||||
|
||||
mutex_lock(&lpc18xx_pwm->period_lock);
|
||||
|
||||
requested_events = bitmap_weight(&lpc18xx_pwm->event_map,
|
||||
LPC18XX_PWM_EVENT_MAX);
|
||||
|
||||
|
|
@ -214,7 +206,6 @@ static int lpc18xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
lpc18xx_pwm->period_ns) {
|
||||
dev_err(pwmchip_parent(chip), "conflicting period requested for PWM %u\n",
|
||||
pwm->hwpwm);
|
||||
mutex_unlock(&lpc18xx_pwm->period_lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
|
|
@ -224,8 +215,6 @@ static int lpc18xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
lpc18xx_pwm_config_period(chip, period_ns);
|
||||
}
|
||||
|
||||
mutex_unlock(&lpc18xx_pwm->period_lock);
|
||||
|
||||
lpc18xx_pwm_config_duty(chip, pwm, duty_ns);
|
||||
|
||||
return 0;
|
||||
|
|
@ -377,9 +366,6 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev)
|
|||
if (lpc18xx_pwm->clk_rate > NSEC_PER_SEC)
|
||||
return dev_err_probe(&pdev->dev, -EINVAL, "pwm clock to fast\n");
|
||||
|
||||
mutex_init(&lpc18xx_pwm->res_lock);
|
||||
mutex_init(&lpc18xx_pwm->period_lock);
|
||||
|
||||
lpc18xx_pwm->max_period_ns =
|
||||
mul_u64_u64_div_u64(NSEC_PER_SEC, LPC18XX_PWM_TIMER_MAX, lpc18xx_pwm->clk_rate);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,11 +17,14 @@
|
|||
* behavior of the output pin that is neither the old nor the new state,
|
||||
* rather something in between.
|
||||
*/
|
||||
#define DEFAULT_SYMBOL_NAMESPACE "PWM_MC33XS2410"
|
||||
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/mc33xs2410.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
|
|
@ -120,12 +123,19 @@ static int mc33xs2410_read_reg(struct spi_device *spi, u8 reg, u16 *val, u8 flag
|
|||
return mc33xs2410_read_regs(spi, ®, flag, val, 1);
|
||||
}
|
||||
|
||||
static int mc33xs2410_read_reg_ctrl(struct spi_device *spi, u8 reg, u16 *val)
|
||||
int mc33xs2410_read_reg_ctrl(struct spi_device *spi, u8 reg, u16 *val)
|
||||
{
|
||||
return mc33xs2410_read_reg(spi, reg, val, MC33XS2410_FRAME_IN_DATA_RD);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mc33xs2410_read_reg_ctrl);
|
||||
|
||||
static int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val)
|
||||
int mc33xs2410_read_reg_diag(struct spi_device *spi, u8 reg, u16 *val)
|
||||
{
|
||||
return mc33xs2410_read_reg(spi, reg, val, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mc33xs2410_read_reg_diag);
|
||||
|
||||
int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val)
|
||||
{
|
||||
u16 tmp;
|
||||
int ret;
|
||||
|
|
@ -139,6 +149,7 @@ static int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val
|
|||
|
||||
return mc33xs2410_write_reg(spi, reg, tmp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mc33xs2410_modify_reg);
|
||||
|
||||
static u8 mc33xs2410_pwm_get_freq(u64 period)
|
||||
{
|
||||
|
|
@ -314,6 +325,7 @@ static int mc33xs2410_reset(struct device *dev)
|
|||
static int mc33xs2410_probe(struct spi_device *spi)
|
||||
{
|
||||
struct device *dev = &spi->dev;
|
||||
struct auxiliary_device *adev;
|
||||
struct pwm_chip *chip;
|
||||
int ret;
|
||||
|
||||
|
|
@ -361,6 +373,10 @@ static int mc33xs2410_probe(struct spi_device *spi)
|
|||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to add pwm chip\n");
|
||||
|
||||
adev = devm_auxiliary_device_create(dev, "hwmon", NULL);
|
||||
if (!adev)
|
||||
return dev_err_probe(dev, -ENODEV, "Failed to register hwmon device\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#define PWM45DWIDTH_FIXUP 0x30
|
||||
#define PWMTHRES 0x30
|
||||
#define PWM45THRES_FIXUP 0x34
|
||||
#define PWM_CK_26M_SEL_V3 0x74
|
||||
#define PWM_CK_26M_SEL 0x210
|
||||
|
||||
#define PWM_CLK_DIV_MAX 7
|
||||
|
|
@ -36,7 +37,7 @@
|
|||
struct pwm_mediatek_of_data {
|
||||
unsigned int num_pwms;
|
||||
bool pwm45_fixup;
|
||||
bool has_ck_26m_sel;
|
||||
u16 pwm_ck_26m_sel_reg;
|
||||
const unsigned int *reg_offset;
|
||||
};
|
||||
|
||||
|
|
@ -64,6 +65,11 @@ static const unsigned int mtk_pwm_reg_offset_v2[] = {
|
|||
0x0080, 0x00c0, 0x0100, 0x0140, 0x0180, 0x01c0, 0x0200, 0x0240
|
||||
};
|
||||
|
||||
/* PWM IP Version 3.0.2 */
|
||||
static const unsigned int mtk_pwm_reg_offset_v3[] = {
|
||||
0x0100, 0x0200, 0x0300, 0x0400, 0x0500, 0x0600, 0x0700, 0x0800
|
||||
};
|
||||
|
||||
static inline struct pwm_mediatek_chip *
|
||||
to_pwm_mediatek_chip(struct pwm_chip *chip)
|
||||
{
|
||||
|
|
@ -136,8 +142,8 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
}
|
||||
|
||||
/* Make sure we use the bus clock and not the 26MHz clock */
|
||||
if (pc->soc->has_ck_26m_sel)
|
||||
writel(0, pc->regs + PWM_CK_26M_SEL);
|
||||
if (pc->soc->pwm_ck_26m_sel_reg)
|
||||
writel(0, pc->regs + pc->soc->pwm_ck_26m_sel_reg);
|
||||
|
||||
/* Using resolution in picosecond gets accuracy higher */
|
||||
resolution = (u64)NSEC_PER_SEC * 1000;
|
||||
|
|
@ -294,90 +300,92 @@ static int pwm_mediatek_probe(struct platform_device *pdev)
|
|||
static const struct pwm_mediatek_of_data mt2712_pwm_data = {
|
||||
.num_pwms = 8,
|
||||
.pwm45_fixup = false,
|
||||
.has_ck_26m_sel = false,
|
||||
.reg_offset = mtk_pwm_reg_offset_v1,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt6795_pwm_data = {
|
||||
.num_pwms = 7,
|
||||
.pwm45_fixup = false,
|
||||
.has_ck_26m_sel = false,
|
||||
.reg_offset = mtk_pwm_reg_offset_v1,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt7622_pwm_data = {
|
||||
.num_pwms = 6,
|
||||
.pwm45_fixup = false,
|
||||
.has_ck_26m_sel = true,
|
||||
.pwm_ck_26m_sel_reg = PWM_CK_26M_SEL,
|
||||
.reg_offset = mtk_pwm_reg_offset_v1,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt7623_pwm_data = {
|
||||
.num_pwms = 5,
|
||||
.pwm45_fixup = true,
|
||||
.has_ck_26m_sel = false,
|
||||
.reg_offset = mtk_pwm_reg_offset_v1,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt7628_pwm_data = {
|
||||
.num_pwms = 4,
|
||||
.pwm45_fixup = true,
|
||||
.has_ck_26m_sel = false,
|
||||
.reg_offset = mtk_pwm_reg_offset_v1,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt7629_pwm_data = {
|
||||
.num_pwms = 1,
|
||||
.pwm45_fixup = false,
|
||||
.has_ck_26m_sel = false,
|
||||
.reg_offset = mtk_pwm_reg_offset_v1,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt7981_pwm_data = {
|
||||
.num_pwms = 3,
|
||||
.pwm45_fixup = false,
|
||||
.has_ck_26m_sel = true,
|
||||
.pwm_ck_26m_sel_reg = PWM_CK_26M_SEL,
|
||||
.reg_offset = mtk_pwm_reg_offset_v2,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt7986_pwm_data = {
|
||||
.num_pwms = 2,
|
||||
.pwm45_fixup = false,
|
||||
.has_ck_26m_sel = true,
|
||||
.pwm_ck_26m_sel_reg = PWM_CK_26M_SEL,
|
||||
.reg_offset = mtk_pwm_reg_offset_v1,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt7988_pwm_data = {
|
||||
.num_pwms = 8,
|
||||
.pwm45_fixup = false,
|
||||
.has_ck_26m_sel = false,
|
||||
.reg_offset = mtk_pwm_reg_offset_v2,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt8183_pwm_data = {
|
||||
.num_pwms = 4,
|
||||
.pwm45_fixup = false,
|
||||
.has_ck_26m_sel = true,
|
||||
.pwm_ck_26m_sel_reg = PWM_CK_26M_SEL,
|
||||
.reg_offset = mtk_pwm_reg_offset_v1,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt8365_pwm_data = {
|
||||
.num_pwms = 3,
|
||||
.pwm45_fixup = false,
|
||||
.has_ck_26m_sel = true,
|
||||
.pwm_ck_26m_sel_reg = PWM_CK_26M_SEL,
|
||||
.reg_offset = mtk_pwm_reg_offset_v1,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt8516_pwm_data = {
|
||||
.num_pwms = 5,
|
||||
.pwm45_fixup = false,
|
||||
.has_ck_26m_sel = true,
|
||||
.pwm_ck_26m_sel_reg = PWM_CK_26M_SEL,
|
||||
.reg_offset = mtk_pwm_reg_offset_v1,
|
||||
};
|
||||
|
||||
static const struct pwm_mediatek_of_data mt6991_pwm_data = {
|
||||
.num_pwms = 4,
|
||||
.pwm45_fixup = false,
|
||||
.pwm_ck_26m_sel_reg = PWM_CK_26M_SEL_V3,
|
||||
.reg_offset = mtk_pwm_reg_offset_v3,
|
||||
};
|
||||
|
||||
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,mt6991-pwm", .data = &mt6991_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 },
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@
|
|||
#include <linux/ktime.h>
|
||||
#include <linux/math.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pwm.h>
|
||||
|
|
@ -56,7 +55,6 @@
|
|||
struct mchp_core_pwm_chip {
|
||||
struct clk *clk;
|
||||
void __iomem *base;
|
||||
struct mutex lock; /* protects the shared period */
|
||||
ktime_t update_timestamp;
|
||||
u32 sync_update_mask;
|
||||
u16 channel_enabled;
|
||||
|
|
@ -360,17 +358,10 @@ static int mchp_core_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
const struct pwm_state *state)
|
||||
{
|
||||
struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&mchp_core_pwm->lock);
|
||||
|
||||
mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm);
|
||||
|
||||
ret = mchp_core_pwm_apply_locked(chip, pwm, state);
|
||||
|
||||
mutex_unlock(&mchp_core_pwm->lock);
|
||||
|
||||
return ret;
|
||||
return mchp_core_pwm_apply_locked(chip, pwm, state);
|
||||
}
|
||||
|
||||
static int mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
|
|
@ -381,8 +372,6 @@ static int mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm
|
|||
u16 prescale, period_steps;
|
||||
u8 duty_steps, posedge, negedge;
|
||||
|
||||
mutex_lock(&mchp_core_pwm->lock);
|
||||
|
||||
mchp_core_pwm_wait_for_sync_update(mchp_core_pwm, pwm->hwpwm);
|
||||
|
||||
if (mchp_core_pwm->channel_enabled & (1 << pwm->hwpwm))
|
||||
|
|
@ -415,8 +404,6 @@ static int mchp_core_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm
|
|||
posedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_POSEDGE(pwm->hwpwm));
|
||||
negedge = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_NEGEDGE(pwm->hwpwm));
|
||||
|
||||
mutex_unlock(&mchp_core_pwm->lock);
|
||||
|
||||
if (negedge == posedge) {
|
||||
state->duty_cycle = state->period;
|
||||
state->period *= 2;
|
||||
|
|
@ -469,8 +456,6 @@ static int mchp_core_pwm_probe(struct platform_device *pdev)
|
|||
&mchp_core_pwm->sync_update_mask))
|
||||
mchp_core_pwm->sync_update_mask = 0;
|
||||
|
||||
mutex_init(&mchp_core_pwm->lock);
|
||||
|
||||
chip->ops = &mchp_core_pwm_ops;
|
||||
|
||||
mchp_core_pwm->channel_enabled = readb_relaxed(mchp_core_pwm->base + MCHPCOREPWM_EN(0));
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include <linux/io.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include <asm/div64.h>
|
||||
|
||||
|
|
@ -161,6 +162,7 @@ static int pwm_probe(struct platform_device *pdev)
|
|||
struct pwm_chip *chip;
|
||||
struct pxa_pwm_chip *pc;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct reset_control *rst;
|
||||
int ret = 0;
|
||||
|
||||
if (IS_ENABLED(CONFIG_OF) && id == NULL)
|
||||
|
|
@ -179,6 +181,10 @@ static int pwm_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(pc->clk))
|
||||
return dev_err_probe(dev, PTR_ERR(pc->clk), "Failed to get clock\n");
|
||||
|
||||
rst = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL);
|
||||
if (IS_ERR(rst))
|
||||
return PTR_ERR(rst);
|
||||
|
||||
chip->ops = &pxa_pwm_ops;
|
||||
|
||||
if (IS_ENABLED(CONFIG_OF))
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
|
@ -61,6 +63,7 @@ static int rockchip_pwm_get_state(struct pwm_chip *chip,
|
|||
struct pwm_state *state)
|
||||
{
|
||||
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
|
||||
u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC;
|
||||
u32 enable_conf = pc->data->enable_conf;
|
||||
unsigned long clk_rate;
|
||||
u64 tmp;
|
||||
|
|
@ -78,12 +81,12 @@ static int rockchip_pwm_get_state(struct pwm_chip *chip,
|
|||
clk_rate = clk_get_rate(pc->clk);
|
||||
|
||||
tmp = readl_relaxed(pc->base + pc->data->regs.period);
|
||||
tmp *= pc->data->prescaler * NSEC_PER_SEC;
|
||||
state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
|
||||
tmp *= prescaled_ns;
|
||||
state->period = DIV_U64_ROUND_UP(tmp, clk_rate);
|
||||
|
||||
tmp = readl_relaxed(pc->base + pc->data->regs.duty);
|
||||
tmp *= pc->data->prescaler * NSEC_PER_SEC;
|
||||
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
|
||||
tmp *= prescaled_ns;
|
||||
state->duty_cycle = DIV_U64_ROUND_UP(tmp, clk_rate);
|
||||
|
||||
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
|
||||
state->enabled = (val & enable_conf) == enable_conf;
|
||||
|
|
@ -103,8 +106,9 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
const struct pwm_state *state)
|
||||
{
|
||||
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
|
||||
unsigned long period, duty;
|
||||
u64 clk_rate, div;
|
||||
u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC;
|
||||
u64 clk_rate, tmp;
|
||||
u32 period_ticks, duty_ticks;
|
||||
u32 ctrl;
|
||||
|
||||
clk_rate = clk_get_rate(pc->clk);
|
||||
|
|
@ -114,12 +118,15 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
* bits, every possible input period can be obtained using the
|
||||
* default prescaler value for all practical clock rate values.
|
||||
*/
|
||||
div = clk_rate * state->period;
|
||||
period = DIV_ROUND_CLOSEST_ULL(div,
|
||||
pc->data->prescaler * NSEC_PER_SEC);
|
||||
tmp = mul_u64_u64_div_u64(clk_rate, state->period, prescaled_ns);
|
||||
if (tmp > U32_MAX)
|
||||
tmp = U32_MAX;
|
||||
period_ticks = tmp;
|
||||
|
||||
div = clk_rate * state->duty_cycle;
|
||||
duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC);
|
||||
tmp = mul_u64_u64_div_u64(clk_rate, state->duty_cycle, prescaled_ns);
|
||||
if (tmp > U32_MAX)
|
||||
tmp = U32_MAX;
|
||||
duty_ticks = tmp;
|
||||
|
||||
/*
|
||||
* Lock the period and duty of previous configuration, then
|
||||
|
|
@ -131,8 +138,8 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
writel_relaxed(ctrl, pc->base + pc->data->regs.ctrl);
|
||||
}
|
||||
|
||||
writel(period, pc->base + pc->data->regs.period);
|
||||
writel(duty, pc->base + pc->data->regs.duty);
|
||||
writel(period_ticks, pc->base + pc->data->regs.period);
|
||||
writel(duty_ticks, pc->base + pc->data->regs.duty);
|
||||
|
||||
if (pc->data->supports_polarity) {
|
||||
ctrl &= ~PWM_POLARITY_MASK;
|
||||
|
|
|
|||
|
|
@ -4,11 +4,28 @@
|
|||
* For SiFive's PWM IP block documentation please refer Chapter 14 of
|
||||
* Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf
|
||||
*
|
||||
* PWM output inversion: According to the SiFive Reference manual
|
||||
* the output of each comparator is high whenever the value of pwms is
|
||||
* greater than or equal to the corresponding pwmcmpX[Reference Manual].
|
||||
*
|
||||
* Figure 29 in the same manual shows that the pwmcmpXcenter bit is
|
||||
* hard-tied to 0 (XNOR), which effectively inverts the comparison so that
|
||||
* the output goes HIGH when `pwms < pwmcmpX`.
|
||||
*
|
||||
* In other words, each pwmcmp register actually defines the **inactive**
|
||||
* (low) period of the pulse, not the active time exactly opposite to what
|
||||
* the documentation text implies.
|
||||
*
|
||||
* To compensate, this driver always **inverts** the duty value when reading
|
||||
* or writing pwmcmp registers , so that users interact with a conventional
|
||||
* **active-high** PWM interface.
|
||||
*
|
||||
*
|
||||
* Limitations:
|
||||
* - When changing both duty cycle and period, we cannot prevent in
|
||||
* software that the output might produce a period with mixed
|
||||
* settings (new period length and old duty cycle).
|
||||
* - The hardware cannot generate a 100% duty cycle.
|
||||
* - The hardware cannot generate a 0% duty cycle.
|
||||
* - The hardware generates only inverted output.
|
||||
*/
|
||||
#include <linux/clk.h>
|
||||
|
|
@ -101,7 +118,7 @@ static void pwm_sifive_update_clock(struct pwm_sifive_ddata *ddata,
|
|||
|
||||
/* As scale <= 15 the shift operation cannot overflow. */
|
||||
num = (unsigned long long)NSEC_PER_SEC << (PWM_SIFIVE_CMPWIDTH + scale);
|
||||
ddata->real_period = div64_ul(num, rate);
|
||||
ddata->real_period = DIV_ROUND_UP_ULL(num, rate);
|
||||
dev_dbg(ddata->parent,
|
||||
"New real_period = %u ns\n", ddata->real_period);
|
||||
}
|
||||
|
|
@ -110,9 +127,14 @@ static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
struct pwm_state *state)
|
||||
{
|
||||
struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip);
|
||||
u32 duty, val;
|
||||
u32 duty, val, inactive;
|
||||
|
||||
duty = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
|
||||
inactive = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
|
||||
/*
|
||||
* PWM hardware uses 'inactive' counts in pwmcmp, so invert to get actual duty.
|
||||
* Here, 'inactive' is the low time and we compute duty as max_count - inactive.
|
||||
*/
|
||||
duty = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - inactive;
|
||||
|
||||
state->enabled = duty > 0;
|
||||
|
||||
|
|
@ -121,9 +143,9 @@ static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
state->enabled = false;
|
||||
|
||||
state->period = ddata->real_period;
|
||||
state->duty_cycle =
|
||||
(u64)duty * ddata->real_period >> PWM_SIFIVE_CMPWIDTH;
|
||||
state->polarity = PWM_POLARITY_INVERSED;
|
||||
state->duty_cycle = DIV_ROUND_UP_ULL((u64)duty * ddata->real_period,
|
||||
(1U << PWM_SIFIVE_CMPWIDTH));
|
||||
state->polarity = PWM_POLARITY_NORMAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -137,9 +159,10 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
unsigned long long num;
|
||||
bool enabled;
|
||||
int ret = 0;
|
||||
u32 frac;
|
||||
u64 frac;
|
||||
u32 inactive;
|
||||
|
||||
if (state->polarity != PWM_POLARITY_INVERSED)
|
||||
if (state->polarity != PWM_POLARITY_NORMAL)
|
||||
return -EINVAL;
|
||||
|
||||
cur_state = pwm->state;
|
||||
|
|
@ -156,9 +179,12 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
* consecutively
|
||||
*/
|
||||
num = (u64)duty_cycle * (1U << PWM_SIFIVE_CMPWIDTH);
|
||||
frac = DIV64_U64_ROUND_CLOSEST(num, state->period);
|
||||
/* The hardware cannot generate a 100% duty cycle */
|
||||
frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1);
|
||||
frac = num;
|
||||
do_div(frac, state->period);
|
||||
/* The hardware cannot generate a 0% duty cycle */
|
||||
frac = min(frac, (u64)(1U << PWM_SIFIVE_CMPWIDTH) - 1);
|
||||
/* pwmcmp register must be loaded with the inactive(invert the duty) */
|
||||
inactive = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac;
|
||||
|
||||
mutex_lock(&ddata->lock);
|
||||
if (state->period != ddata->approx_period) {
|
||||
|
|
@ -190,7 +216,7 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
}
|
||||
}
|
||||
|
||||
writel(frac, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
|
||||
writel(inactive, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
|
||||
|
||||
if (!state->enabled)
|
||||
clk_disable(ddata->clk);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
* the running period.
|
||||
* - When PERIOD and HLPERIOD is set to 0, the PWM wave output will
|
||||
* be stopped and the output is pulled to high.
|
||||
* - SG2044 supports both polarities, SG2042 only normal polarity.
|
||||
* See the datasheet [1] for more details.
|
||||
* [1]:https://github.com/sophgo/sophgo-doc/tree/main/SG2042/TRM
|
||||
*/
|
||||
|
|
@ -41,6 +42,10 @@
|
|||
#define SG2042_PWM_HLPERIOD(chan) ((chan) * 8 + 0)
|
||||
#define SG2042_PWM_PERIOD(chan) ((chan) * 8 + 4)
|
||||
|
||||
#define SG2044_PWM_POLARITY 0x40
|
||||
#define SG2044_PWM_PWMSTART 0x44
|
||||
#define SG2044_PWM_OE 0xd0
|
||||
|
||||
#define SG2042_PWM_CHANNELNUM 4
|
||||
|
||||
/**
|
||||
|
|
@ -53,6 +58,10 @@ struct sg2042_pwm_ddata {
|
|||
unsigned long clk_rate_hz;
|
||||
};
|
||||
|
||||
struct sg2042_chip_data {
|
||||
const struct pwm_ops ops;
|
||||
};
|
||||
|
||||
/*
|
||||
* period_ticks: PERIOD
|
||||
* hlperiod_ticks: HLPERIOD
|
||||
|
|
@ -66,13 +75,31 @@ static void pwm_sg2042_config(struct sg2042_pwm_ddata *ddata, unsigned int chan,
|
|||
writel(hlperiod_ticks, base + SG2042_PWM_HLPERIOD(chan));
|
||||
}
|
||||
|
||||
static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
const struct pwm_state *state)
|
||||
static void pwm_sg2042_set_dutycycle(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
const struct pwm_state *state)
|
||||
{
|
||||
struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip);
|
||||
u32 hlperiod_ticks;
|
||||
u32 period_ticks;
|
||||
|
||||
/*
|
||||
* Duration of High level (duty_cycle) = HLPERIOD x Period_of_input_clk
|
||||
* Duration of One Cycle (period) = PERIOD x Period_of_input_clk
|
||||
*/
|
||||
period_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->period, NSEC_PER_SEC), U32_MAX);
|
||||
hlperiod_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->duty_cycle, NSEC_PER_SEC), U32_MAX);
|
||||
|
||||
dev_dbg(pwmchip_parent(chip), "chan[%u]: ENABLE=%u, PERIOD=%u, HLPERIOD=%u, POLARITY=%u\n",
|
||||
pwm->hwpwm, state->enabled, period_ticks, hlperiod_ticks, state->polarity);
|
||||
|
||||
pwm_sg2042_config(ddata, pwm->hwpwm, period_ticks, hlperiod_ticks);
|
||||
}
|
||||
|
||||
static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
const struct pwm_state *state)
|
||||
{
|
||||
struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip);
|
||||
|
||||
if (state->polarity == PWM_POLARITY_INVERSED)
|
||||
return -EINVAL;
|
||||
|
||||
|
|
@ -81,17 +108,7 @@ static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Duration of High level (duty_cycle) = HLPERIOD x Period_of_input_clk
|
||||
* Duration of One Cycle (period) = PERIOD x Period_of_input_clk
|
||||
*/
|
||||
period_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->period, NSEC_PER_SEC), U32_MAX);
|
||||
hlperiod_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->duty_cycle, NSEC_PER_SEC), U32_MAX);
|
||||
|
||||
dev_dbg(pwmchip_parent(chip), "chan[%u]: PERIOD=%u, HLPERIOD=%u\n",
|
||||
pwm->hwpwm, period_ticks, hlperiod_ticks);
|
||||
|
||||
pwm_sg2042_config(ddata, pwm->hwpwm, period_ticks, hlperiod_ticks);
|
||||
pwm_sg2042_set_dutycycle(chip, pwm, state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -123,13 +140,97 @@ static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwm_ops pwm_sg2042_ops = {
|
||||
.apply = pwm_sg2042_apply,
|
||||
.get_state = pwm_sg2042_get_state,
|
||||
static void pwm_sg2044_set_outputen(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
|
||||
bool enabled)
|
||||
{
|
||||
u32 pwmstart;
|
||||
|
||||
pwmstart = readl(ddata->base + SG2044_PWM_PWMSTART);
|
||||
|
||||
if (enabled)
|
||||
pwmstart |= BIT(pwm->hwpwm);
|
||||
else
|
||||
pwmstart &= ~BIT(pwm->hwpwm);
|
||||
|
||||
writel(pwmstart, ddata->base + SG2044_PWM_PWMSTART);
|
||||
}
|
||||
|
||||
static void pwm_sg2044_set_outputdir(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
|
||||
bool enabled)
|
||||
{
|
||||
u32 pwm_oe;
|
||||
|
||||
pwm_oe = readl(ddata->base + SG2044_PWM_OE);
|
||||
|
||||
if (enabled)
|
||||
pwm_oe |= BIT(pwm->hwpwm);
|
||||
else
|
||||
pwm_oe &= ~BIT(pwm->hwpwm);
|
||||
|
||||
writel(pwm_oe, ddata->base + SG2044_PWM_OE);
|
||||
}
|
||||
|
||||
static void pwm_sg2044_set_polarity(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
|
||||
const struct pwm_state *state)
|
||||
{
|
||||
u32 pwm_polarity;
|
||||
|
||||
pwm_polarity = readl(ddata->base + SG2044_PWM_POLARITY);
|
||||
|
||||
if (state->polarity == PWM_POLARITY_NORMAL)
|
||||
pwm_polarity &= ~BIT(pwm->hwpwm);
|
||||
else
|
||||
pwm_polarity |= BIT(pwm->hwpwm);
|
||||
|
||||
writel(pwm_polarity, ddata->base + SG2044_PWM_POLARITY);
|
||||
}
|
||||
|
||||
static int pwm_sg2044_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
const struct pwm_state *state)
|
||||
{
|
||||
struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip);
|
||||
|
||||
pwm_sg2044_set_polarity(ddata, pwm, state);
|
||||
|
||||
pwm_sg2042_set_dutycycle(chip, pwm, state);
|
||||
|
||||
/*
|
||||
* re-enable PWMSTART to refresh the register period
|
||||
*/
|
||||
pwm_sg2044_set_outputen(ddata, pwm, false);
|
||||
|
||||
if (!state->enabled)
|
||||
return 0;
|
||||
|
||||
pwm_sg2044_set_outputdir(ddata, pwm, true);
|
||||
pwm_sg2044_set_outputen(ddata, pwm, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct sg2042_chip_data sg2042_chip_data = {
|
||||
.ops = {
|
||||
.apply = pwm_sg2042_apply,
|
||||
.get_state = pwm_sg2042_get_state,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct sg2042_chip_data sg2044_chip_data = {
|
||||
.ops = {
|
||||
.apply = pwm_sg2044_apply,
|
||||
.get_state = pwm_sg2042_get_state,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct of_device_id sg2042_pwm_ids[] = {
|
||||
{ .compatible = "sophgo,sg2042-pwm" },
|
||||
{
|
||||
.compatible = "sophgo,sg2042-pwm",
|
||||
.data = &sg2042_chip_data
|
||||
},
|
||||
{
|
||||
.compatible = "sophgo,sg2044-pwm",
|
||||
.data = &sg2044_chip_data
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sg2042_pwm_ids);
|
||||
|
|
@ -137,12 +238,17 @@ MODULE_DEVICE_TABLE(of, sg2042_pwm_ids);
|
|||
static int pwm_sg2042_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
const struct sg2042_chip_data *chip_data;
|
||||
struct sg2042_pwm_ddata *ddata;
|
||||
struct reset_control *rst;
|
||||
struct pwm_chip *chip;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
chip_data = device_get_match_data(dev);
|
||||
if (!chip_data)
|
||||
return -ENODEV;
|
||||
|
||||
chip = devm_pwmchip_alloc(dev, SG2042_PWM_CHANNELNUM, sizeof(*ddata));
|
||||
if (IS_ERR(chip))
|
||||
return PTR_ERR(chip);
|
||||
|
|
@ -170,7 +276,7 @@ static int pwm_sg2042_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(rst))
|
||||
return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n");
|
||||
|
||||
chip->ops = &pwm_sg2042_ops;
|
||||
chip->ops = &chip_data->ops;
|
||||
chip->atomic = true;
|
||||
|
||||
ret = devm_pwmchip_add(dev, chip);
|
||||
|
|
@ -190,5 +296,6 @@ static struct platform_driver pwm_sg2042_driver = {
|
|||
module_platform_driver(pwm_sg2042_driver);
|
||||
|
||||
MODULE_AUTHOR("Chen Wang");
|
||||
MODULE_AUTHOR("Longbin Li <looong.bin@gmail.com>");
|
||||
MODULE_DESCRIPTION("Sophgo SG2042 PWM driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ struct sti_pwm_chip {
|
|||
struct pwm_device *cur;
|
||||
unsigned long configured;
|
||||
unsigned int en_count;
|
||||
struct mutex sti_pwm_lock; /* To sync between enable/disable calls */
|
||||
void __iomem *mmio;
|
||||
};
|
||||
|
||||
|
|
@ -244,55 +243,46 @@ static int sti_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
{
|
||||
struct sti_pwm_chip *pc = to_sti_pwmchip(chip);
|
||||
struct device *dev = pc->dev;
|
||||
int ret = 0;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Since we have a common enable for all PWM devices, do not enable if
|
||||
* already enabled.
|
||||
*/
|
||||
mutex_lock(&pc->sti_pwm_lock);
|
||||
|
||||
if (!pc->en_count) {
|
||||
ret = clk_enable(pc->pwm_clk);
|
||||
if (ret)
|
||||
goto out;
|
||||
return ret;
|
||||
|
||||
ret = clk_enable(pc->cpt_clk);
|
||||
if (ret)
|
||||
goto out;
|
||||
return ret;
|
||||
|
||||
ret = regmap_field_write(pc->pwm_out_en, 1);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable PWM device %u: %d\n",
|
||||
pwm->hwpwm, ret);
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
pc->en_count++;
|
||||
|
||||
out:
|
||||
mutex_unlock(&pc->sti_pwm_lock);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sti_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct sti_pwm_chip *pc = to_sti_pwmchip(chip);
|
||||
|
||||
mutex_lock(&pc->sti_pwm_lock);
|
||||
|
||||
if (--pc->en_count) {
|
||||
mutex_unlock(&pc->sti_pwm_lock);
|
||||
if (--pc->en_count)
|
||||
return;
|
||||
}
|
||||
|
||||
regmap_field_write(pc->pwm_out_en, 0);
|
||||
|
||||
clk_disable(pc->pwm_clk);
|
||||
clk_disable(pc->cpt_clk);
|
||||
|
||||
mutex_unlock(&pc->sti_pwm_lock);
|
||||
}
|
||||
|
||||
static void sti_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
|
|
@ -594,7 +584,6 @@ static int sti_pwm_probe(struct platform_device *pdev)
|
|||
|
||||
pc->dev = dev;
|
||||
pc->en_count = 0;
|
||||
mutex_init(&pc->sti_pwm_lock);
|
||||
|
||||
ret = sti_pwm_probe_regmap(pc);
|
||||
if (ret)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#define CCMR_CHANNEL_SHIFT 8
|
||||
#define CCMR_CHANNEL_MASK 0xFF
|
||||
#define MAX_BREAKINPUT 2
|
||||
#define STM32_MAX_PWM_OUTPUT 4
|
||||
|
||||
struct stm32_breakinput {
|
||||
u32 index;
|
||||
|
|
@ -768,10 +769,19 @@ static int stm32_pwm_probe_breakinputs(struct stm32_pwm *priv,
|
|||
return stm32_pwm_apply_breakinputs(priv);
|
||||
}
|
||||
|
||||
static void stm32_pwm_detect_complementary(struct stm32_pwm *priv)
|
||||
static void stm32_pwm_detect_complementary(struct stm32_pwm *priv, struct stm32_timers *ddata)
|
||||
{
|
||||
u32 ccer;
|
||||
|
||||
if (ddata->ipidr) {
|
||||
u32 val;
|
||||
|
||||
/* Simply read from HWCFGR the number of complementary outputs (MP25). */
|
||||
regmap_read(priv->regmap, TIM_HWCFGR1, &val);
|
||||
priv->have_complementary_output = !!FIELD_GET(TIM_HWCFGR1_NB_OF_DT, val);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If complementary bit doesn't exist writing 1 will have no
|
||||
* effect so we can detect it.
|
||||
|
|
@ -783,22 +793,39 @@ static void stm32_pwm_detect_complementary(struct stm32_pwm *priv)
|
|||
priv->have_complementary_output = (ccer != 0);
|
||||
}
|
||||
|
||||
static unsigned int stm32_pwm_detect_channels(struct regmap *regmap,
|
||||
static unsigned int stm32_pwm_detect_channels(struct stm32_timers *ddata,
|
||||
unsigned int *num_enabled)
|
||||
{
|
||||
struct regmap *regmap = ddata->regmap;
|
||||
u32 ccer, ccer_backup;
|
||||
|
||||
regmap_read(regmap, TIM_CCER, &ccer_backup);
|
||||
*num_enabled = hweight32(ccer_backup & TIM_CCER_CCXE);
|
||||
|
||||
if (ddata->ipidr) {
|
||||
u32 hwcfgr;
|
||||
unsigned int npwm;
|
||||
|
||||
/* Deduce from HWCFGR the number of outputs (MP25). */
|
||||
regmap_read(regmap, TIM_HWCFGR1, &hwcfgr);
|
||||
|
||||
/*
|
||||
* Timers may have more capture/compare channels than the
|
||||
* actual number of PWM channel outputs (e.g. TIM_CH[1..4]).
|
||||
*/
|
||||
npwm = FIELD_GET(TIM_HWCFGR1_NB_OF_CC, hwcfgr);
|
||||
|
||||
return npwm < STM32_MAX_PWM_OUTPUT ? npwm : STM32_MAX_PWM_OUTPUT;
|
||||
}
|
||||
|
||||
/*
|
||||
* If channels enable bits don't exist writing 1 will have no
|
||||
* effect so we can detect and count them.
|
||||
*/
|
||||
regmap_read(regmap, TIM_CCER, &ccer_backup);
|
||||
regmap_set_bits(regmap, TIM_CCER, TIM_CCER_CCXE);
|
||||
regmap_read(regmap, TIM_CCER, &ccer);
|
||||
regmap_write(regmap, TIM_CCER, ccer_backup);
|
||||
|
||||
*num_enabled = hweight32(ccer_backup & TIM_CCER_CCXE);
|
||||
|
||||
return hweight32(ccer & TIM_CCER_CCXE);
|
||||
}
|
||||
|
||||
|
|
@ -813,7 +840,7 @@ static int stm32_pwm_probe(struct platform_device *pdev)
|
|||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
npwm = stm32_pwm_detect_channels(ddata->regmap, &num_enabled);
|
||||
npwm = stm32_pwm_detect_channels(ddata, &num_enabled);
|
||||
|
||||
chip = devm_pwmchip_alloc(dev, npwm, sizeof(*priv));
|
||||
if (IS_ERR(chip))
|
||||
|
|
@ -834,7 +861,7 @@ static int stm32_pwm_probe(struct platform_device *pdev)
|
|||
return dev_err_probe(dev, ret,
|
||||
"Failed to configure breakinputs\n");
|
||||
|
||||
stm32_pwm_detect_complementary(priv);
|
||||
stm32_pwm_detect_complementary(priv, ddata);
|
||||
|
||||
ret = devm_clk_rate_exclusive_get(dev, priv->clk);
|
||||
if (ret)
|
||||
|
|
@ -907,6 +934,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(stm32_pwm_pm_ops, stm32_pwm_suspend, stm32_pwm_r
|
|||
|
||||
static const struct of_device_id stm32_pwm_of_match[] = {
|
||||
{ .compatible = "st,stm32-pwm", },
|
||||
{ .compatible = "st,stm32mp25-pwm", },
|
||||
{ /* end node */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, stm32_pwm_of_match);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
#include <linux/pwm.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/time.h>
|
||||
|
||||
#define PWM_CTRL_REG 0x0
|
||||
|
|
@ -85,7 +84,6 @@ struct sun4i_pwm_chip {
|
|||
struct clk *clk;
|
||||
struct reset_control *rst;
|
||||
void __iomem *base;
|
||||
spinlock_t ctrl_lock;
|
||||
const struct sun4i_pwm_data *data;
|
||||
};
|
||||
|
||||
|
|
@ -258,7 +256,6 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
return ret;
|
||||
}
|
||||
|
||||
spin_lock(&sun4ichip->ctrl_lock);
|
||||
ctrl = sun4i_pwm_readl(sun4ichip, PWM_CTRL_REG);
|
||||
|
||||
if (sun4ichip->data->has_direct_mod_clk_output) {
|
||||
|
|
@ -266,7 +263,6 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
ctrl |= BIT_CH(PWM_BYPASS, pwm->hwpwm);
|
||||
/* We can skip other parameter */
|
||||
sun4i_pwm_writel(sun4ichip, ctrl, PWM_CTRL_REG);
|
||||
spin_unlock(&sun4ichip->ctrl_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -297,8 +293,6 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
|
||||
sun4i_pwm_writel(sun4ichip, ctrl, PWM_CTRL_REG);
|
||||
|
||||
spin_unlock(&sun4ichip->ctrl_lock);
|
||||
|
||||
if (state->enabled)
|
||||
return 0;
|
||||
|
||||
|
|
@ -309,12 +303,10 @@ static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
else
|
||||
usleep_range(delay_us, delay_us * 2);
|
||||
|
||||
spin_lock(&sun4ichip->ctrl_lock);
|
||||
ctrl = sun4i_pwm_readl(sun4ichip, PWM_CTRL_REG);
|
||||
ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
|
||||
ctrl &= ~BIT_CH(PWM_EN, pwm->hwpwm);
|
||||
sun4i_pwm_writel(sun4ichip, ctrl, PWM_CTRL_REG);
|
||||
spin_unlock(&sun4ichip->ctrl_lock);
|
||||
|
||||
clk_disable_unprepare(sun4ichip->clk);
|
||||
|
||||
|
|
@ -456,8 +448,6 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
|
|||
|
||||
chip->ops = &sun4i_pwm_ops;
|
||||
|
||||
spin_lock_init(&sun4ichip->ctrl_lock);
|
||||
|
||||
ret = pwmchip_add(chip);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
|
||||
|
|
|
|||
|
|
@ -61,10 +61,6 @@
|
|||
#define TWL6040_LED_MODE_OFF 0x02
|
||||
#define TWL6040_LED_MODE_MASK 0x03
|
||||
|
||||
struct twl_pwmled_chip {
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
static inline struct twl_pwmled_chip *to_twl(struct pwm_chip *chip)
|
||||
{
|
||||
return pwmchip_get_drvdata(chip);
|
||||
|
|
@ -106,15 +102,13 @@ static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
|
||||
static int twl4030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct twl_pwmled_chip *twl = to_twl(chip);
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&twl->mutex);
|
||||
ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG);
|
||||
if (ret < 0) {
|
||||
dev_err(pwmchip_parent(chip), "%s: Failed to read LEDEN\n", pwm->label);
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
|
||||
val |= TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS);
|
||||
|
|
@ -123,23 +117,19 @@ static int twl4030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
if (ret < 0)
|
||||
dev_err(pwmchip_parent(chip), "%s: Failed to enable PWM\n", pwm->label);
|
||||
|
||||
out:
|
||||
mutex_unlock(&twl->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void twl4030_pwmled_disable(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm)
|
||||
{
|
||||
struct twl_pwmled_chip *twl = to_twl(chip);
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&twl->mutex);
|
||||
ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG);
|
||||
if (ret < 0) {
|
||||
dev_err(pwmchip_parent(chip), "%s: Failed to read LEDEN\n", pwm->label);
|
||||
goto out;
|
||||
return;
|
||||
}
|
||||
|
||||
val &= ~TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS);
|
||||
|
|
@ -147,9 +137,6 @@ static void twl4030_pwmled_disable(struct pwm_chip *chip,
|
|||
ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG);
|
||||
if (ret < 0)
|
||||
dev_err(pwmchip_parent(chip), "%s: Failed to disable PWM\n", pwm->label);
|
||||
|
||||
out:
|
||||
mutex_unlock(&twl->mutex);
|
||||
}
|
||||
|
||||
static int twl4030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
|
|
@ -209,16 +196,14 @@ static int twl6030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
|
||||
static int twl6030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct twl_pwmled_chip *twl = to_twl(chip);
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&twl->mutex);
|
||||
ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
|
||||
if (ret < 0) {
|
||||
dev_err(pwmchip_parent(chip), "%s: Failed to read PWM_CTRL2\n",
|
||||
pwm->label);
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
|
||||
val &= ~TWL6040_LED_MODE_MASK;
|
||||
|
|
@ -228,24 +213,20 @@ static int twl6030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
if (ret < 0)
|
||||
dev_err(pwmchip_parent(chip), "%s: Failed to enable PWM\n", pwm->label);
|
||||
|
||||
out:
|
||||
mutex_unlock(&twl->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void twl6030_pwmled_disable(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm)
|
||||
{
|
||||
struct twl_pwmled_chip *twl = to_twl(chip);
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&twl->mutex);
|
||||
ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
|
||||
if (ret < 0) {
|
||||
dev_err(pwmchip_parent(chip), "%s: Failed to read PWM_CTRL2\n",
|
||||
pwm->label);
|
||||
goto out;
|
||||
return;
|
||||
}
|
||||
|
||||
val &= ~TWL6040_LED_MODE_MASK;
|
||||
|
|
@ -254,9 +235,6 @@ static void twl6030_pwmled_disable(struct pwm_chip *chip,
|
|||
ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
|
||||
if (ret < 0)
|
||||
dev_err(pwmchip_parent(chip), "%s: Failed to disable PWM\n", pwm->label);
|
||||
|
||||
out:
|
||||
mutex_unlock(&twl->mutex);
|
||||
}
|
||||
|
||||
static int twl6030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
|
|
@ -287,16 +265,14 @@ static int twl6030_pwmled_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
|
||||
static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct twl_pwmled_chip *twl = to_twl(chip);
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&twl->mutex);
|
||||
ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
|
||||
if (ret < 0) {
|
||||
dev_err(pwmchip_parent(chip), "%s: Failed to read PWM_CTRL2\n",
|
||||
pwm->label);
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
|
||||
val &= ~TWL6040_LED_MODE_MASK;
|
||||
|
|
@ -306,23 +282,19 @@ static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
if (ret < 0)
|
||||
dev_err(pwmchip_parent(chip), "%s: Failed to request PWM\n", pwm->label);
|
||||
|
||||
out:
|
||||
mutex_unlock(&twl->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct twl_pwmled_chip *twl = to_twl(chip);
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&twl->mutex);
|
||||
ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2);
|
||||
if (ret < 0) {
|
||||
dev_err(pwmchip_parent(chip), "%s: Failed to read PWM_CTRL2\n",
|
||||
pwm->label);
|
||||
goto out;
|
||||
return;
|
||||
}
|
||||
|
||||
val &= ~TWL6040_LED_MODE_MASK;
|
||||
|
|
@ -331,9 +303,6 @@ static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||
ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2);
|
||||
if (ret < 0)
|
||||
dev_err(pwmchip_parent(chip), "%s: Failed to free PWM\n", pwm->label);
|
||||
|
||||
out:
|
||||
mutex_unlock(&twl->mutex);
|
||||
}
|
||||
|
||||
static const struct pwm_ops twl6030_pwmled_ops = {
|
||||
|
|
@ -345,7 +314,6 @@ static const struct pwm_ops twl6030_pwmled_ops = {
|
|||
static int twl_pwmled_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pwm_chip *chip;
|
||||
struct twl_pwmled_chip *twl;
|
||||
unsigned int npwm;
|
||||
const struct pwm_ops *ops;
|
||||
|
||||
|
|
@ -357,15 +325,12 @@ static int twl_pwmled_probe(struct platform_device *pdev)
|
|||
npwm = 1;
|
||||
}
|
||||
|
||||
chip = devm_pwmchip_alloc(&pdev->dev, npwm, sizeof(*twl));
|
||||
chip = devm_pwmchip_alloc(&pdev->dev, npwm, 0);
|
||||
if (IS_ERR(chip))
|
||||
return PTR_ERR(chip);
|
||||
twl = to_twl(chip);
|
||||
|
||||
chip->ops = ops;
|
||||
|
||||
mutex_init(&twl->mutex);
|
||||
|
||||
return devm_pwmchip_add(&pdev->dev, chip);
|
||||
}
|
||||
|
||||
|
|
|
|||
16
include/linux/mc33xs2410.h
Normal file
16
include/linux/mc33xs2410.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2024 Liebherr-Electronics and Drives GmbH
|
||||
*/
|
||||
#ifndef _MC33XS2410_H
|
||||
#define _MC33XS2410_H
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
MODULE_IMPORT_NS("PWM_MC33XS2410");
|
||||
|
||||
int mc33xs2410_read_reg_ctrl(struct spi_device *spi, u8 reg, u16 *val);
|
||||
int mc33xs2410_read_reg_diag(struct spi_device *spi, u8 reg, u16 *val);
|
||||
int mc33xs2410_modify_reg(struct spi_device *spi, u8 reg, u8 mask, u8 val);
|
||||
|
||||
#endif /* _MC33XS2410_H */
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#ifndef __LINUX_PWM_H
|
||||
#define __LINUX_PWM_H
|
||||
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
|
|
@ -273,6 +274,8 @@ struct pwm_capture {
|
|||
unsigned int duty_cycle;
|
||||
};
|
||||
|
||||
#define PWM_WFHWSIZE 20
|
||||
|
||||
/**
|
||||
* struct pwm_ops - PWM controller operations
|
||||
* @request: optional hook for requesting a PWM
|
||||
|
|
@ -311,6 +314,7 @@ struct pwm_ops {
|
|||
/**
|
||||
* struct pwm_chip - abstract a PWM controller
|
||||
* @dev: device providing the PWMs
|
||||
* @cdev: &struct cdev for this device
|
||||
* @ops: callbacks for this PWM controller
|
||||
* @owner: module providing this chip
|
||||
* @id: unique number of this PWM chip
|
||||
|
|
@ -325,6 +329,7 @@ struct pwm_ops {
|
|||
*/
|
||||
struct pwm_chip {
|
||||
struct device dev;
|
||||
struct cdev cdev;
|
||||
const struct pwm_ops *ops;
|
||||
struct module *owner;
|
||||
unsigned int id;
|
||||
|
|
|
|||
53
include/uapi/linux/pwm.h
Normal file
53
include/uapi/linux/pwm.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
|
||||
|
||||
#ifndef _UAPI_PWM_H_
|
||||
#define _UAPI_PWM_H_
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/**
|
||||
* struct pwmchip_waveform - Describe a PWM waveform for a pwm_chip's PWM channel
|
||||
* @hwpwm: per-chip relative index of the PWM device
|
||||
* @__pad: padding, must be zero
|
||||
* @period_length_ns: duration of the repeating period.
|
||||
* A value of 0 represents a disabled PWM.
|
||||
* @duty_length_ns: duration of the active part in each period
|
||||
* @duty_offset_ns: offset of the rising edge from a period's start
|
||||
*/
|
||||
struct pwmchip_waveform {
|
||||
__u32 hwpwm;
|
||||
__u32 __pad;
|
||||
__u64 period_length_ns;
|
||||
__u64 duty_length_ns;
|
||||
__u64 duty_offset_ns;
|
||||
};
|
||||
|
||||
/* Reserves the passed hwpwm for exclusive control. */
|
||||
#define PWM_IOCTL_REQUEST _IO(0x75, 1)
|
||||
|
||||
/* counter part to PWM_IOCTL_REQUEST */
|
||||
#define PWM_IOCTL_FREE _IO(0x75, 2)
|
||||
|
||||
/*
|
||||
* Modifies the passed wf according to hardware constraints. All parameters are
|
||||
* rounded down to the next possible value, unless there is no such value, then
|
||||
* values are rounded up. Note that zero isn't considered for rounding down
|
||||
* period_length_ns.
|
||||
*/
|
||||
#define PWM_IOCTL_ROUNDWF _IOWR(0x75, 3, struct pwmchip_waveform)
|
||||
|
||||
/* Get the currently implemented waveform */
|
||||
#define PWM_IOCTL_GETWF _IOWR(0x75, 4, struct pwmchip_waveform)
|
||||
|
||||
/* Like PWM_IOCTL_ROUNDWF + PWM_IOCTL_SETEXACTWF in one go. */
|
||||
#define PWM_IOCTL_SETROUNDEDWF _IOW(0x75, 5, struct pwmchip_waveform)
|
||||
|
||||
/*
|
||||
* Program the PWM to emit exactly the passed waveform, subject only to rounding
|
||||
* down each value less than 1 ns. Returns 0 on success, -EDOM if the waveform
|
||||
* cannot be implemented exactly, or other negative error codes.
|
||||
*/
|
||||
#define PWM_IOCTL_SETEXACTWF _IOW(0x75, 6, struct pwmchip_waveform)
|
||||
|
||||
#endif /* _UAPI_PWM_H_ */
|
||||
Loading…
Reference in New Issue
Block a user