diff mbox series

[4/6] pwm: adp5585: add adp5585 PWM support

Message ID 20240716-adi-v1-4-79c0122986e7@nxp.com
State New
Headers show
Series mfd: add adp5585 gpio and pwm support | expand

Commit Message

Frank Li July 16, 2024, 7:28 p.m. UTC
From: Clark Wang <xiaoning.wang@nxp.com>

Add PWM function support for MFD adp5585.

Reviewed-by: Haibo Chen <haibo.chen@nxp.com>
Signed-off-by: Clark Wang <xiaoning.wang@nxp.com>
Signed-off-by: Jindong Yue <jindong.yue@nxp.com>
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
 drivers/pwm/Kconfig       |   8 ++
 drivers/pwm/Makefile      |   1 +
 drivers/pwm/pwm-adp5585.c | 215 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 224 insertions(+)

Comments

Fabio Estevam July 17, 2024, 3:35 p.m. UTC | #1
On Wed, Jul 17, 2024 at 11:29 AM Frank Li <Frank.li@nxp.com> wrote:

> Thank you for you review. I just found someone already submit similar patch
>
> https://lore.kernel.org/linux-gpio/20240608141633.2562-5-laurent.pinchart@ideasonboard.com/
>
> Let's wait for laurent. If he is busy, I can rework base on the above one.

Adding Laurent.
Laurent Pinchart July 19, 2024, 1:50 p.m. UTC | #2
On Wed, Jul 17, 2024 at 12:35:29PM -0300, Fabio Estevam wrote:
> On Wed, Jul 17, 2024 at 11:29 AM Frank Li <Frank.li@nxp.com> wrote:
> 
> > Thank you for you review. I just found someone already submit similar patch
> >
> > https://lore.kernel.org/linux-gpio/20240608141633.2562-5-laurent.pinchart@ideasonboard.com/
> >
> > Let's wait for laurent. If he is busy, I can rework base on the above one.
> 
> Adding Laurent.

Thanks Fabio.

I had a quick look at this series, and I think mine is better as it
supports more features and more chip variants.
diff mbox series

Patch

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 3e53838990f5b..baaadf877b9c6 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -38,6 +38,14 @@  config PWM_DEBUG
 	  It is expected to introduce some runtime overhead and diagnostic
 	  output to the kernel log, so only enable while working on a driver.
 
+config PWM_ADP5585
+	tristate "ADP5585 PWM support"
+	depends on MFD_ADP5585
+	help
+	  This option enables support for on-chip PWM found
+	  on Analog Devices ADP5585.
+
+
 config PWM_AB8500
 	tristate "AB8500 PWM support"
 	depends on AB8500_CORE && ARCH_U8500
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 0be4f3e6dd432..161131a261e94 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -2,6 +2,7 @@ 
 obj-$(CONFIG_PWM)		+= core.o
 obj-$(CONFIG_PWM_AB8500)	+= pwm-ab8500.o
 obj-$(CONFIG_PWM_APPLE)		+= pwm-apple.o
