This is a simple DC-motor motion control driver that implements - 1 or 2 PWM channels for uni-directional or bi-directional drive - Trapezoidal acceleration profiles - Time-based movements - External trigger support Tested with TI DRV8873 Signed-off-by: David Jander <david@xxxxxxxxxxx> --- drivers/motion/Kconfig | 13 ++- drivers/motion/Makefile | 1 + drivers/motion/simple-pwm.c | 199 ++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 drivers/motion/simple-pwm.c diff --git a/drivers/motion/Kconfig b/drivers/motion/Kconfig index 7715301c667e..63c4bdedb12a 100644 --- a/drivers/motion/Kconfig +++ b/drivers/motion/Kconfig @@ -11,7 +11,6 @@ menuconfig MOTION if MOTION - config TMC5240 tristate "TMC5240 stepper motor driver" depends on SPI @@ -23,6 +22,18 @@ config TMC5240 To compile this driver as a module, choose M here: the module will be called tmc5240. +config MOTION_SIMPLE_PWM + tristate "Simple PWM base DC motor driver" + depends on PWM + select MOTION_HELPERS + help + Say Y here is you have a DC motor driver you wish to control + with 1 or 2 PWM outputs. Typically this is an H-bridge or similar + driver, like the TI DRV8873 for example. + + To compile this driver as a module, choose M here: the + module will be called "motion-simple-pwm". + config MOTION_HELPERS bool depends on MOTION diff --git a/drivers/motion/Makefile b/drivers/motion/Makefile index 4f4e31138503..6b13b527fa17 100644 --- a/drivers/motion/Makefile +++ b/drivers/motion/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_MOTION) += motion-core.o obj-$(CONFIG_MOTION_HELPERS) += motion-helpers.o obj-$(CONFIG_TMC5240) += tmc5240.o +obj-$(CONFIG_MOTION_SIMPLE_PWM) += simple-pwm.o diff --git a/drivers/motion/simple-pwm.c b/drivers/motion/simple-pwm.c new file mode 100644 index 000000000000..89626c792235 --- /dev/null +++ b/drivers/motion/simple-pwm.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Motion Control Subsystem - Simple speed proportional-PWM based motor driver + * + * Copyright (C) 2024 Protonic Holland + * David Jander <david@xxxxxxxxxxx> + */ + +#include <asm-generic/errno-base.h> +#include <linux/err.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/motion.h> +#include <linux/types.h> +#include <linux/pm.h> +#include <linux/pwm.h> +#include <asm/unaligned.h> + +#include "motion-core.h" +#include "motion-helpers.h" + +#define MOTPWM_PWM_SCALE 100000 +#define MOTPWM_TIMER_PERIOD (20 * NSEC_PER_MSEC) + +struct motpwm { + struct pwm_device *pwms[2]; + struct motion_device mdev; + struct platform_device *pdev; + bool pwm_inverted; +}; + +static inline int __effective_dc(struct motpwm *mp, unsigned int dc) +{ + if (mp->pwm_inverted) + return MOTPWM_PWM_SCALE - dc; + return dc; +} + +static inline int __motpwm_set_speed_locked(struct motpwm *mp, unsigned int dir, + unsigned int dc, bool enable) +{ + struct pwm_state ps; + int cidx = !dir; + struct pwm_device *pwm, *cpwm; + + dir = !!dir; + pwm = mp->pwms[dir]; + cpwm = mp->pwms[cidx]; + + if (cpwm) { + pwm_init_state(cpwm, &ps); + ps.duty_cycle = __effective_dc(mp, 0); + ps.enabled = enable; + pwm_apply_might_sleep(cpwm, &ps); + } + if (!pwm) + return -EINVAL; + + pwm_init_state(pwm, &ps); + pwm_set_relative_duty_cycle(&ps, __effective_dc(mp, dc), MOTPWM_PWM_SCALE); + ps.enabled = enable; + pwm_apply_might_sleep(pwm, &ps); + + return 0; +} + +static void motpwm_startup(struct motion_device *mdev) +{ + dev_info(mdev->dev, "Startup\n"); +} + +static void motpwm_powerdown(struct motion_device *mdev) +{ + struct motpwm *mp = container_of(mdev, struct motpwm, mdev); + + dev_info(mdev->dev, "Shutdown\n"); + __motpwm_set_speed_locked(mp, 0, 0, false); +} + +static int motpwm_check_speed(struct motion_device *mdev, unsigned int dir, + unsigned int speed) +{ + struct motpwm *mp = container_of(mdev, struct motpwm, mdev); + + if (!mp->pwms[!!dir]) + return -EINVAL; + + if (speed > MOTPWM_PWM_SCALE) + return -EINVAL; + + return 0; +} + +static void motpwm_set_speed(struct motion_device *mdev, unsigned int dir, + unsigned int speed) +{ + struct motpwm *mp = container_of(mdev, struct motpwm, mdev); + + __motpwm_set_speed_locked(mp, dir, speed, true); +} + +static struct motion_timed_speed_ops motpwm_mts_ops = { + .startup = motpwm_startup, + .powerdown = motpwm_powerdown, + .check_speed = motpwm_check_speed, + .set_speed = motpwm_set_speed +}; + +static int motpwm_probe(struct platform_device *pdev) +{ + struct motpwm *mp; + struct device *dev = &pdev->dev; + struct fwnode_handle *fwnode = dev_fwnode(dev); + + mp = devm_kzalloc(dev, sizeof(struct motpwm), GFP_KERNEL); + if (!mp) + return -ENOMEM; + + mp->pwms[0] = devm_fwnode_pwm_get(dev, fwnode, "left"); + if (IS_ERR(mp->pwms[0])) { + int err = PTR_ERR(mp->pwms[0]); + + if (err == -ENODEV) + mp->pwms[0] = NULL; + else + return err; + } + mp->pwms[1] = devm_fwnode_pwm_get(dev, fwnode, "right"); + if (IS_ERR(mp->pwms[1])) { + int err = PTR_ERR(mp->pwms[1]); + + if (err == -ENODEV) + mp->pwms[1] = NULL; + else + return err; + } + if (!mp->pwms[0] && !mp->pwms[1]) { + dev_err(dev, "Need at least one PWM"); + return -ENODEV; + } + + mp->pwm_inverted = fwnode_property_read_bool(fwnode, "motion,pwm-inverted"); + + mp->pdev = pdev; + platform_set_drvdata(pdev, mp); + + mp->mdev.parent = &pdev->dev; + motion_timed_speed_init(&mp->mdev, &motpwm_mts_ops, MOTPWM_PWM_SCALE); + mp->mdev.capabilities.type = MOT_TYPE_DC_MOTOR; + mp->mdev.capabilities.subdiv = 1; + motion_fwnode_get_capabilities(&mp->mdev, fwnode); + + return motion_register_device(&mp->mdev); +} + +static void motpwm_shutdown(struct platform_device *pdev) +{ + struct motpwm *mp = platform_get_drvdata(pdev); + + motion_unregister_device(&mp->mdev); +} + +static int motpwm_suspend(struct device *dev) +{ + return 0; +} + +static int motpwm_resume(struct device *dev) +{ + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(motpwm_pm, motpwm_suspend, motpwm_resume); + +static const struct of_device_id motpwm_of_match[] = { + { .compatible = "motion-simple-pwm" }, + {} +}; +MODULE_DEVICE_TABLE(of, motpwm_of_match); + +static struct platform_driver motpwm_driver = { + .probe = motpwm_probe, + .shutdown = motpwm_shutdown, + .driver = { + .name = "motion-simple-pwm", + .pm = pm_sleep_ptr(&motpwm_pm), + .of_match_table = motpwm_of_match, + }, +}; + +module_platform_driver(motpwm_driver); + +MODULE_AUTHOR("David Jander <david@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("Simple PWM based DC motor motion control driver"); +MODULE_LICENSE("GPL"); -- 2.47.2