pwm: Provide pwmchip_alloc() function and a devm variant of it
authorUwe Kleine-König <u.kleine-koenig@pengutronix.de>
Wed, 14 Feb 2024 09:30:50 +0000 (10:30 +0100)
committerUwe Kleine-König <u.kleine-koenig@pengutronix.de>
Thu, 15 Feb 2024 11:59:15 +0000 (12:59 +0100)
This function allocates a struct pwm_chip and driver data. Compared to
the status quo the split into pwm_chip and driver data is new, otherwise
it doesn't change anything relevant (yet).

The intention is that after all drivers are switched to use this
allocation function, its possible to add a struct device to struct
pwm_chip to properly track the latter's lifetime without touching all
drivers again. Proper lifetime tracking is a necessary precondition to
introduce character device support for PWMs (that implements atomic
setting and doesn't suffer from the sysfs overhead of the /sys/class/pwm
userspace support).

The new function pwmchip_priv() (obviously?) only works for chips
allocated with pwmchip_alloc().

Link: https://lore.kernel.org/r/9577d6053a5a52536057dc8654ff567181c2da82.1707900770.git.u.kleine-koenig@pengutronix.de
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Documentation/driver-api/driver-model/devres.rst
Documentation/driver-api/pwm.rst
drivers/pwm/core.c
include/linux/pwm.h

index c5f99d834ec56a5e9fff9d10b1bcd8c98c342525..e4df72c408d274eee6bea5da6e1e81d220dcfba8 100644 (file)
@@ -420,6 +420,7 @@ POWER
   devm_reboot_mode_unregister()
 
 PWM
+  devm_pwmchip_alloc()
   devm_pwmchip_add()
   devm_pwm_get()
   devm_fwnode_pwm_get()
index 3c28ccc4b61133bffe1c04f937ff63b25d7c0750..b41b1c56477f3d833b2e9189e0b88b3f3f38f263 100644 (file)
@@ -143,11 +143,12 @@ to implement the pwm_*() functions itself. This means that it's impossible
 to have multiple PWM drivers in the system. For this reason it's mandatory
 for new drivers to use the generic PWM framework.
 
-A new PWM controller/chip can be added using pwmchip_add() and removed
-again with pwmchip_remove(). pwmchip_add() takes a filled in struct
-pwm_chip as argument which provides a description of the PWM chip, the
-number of PWM devices provided by the chip and the chip-specific
-implementation of the supported PWM operations to the framework.
+A new PWM controller/chip can be allocated using pwmchip_alloc(), then
+registered using pwmchip_add() and removed again with pwmchip_remove(). To undo
+pwmchip_alloc() use pwmchip_put(). pwmchip_add() takes a filled in struct
+pwm_chip as argument which provides a description of the PWM chip, the number
+of PWM devices provided by the chip and the chip-specific implementation of the
+supported PWM operations to the framework.
 
 When implementing polarity support in a PWM driver, make sure to respect the
 signal conventions in the PWM framework. By definition, normal polarity
index 830a697826af52653259ace0df281ebe7a1adf5f..d70f793ce4b38dd7bfb20c0d44392b018ab784ea 100644 (file)
@@ -454,6 +454,64 @@ of_pwm_single_xlate(struct pwm_chip *chip, const struct of_phandle_args *args)
 }
 EXPORT_SYMBOL_GPL(of_pwm_single_xlate);
 
+#define PWMCHIP_ALIGN ARCH_DMA_MINALIGN
+
+static void *pwmchip_priv(struct pwm_chip *chip)
+{
+       return (void *)chip + ALIGN(sizeof(*chip), PWMCHIP_ALIGN);
+}
+
+/* This is the counterpart to pwmchip_alloc() */
+void pwmchip_put(struct pwm_chip *chip)
+{
+       kfree(chip);
+}
+EXPORT_SYMBOL_GPL(pwmchip_put);
+
+struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv)
+{
+       struct pwm_chip *chip;
+       size_t alloc_size;
+
+       alloc_size = size_add(ALIGN(sizeof(*chip), PWMCHIP_ALIGN), sizeof_priv);
+
+       chip = kzalloc(alloc_size, GFP_KERNEL);
+       if (!chip)
+               return ERR_PTR(-ENOMEM);
+
+       chip->dev = parent;
+       chip->npwm = npwm;
+
+       pwmchip_set_drvdata(chip, pwmchip_priv(chip));
+
+       return chip;
+}
+EXPORT_SYMBOL_GPL(pwmchip_alloc);
+
+static void devm_pwmchip_put(void *data)
+{
+       struct pwm_chip *chip = data;
+
+       pwmchip_put(chip);
+}
+
+struct pwm_chip *devm_pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv)
+{
+       struct pwm_chip *chip;
+       int ret;
+
+       chip = pwmchip_alloc(parent, npwm, sizeof_priv);
+       if (IS_ERR(chip))
+               return chip;
+
+       ret = devm_add_action_or_reset(parent, devm_pwmchip_put, chip);
+       if (ret)
+               return ERR_PTR(ret);
+
+       return chip;
+}
+EXPORT_SYMBOL_GPL(devm_pwmchip_alloc);
+
 static void of_pwmchip_add(struct pwm_chip *chip)
 {
        if (!pwmchip_parent(chip) || !pwmchip_parent(chip)->of_node)
index 29a7d9140f77ab0dc31c476ffdf403afdb203fdc..4a6568dfdf3fa606f7f9c682067b116fc594e2a7 100644 (file)
@@ -403,6 +403,10 @@ static inline bool pwm_might_sleep(struct pwm_device *pwm)
 int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
                unsigned long timeout);
 
+void pwmchip_put(struct pwm_chip *chip);
+struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv);
+struct pwm_chip *devm_pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv);
+
 int __pwmchip_add(struct pwm_chip *chip, struct module *owner);
 #define pwmchip_add(chip) __pwmchip_add(chip, THIS_MODULE)
 void pwmchip_remove(struct pwm_chip *chip);
@@ -475,6 +479,24 @@ static inline int pwm_capture(struct pwm_device *pwm,
        return -EINVAL;
 }
 
+static inline void pwmchip_put(struct pwm_chip *chip)
+{
+}
+
+static inline struct pwm_chip *pwmchip_alloc(struct device *parent,
+                                            unsigned int npwm,
+                                            size_t sizeof_priv)
+{
+       return ERR_PTR(-EINVAL);
+}
+
+static inline struct pwm_chip *devm_pwmchip_alloc(struct device *parent,
+                                                 unsigned int npwm,
+                                                 size_t sizeof_priv)
+{
+       return pwmchip_alloc(parent, npwm, sizeof_priv);
+}
+
 static inline int pwmchip_add(struct pwm_chip *chip)
 {
        return -EINVAL;