+obj-$(CONFIG_PWM_ADP5585)	+= pwm-adp5585.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
diff --git a/drivers/pwm/pwm-adp5585.c b/drivers/pwm/pwm-adp5585.c
new file mode 100644
index 0000000000000..f578d24df5c74
--- /dev/null
+++ b/drivers/pwm/pwm-adp5585.c
@@ -0,0 +1,215 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PWM driver for Analog Devices ADP5585 MFD
+ *
+ * Copyright 2024 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/mfd/adp5585.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+
+#define ADP5585_PWM_CHAN_NUM		1
+#define ADP5585_PWM_FASTEST_PERIOD_NS	2000
+#define ADP5585_PWM_SLOWEST_PERIOD_NS	131070000
+
+struct adp5585_pwm_chip {
+	struct device *parent;
+	struct mutex lock;
+	u8 pin_config_val;
+};
+
+static inline struct adp5585_pwm_chip *
+to_adp5585_pwm_chip(struct pwm_chip *chip)
+{
+	return pwmchip_get_drvdata(chip);
+}
+
+static int adp5585_pwm_reg_read(struct adp5585_pwm_chip *adp5585_pwm, u8 reg, u8 *val)
+{
+	struct adp5585_dev *adp5585  = dev_get_drvdata(adp5585_pwm->parent);
+
+	return adp5585->read_reg(adp5585, reg, val);
+}
+
+static int adp5585_pwm_reg_write(struct adp5585_pwm_chip *adp5585_pwm, u8 reg, u8 val)
+{
+	struct adp5585_dev *adp5585  = dev_get_drvdata(adp5585_pwm->parent);
+
+	return adp5585->write_reg(adp5585, reg, val);
+}
+
+static int pwm_adp5585_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+				 struct pwm_state *state)
+{
+	struct adp5585_pwm_chip *adp5585_pwm = to_adp5585_pwm_chip(chip);
+	u32 on, off;
+	u8 temp;
+
+	/* get period */
+	adp5585_pwm_reg_read(adp5585_pwm, ADP5585_PWM_OFFT_LOW, &temp);
+	off = temp;
+	adp5585_pwm_reg_read(adp5585_pwm, ADP5585_PWM_OFFT_HIGH, &temp);
+	off |= temp << 8;
+	adp5585_pwm_reg_read(adp5585_pwm, ADP5585_PWM_ONT_LOW, &temp);
+	on = temp;
+	adp5585_pwm_reg_read(adp5585_pwm, ADP5585_PWM_ONT_HIGH, &temp);
+	on |= temp << 8;
+	state->period = (on + off) * NSEC_PER_USEC;
+
+	state->duty_cycle = on;
+	state->polarity = PWM_POLARITY_NORMAL;
+
+	/* get channel status */
+	adp5585_pwm_reg_read(adp5585_pwm, ADP5585_PWM_CFG, &temp);
+	state->enabled = temp & ADP5585_PWM_CFG_EN;
+
+	return 0;
+}
+
+static int pwm_adp5585_apply(struct pwm_chip *chip,
+			     struct pwm_device *pwm,
+			     const struct pwm_state *state)
+{
+	struct adp5585_pwm_chip *adp5585_pwm = to_adp5585_pwm_chip(chip);
+	u32 on, off;
+	u8 enabled;
+	int ret;
+
+	if (state->period > ADP5585_PWM_SLOWEST_PERIOD_NS ||
+	    state->period < ADP5585_PWM_FASTEST_PERIOD_NS)
+		return -EINVAL;
+
+	guard(mutex)(&adp5585_pwm->lock);
+
+	/* set on/off cycle*/
+	on = DIV_ROUND_CLOSEST_ULL(state->duty_cycle, NSEC_PER_USEC);
+	off = DIV_ROUND_CLOSEST_ULL((state->period - state->duty_cycle), NSEC_PER_USEC);
+
+	ret = adp5585_pwm_reg_write(adp5585_pwm, ADP5585_PWM_OFFT_LOW, off & ADP5585_REG_MASK);
+	if (ret)
+		return ret;
+
+	ret = adp5585_pwm_reg_write(adp5585_pwm, ADP5585_PWM_OFFT_HIGH,
+				    (off >> 8) & ADP5585_REG_MASK);
+	if (ret)
+		return ret;
+
+	ret = adp5585_pwm_reg_write(adp5585_pwm, ADP5585_PWM_ONT_LOW, on & ADP5585_REG_MASK);
+	if (ret)
+		return ret;
+
+	ret = adp5585_pwm_reg_write(adp5585_pwm, ADP5585_PWM_ONT_HIGH,
+				    (on >> 8) & ADP5585_REG_MASK);
+	if (ret)
+		return ret;
+
+	/* enable PWM and set to continuous PWM mode*/
+	adp5585_pwm_reg_read(adp5585_pwm, ADP5585_PWM_CFG, &enabled);
+	if (state->enabled)
+		ret = adp5585_pwm_reg_write(adp5585_pwm, ADP5585_PWM_CFG, ADP5585_PWM_CFG_EN);
+	else
+		ret = adp5585_pwm_reg_write(adp5585_pwm, ADP5585_PWM_CFG, 0);
+
+	return ret;
+}
+
+static int pwm_adp5585_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct adp5585_pwm_chip *adp5585_pwm = to_adp5585_pwm_chip(chip);
+	u8 reg_cfg;
+	int ret;
+
+	guard(mutex)(&adp5585_pwm->lock);
+
+	adp5585_pwm_reg_read(adp5585_pwm, ADP5585_PIN_CONFIG_C, &adp5585_pwm->pin_config_val);
+	reg_cfg = adp5585_pwm->pin_config_val & ~ADP5585_PIN_CONFIG_R3_MASK;
+	reg_cfg |= ADP5585_PIN_CONFIG_R3_PWM;
+	ret = adp5585_pwm_reg_write(adp5585_pwm, ADP5585_PIN_CONFIG_C, reg_cfg);
+
+	adp5585_pwm_reg_read(adp5585_pwm, ADP5585_GENERAL_CFG, &adp5585_pwm->pin_config_val);
+	reg_cfg |= ADP5585_GENERAL_CFG_OSC_EN;
+	ret = adp5585_pwm_reg_write(adp5585_pwm, ADP5585_GENERAL_CFG, reg_cfg);
+
+	return ret;
+}
+
+static void pwm_adp5585_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct adp5585_pwm_chip *adp5585_pwm = to_adp5585_pwm_chip(chip);
+	u8 reg_cfg;
+
+	guard(mutex)(&adp5585_pwm->lock);
+
+	adp5585_pwm_reg_read(adp5585_pwm, ADP5585_PIN_CONFIG_C, &reg_cfg);
+	reg_cfg &= ~ADP5585_PIN_CONFIG_R3_MASK;
+	reg_cfg |= adp5585_pwm->pin_config_val & ADP5585_PIN_CONFIG_R3_MASK;
+	adp5585_pwm_reg_write(adp5585_pwm, ADP5585_PIN_CONFIG_C, reg_cfg);
+}
+
+static const struct pwm_ops adp5585_pwm_ops = {
+	.request = pwm_adp5585_request,
+	.free = pwm_adp5585_free,
+	.get_state = pwm_adp5585_get_state,
+	.apply = pwm_adp5585_apply,
+};
+
+static int adp5585_pwm_probe(struct platform_device *pdev)
+{
+	struct adp5585_pwm_chip *adp5585_pwm;
+	struct pwm_chip *chip;
+	unsigned int npwm = ADP5585_PWM_CHAN_NUM;
+	int ret;
+
+	chip = devm_pwmchip_alloc(&pdev->dev, npwm, sizeof(*adp5585_pwm));
+	if (IS_ERR(chip))
+		return PTR_ERR(chip);
+
+	adp5585_pwm = to_adp5585_pwm_chip(chip);
+	adp5585_pwm->parent = pdev->dev.parent;
+
+	platform_set_drvdata(pdev, adp5585_pwm);
+
+	chip->ops = &adp5585_pwm_ops;
+	mutex_init(&adp5585_pwm->lock);
+
+	ret = devm_pwmchip_add(&pdev->dev, chip);
+	if (ret)
+		dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
+
+	return ret;
+}
+
+static void adp5585_pwm_remove(struct platform_device *pdev)
+{
+	struct pwm_chip *chip = platform_get_drvdata(pdev);
+
+	pwmchip_remove(chip);
+}
+
+static const struct of_device_id adp5585_pwm_of_match[] = {
+	{.compatible = "adp5585-pwm", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, adp5585_pwm_of_match);
+
+static struct platform_driver adp5585_pwm_driver = {
+	.driver	= {
+		.name	= "adp5585-pwm",
+		.of_match_table = adp5585_pwm_of_match,
+	},
+	.probe		= adp5585_pwm_probe,
+	.remove		= adp5585_pwm_remove,
+};
+module_platform_driver(adp5585_pwm_driver);
+
+MODULE_AUTHOR("Xiaoning Wang <xiaoning.wang@nxp.com>");
+MODULE_DESCRIPTION("ADP5585 PWM Driver");
+MODULE_LICENSE("GPL");