From e6ad48dd507d3d2e19a53fee40d068cc75cc2282 Mon Sep 17 00:00:00 2001 From: Davide Cardillo <davide.cardillo@seco.com> Date: Tue, 4 Jan 2022 21:47:47 +0100 Subject: [PATCH] [PWM INTERFACE][drivers/seco/pwm_generic.c] Add driver v1.0 for pwm generic interface This driver coming from SECO driver library: - driver: seco_pwm_generic - version: v1.0 This driver provides a SYSFS interface for a PWM controller. --- drivers/seco/Kconfig | 11 + drivers/seco/Makefile | 2 + drivers/seco/pwm_generic.c | 500 ++++++++++++++++++++++++++++++++++++ include/linux/pwm_generic.h | 23 ++ 4 files changed, 536 insertions(+) create mode 100644 drivers/seco/pwm_generic.c create mode 100644 include/linux/pwm_generic.h diff --git a/drivers/seco/Kconfig b/drivers/seco/Kconfig index 271fa17db0569c..4ab2f280829d2c 100644 --- a/drivers/seco/Kconfig +++ b/drivers/seco/Kconfig @@ -30,4 +30,15 @@ config SECO_PWR_BTN if usure, say Y. + +config SECO_PWM_GEN + bool "Generic PWM Support" + depends on PWM + help + This option enables support for a generic PWM. Useful for buzzer since + it provides a one shot function with programmable time. + You can change pwm parameters through sysfs. + + If unsure, say Y. + endmenu diff --git a/drivers/seco/Makefile b/drivers/seco/Makefile index 47635183f39083..71cbde1020ae85 100644 --- a/drivers/seco/Makefile +++ b/drivers/seco/Makefile @@ -7,4 +7,6 @@ obj-$(CONFIG_ECTRL_MSP430) += ectrl_msp430.o obj-$(CONFIG_SECO_CPLD_FW) += cpld.o cgpio_expander.o lpc_bridge.o sio_w83627.o sio_w83627_gpio.o sio_w83627_serial.o sio_xr28v382.o sio_xr28v382_serial.o pwm_cpld.o obj-$(CONFIG_SECO_PWR_BTN) += pwrbutton_management.o +obj-$(CONFIG_SECO_PWM_GEN) += pwm_generic.o + diff --git a/drivers/seco/pwm_generic.c b/drivers/seco/pwm_generic.c new file mode 100644 index 00000000000000..319e6b49ab72d9 --- /dev/null +++ b/drivers/seco/pwm_generic.c @@ -0,0 +1,500 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/pwm.h> +#include <linux/pwm_generic.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + + +#define PWM_NAME_ID "pwm_generic" + +#define PWMG_INFO(fmt, arg...) printk(KERN_INFO "PWM driver: " fmt "\n" , ## arg) +#define PWMG_ERR(fmt, arg...) dev_err(&pdev->dev, "%s: " fmt "\n" , __func__ , ## arg) +#define PWMG_DBG(fmt, arg...) pr_debug("%s: " fmt "\n" , __func__ , ## arg) + +/* One Shot Buzzer Work */ +#define PWMG_WORK_POLLING_JIFFIES msecs_to_jiffies(1) // 1 milliseconds +#define DIV_TIME 1 + +struct pwmg_data { + struct pwm_device *pwm; + struct device *dev; + int id; + unsigned int period; + unsigned int period_min; + unsigned int period_max; + unsigned int lth_duty; + unsigned long duty; + unsigned long max_duty; + int enable; + struct mutex ops_lock; + unsigned long oneshot; + struct delayed_work pwmg_oneshot_work; + struct workqueue_struct *pwmg_workq; +}; + + +static int pwmg_update_status (struct pwmg_data *pd) { + + int duty = pd->duty; + int max = pd->max_duty; + + if (duty == 0 || pd->enable == 0) { + pwm_config (pd->pwm, 0, pd->period); + pwm_disable (pd->pwm); + } else if (pd->enable == 1) { + duty = pd->lth_duty + + (duty * (pd->period - pd->lth_duty) / max); + pwm_config (pd->pwm, duty, pd->period); + pwm_enable (pd->pwm); + } + return 0; +} + +/* + * pwmg_oneshot_work_handler controls wm->oneshot value and it dectivates + * the buzzer when jiffies time becomes less than a given value written through + * sysfs in the parameter oneshot. +*/ + +static void pwmg_oneshot_work_handler (struct work_struct *work) { + + struct pwmg_data *wm = container_of(work, struct pwmg_data, pwmg_oneshot_work.work); + if(wm->oneshot >= 1) { + wm->oneshot = wm->oneshot - 1; + queue_delayed_work(wm->pwmg_workq, &wm->pwmg_oneshot_work, PWMG_WORK_POLLING_JIFFIES); + } + else { + wm->enable = 0; + pwmg_update_status(wm); + } + return; +} + +static ssize_t pwmg_show_enable (struct device *dev, + struct device_attribute *attr, char *buf) { + + struct pwmg_data *pd = (struct pwmg_data *)dev_get_drvdata(dev); + + if (!pd) return 0; + return sprintf(buf, "%d\n", pd->enable); +} + + +static ssize_t pwmg_store_enable (struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) { + + int rc; + unsigned long int enable; + struct pwmg_data *pd = (struct pwmg_data *)dev_get_drvdata(dev); + + if (!pd) return 0; + + rc = kstrtoul (buf, 0, &enable); + if (rc) + return rc; + + rc = -ENXIO; + + mutex_lock (&pd->ops_lock); + + if (enable == 1 || enable == 0) { + pd->enable = enable; + pwmg_update_status (pd); + rc = count; + } + + mutex_unlock (&pd->ops_lock); + + return rc; +} + + +static ssize_t pwmg_show_duty (struct device *dev, + struct device_attribute *attr, char *buf) { + + struct pwmg_data *pd = (struct pwmg_data *)dev_get_drvdata(dev); + + if (!pd) return 0; + + return sprintf(buf, "%lu\n", pd->duty); +} + + +static ssize_t pwmg_store_duty (struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) { + + int rc; + unsigned long duty; + struct pwmg_data *pd = (struct pwmg_data *)dev_get_drvdata(dev); + + if (!pd) return 0; + + rc = kstrtoul (buf, 0, &duty); + if (rc) + return rc; + + rc = -ENXIO; + + mutex_lock (&pd->ops_lock); + if (duty > pd->max_duty) + rc = -EINVAL; + else { + pd->duty = duty; + pwmg_update_status (pd); + rc = count; + } + + mutex_unlock (&pd->ops_lock); + + return rc; +} + + +static ssize_t pwmg_show_max_duty (struct device *dev, + struct device_attribute *attr, char *buf) { + + struct pwmg_data *pd = (struct pwmg_data *)dev_get_drvdata(dev); + + if (!pd) return 0; + + return sprintf(buf, "%lu\n", pd->max_duty); +} + + +static ssize_t pwmg_show_period (struct device *dev, + struct device_attribute *attr, char *buf) { + + struct pwmg_data *pd = (struct pwmg_data *)dev_get_drvdata(dev); + + if (!pd) return 0; + + return sprintf(buf, "%d\n", pd->period); +} + + +static ssize_t pwmg_store_period (struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) { + + int rc; + unsigned long period; + struct pwmg_data *pd = (struct pwmg_data *)dev_get_drvdata(dev); + + if (!pd) return 0; + + rc = kstrtoul (buf, 0, &period); + if (rc) + return rc; + + rc = -ENXIO; + + mutex_lock (&pd->ops_lock); + if ((period > pd->period_max) || (period < pd->period_min)) + rc = -EINVAL; + else { + pd->lth_duty /= pd->period; + pd->period = period; + pd->lth_duty = pd->lth_duty * pd->period; + pwmg_update_status (pd); + rc = count; + } + + mutex_unlock (&pd->ops_lock); + + return rc; +} + + +static ssize_t pwmg_show_max_period (struct device *dev, + struct device_attribute *attr, char *buf) { + + struct pwmg_data *pd = (struct pwmg_data *)dev_get_drvdata(dev); + + if (!pd) return 0; + return sprintf(buf, "%d\n", pd->period_max); +} + + +static ssize_t pwmg_show_min_period (struct device *dev, + struct device_attribute *attr, char *buf) { + + struct pwmg_data *pd = (struct pwmg_data *)dev_get_drvdata(dev); + + if (!pd) return 0; + + return sprintf(buf, "%d\n", pd->period_min); +} + +static ssize_t pwmg_show_oneshot_time (struct device *dev, + struct device_attribute *attr, char *buf) { + + struct pwmg_data *pd = (struct pwmg_data *)dev_get_drvdata(dev); + + if (!pd) return 0; + return sprintf(buf, "%d\n", jiffies_to_msecs(pd->oneshot) / DIV_TIME); +} + +static ssize_t pwmg_store_oneshot_time (struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) { + + int rc; + unsigned long int oneshot; + struct pwmg_data *pd = (struct pwmg_data *)dev_get_drvdata(dev); + + if (!pd) return 0; + + rc = kstrtoul (buf, 0, &oneshot); + if (rc) + return rc; + + rc = -ENXIO; + + mutex_lock (&pd->ops_lock); + + if (oneshot >= 0 ) { + pd->oneshot = msecs_to_jiffies(oneshot*DIV_TIME); + pd->enable = 1; + pwmg_update_status (pd); + rc = count; + /* launch work */ + if (pd->pwmg_workq) + queue_delayed_work(pd->pwmg_workq, &pd->pwmg_oneshot_work, PWMG_WORK_POLLING_JIFFIES); + } + + mutex_unlock (&pd->ops_lock); + + return rc; + +} + +static DEVICE_ATTR (enable, 0644, pwmg_show_enable, pwmg_store_enable); +static DEVICE_ATTR (duty, 0644, pwmg_show_duty, pwmg_store_duty); +static DEVICE_ATTR (max_duty, 0444, pwmg_show_max_duty, NULL); +static DEVICE_ATTR (period_ns, 0644, pwmg_show_period, pwmg_store_period); +static DEVICE_ATTR (period_ns_max, 0444, pwmg_show_max_period, NULL); +static DEVICE_ATTR (period_ns_min, 0444, pwmg_show_min_period, NULL); +static DEVICE_ATTR (oneshot_ms, 0644, pwmg_show_oneshot_time, pwmg_store_oneshot_time); + +#ifdef CONFIG_OF +static int pwmg_generic_parse_dt(struct device *dev, + struct platform_pwmg_generic_data *data) +{ + struct device_node *node = dev->of_node; + int ret; + + if (!node) + return -ENODEV; + + memset(data, 0, sizeof(*data)); + + /* determine the pwm-id */ + + ret = of_property_read_u32(node, "pwm-id", + &(data->pwm_id)); + if (ret < 0) + return ret; + + /* determine the max-duty */ + + ret = of_property_read_u32(node, "max-duty", + &(data->max_duty)); + if (ret < 0) + return ret; + + /* determine the dft-duty */ + + ret = of_property_read_u32(node, "dft-duty", + &(data->dft_duty)); + if (ret < 0) + return ret; + + /* determine the pwm-period-ns */ + + ret = of_property_read_u32(node, "pwm-period-ns", + &(data->pwm_period_ns)); + if (ret < 0) + return ret; + + /* determine the period-ns-min */ + + ret = of_property_read_u32(node, "period-ns-min", + &(data->period_ns_min)); + if (ret < 0) + return ret; + + /* determine the period-ns-max */ + + ret = of_property_read_u32(node, "period-ns-max", + &(data->period_ns_max)); + if (ret < 0) + return ret; + + /* determine the enable */ + + ret = of_property_read_u32(node, "enable", + &(data->enable)); + if (ret < 0) + return ret; + + return 0; +} + +static struct of_device_id pwmg_generic_of_match[] = { + { .compatible = "pwm-generic" }, + { } +}; + +MODULE_DEVICE_TABLE(of, pwmg_generic_of_match); +#else +static int pwmg_generic_parse_dt(struct device *dev, + struct platform_pwmg_backlight_data *data) +{ + return -ENODEV; +} +#endif + +static int pwmg_generic_probe (struct platform_device *pdev) { + + struct platform_pwmg_generic_data *data = pdev->dev.platform_data; + struct platform_pwmg_generic_data defdata; + int ret; + struct pwmg_data *pd; + + dev_info(&pdev->dev,"probe...\n"); + + if (!data) { + ret = pwmg_generic_parse_dt(&pdev->dev, &defdata); + if (ret < 0) { + dev_err(&pdev->dev, "failed to find platform data\n"); + return ret; + } + + data = &defdata; + } + + pd = kzalloc (sizeof(*pd), GFP_KERNEL); + if (!pd) { + PWMG_ERR ("no memory for state"); + ret = -ENOMEM; + goto err_alloc; + } + + pd->dev = &pdev->dev; + pd->pwm = pwm_get(&pdev->dev, NULL); + if (IS_ERR(pd->pwm)) { + dev_info(&pdev->dev, "unable to request PWM, trying legacy API\n"); + pd->pwm = pwm_request(data->pwm_id, PWM_NAME_ID); + } + if (IS_ERR(pd->pwm)) { + PWMG_ERR ("unable to request PWM"); + ret = PTR_ERR(pd->pwm); + goto err_pwm; + } else + PWMG_DBG ("got pwm for backlight\n"); + + data->reserved = pd; + + pd->duty = data->dft_duty; + pd->max_duty = data->max_duty; + + pd->period = data->pwm_period_ns; + pd->period_max = data->period_ns_max; + pd->period_min = data->period_ns_min; + + pd->lth_duty = data->lth_duty * + (data->pwm_period_ns / data->max_duty); + + pd->enable = data->enable; + + mutex_init(&pd->ops_lock); + + ret = device_create_file (pd->dev, &dev_attr_enable); + if (ret < 0) + goto err_fs; + ret = device_create_file (pd->dev, &dev_attr_duty); + if (ret < 0) + goto err_fs; + ret = device_create_file (pd->dev, &dev_attr_max_duty); + if (ret < 0) + goto err_fs; + ret = device_create_file (pd->dev, &dev_attr_period_ns); + if (ret < 0) + goto err_fs; + ret = device_create_file (pd->dev, &dev_attr_period_ns_max); + if (ret < 0) + goto err_fs; + ret = device_create_file (pd->dev, &dev_attr_period_ns_min); + if (ret < 0) + goto err_fs; + ret = device_create_file (pd->dev, &dev_attr_oneshot_ms); + if (ret < 0) + goto err_fs; + pwmg_update_status (pd); + + /* Create workqueue */ + pd->pwmg_workq = create_singlethread_workqueue("pwmg_oneshot"); + if (pd->pwmg_workq == NULL) { + return -EINVAL; + } + INIT_DELAYED_WORK(&pd->pwmg_oneshot_work, pwmg_oneshot_work_handler ); + + platform_set_drvdata(pdev, pd); + + return 0; + +err_fs: + pwm_free(pd->pwm); +err_pwm: + kfree(pd); +err_alloc: + return ret; +} + + +static int pwmg_generic_remove(struct platform_device *pdev) { + + struct platform_pwmg_generic_data *data = pdev->dev.platform_data; + struct pwmg_data *pd = (struct pwmg_data *)data->reserved; + + pwm_config (pd->pwm, 0, pd->period); + pwm_disable (pd->pwm); + pwm_free (pd->pwm); + kfree (pd); + return 0; +} + + +static int pwmg_generic_suspend(struct device *pdev) { + return 0; +} + + +static int pwmg_generic_resume (struct device *pdev) { + return 0; +} + +static const struct dev_pm_ops pwmg_generic_pm_ops = { +#ifdef CONFIG_PM_SLEEP + .suspend = pwmg_generic_suspend, + .resume = pwmg_generic_resume, +#endif +}; + +static struct platform_driver pwmg_generic_driver = { + .driver = { + .name = "pwm-generic", + .of_match_table = of_match_ptr(pwmg_generic_of_match), + }, + .probe = pwmg_generic_probe, + .remove = pwmg_generic_remove, +}; + +module_platform_driver(pwmg_generic_driver); + +MODULE_AUTHOR("DC MS SECO"); +MODULE_DESCRIPTION("SECO generic PWM Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pwm-generic"); diff --git a/include/linux/pwm_generic.h b/include/linux/pwm_generic.h new file mode 100644 index 00000000000000..4b315fd33325a8 --- /dev/null +++ b/include/linux/pwm_generic.h @@ -0,0 +1,23 @@ +/* + * Generic PWM data - see drivers/seco/pwm_generic.h + */ +#ifndef __LINUX_PWM_GENERIC_H +#define __LINUX_PWM_GENERIC_H + + +#define MAX_PERIOD_NS 50000000 // 50 ms +#define MIN_PERIOD_NS 150 // 150 ns + +struct platform_pwmg_generic_data { + int pwm_id; + unsigned int max_duty; + unsigned int dft_duty; + unsigned int lth_duty; + unsigned int pwm_period_ns; + unsigned int period_ns_max; + unsigned int period_ns_min; + void *reserved; + unsigned int enable; +}; + +#endif -- GitLab