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:
Linus Torvalds 2025-07-28 23:17:46 -07:00
commit f38b751290
41 changed files with 1228 additions and 307 deletions

View File

@ -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#

View 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>;
};
};

View File

@ -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>;
};

View File

@ -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>;
};

View File

@ -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

View File

@ -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

View File

@ -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>;
};

View 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>;
};

View File

@ -17,7 +17,9 @@ allOf:
properties:
compatible:
const: sophgo,sg2042-pwm
enum:
- sophgo,sg2042-pwm
- sophgo,sg2044-pwm
reg:
maxItems: 1

View File

@ -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,.*":

View File

@ -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
-------

View File

@ -167,6 +167,7 @@ Hardware Monitoring Kernel Drivers
max77705
max8688
mc13783-adc
mc33xs2410_hwmon
mc34vr500
mcp3021
menf21bmc

View 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
======================= ======================================================

View File

@ -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";

View File

@ -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>;
};
};

View File

@ -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

View File

@ -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

View 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, &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),
&reg_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, &reg_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");

View File

@ -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)

View File

@ -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

View File

@ -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;
}

View 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");

View File

@ -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))

View File

@ -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);
}

View File

@ -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);

View File

@ -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));
}

View File

@ -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);

View File

@ -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, &reg, 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;
}

View File

@ -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 },

View File

@ -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));

View File

@ -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))

View File

@ -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;

View File

@ -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);

View File

@ -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");

View File

@ -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)

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View 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 */

View File

@ -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
View 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_ */