diff mbox series

[RFC,2/7] motion: Add ADI/Trinamic TMC5240 stepper motor controller

Message ID 20250227162823.3585810-3-david@protonic.nl (mailing list archive)
State New
Headers show
Series Add Linux Motion Control subsystem | expand

Commit Message

David Jander Feb. 27, 2025, 4:28 p.m. UTC
The TMC5240 is an advanced stepper motor controller that includes a an
automatic ramp generator and target positioning. This driver makes use of
the Linux Motion Control subsystem. It can use the internal REFl/REFR
end stop inputs as trigger events and exposes setup and configuration
parameters as well as target positions of virtual end-stops as sysfs
device attributes.

Signed-off-by: David Jander <david@protonic.nl>
---
 MAINTAINERS                 |    5 +
 drivers/motion/Kconfig      |   12 +
 drivers/motion/Makefile     |    1 +
 drivers/motion/tmc5240.c    | 1157 +++++++++++++++++++++++++++++++++++
 include/uapi/linux/motion.h |    2 +-
 5 files changed, 1176 insertions(+), 1 deletion(-)
 create mode 100644 drivers/motion/tmc5240.c
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 57267584166c..b5801db5cb8e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1628,6 +1628,11 @@  F:	drivers/iio/amplifiers/hmc425a.c
 F:	drivers/staging/iio/*/ad*
 X:	drivers/iio/*/adjd*
 
+ANALOG DEVICES INC TMC5240 MOTION DRIVER
+M:	David Jander <david@protonic>
+S:	Maintained
+F:	drivers/motion/tmc5240.c
+
 ANALOGBITS PLL LIBRARIES
 M:	Paul Walmsley <paul.walmsley@sifive.com>
 M:	Samuel Holland <samuel.holland@sifive.com>
diff --git a/drivers/motion/Kconfig b/drivers/motion/Kconfig
index 085f9647b47b..7715301c667e 100644
--- a/drivers/motion/Kconfig
+++ b/drivers/motion/Kconfig
@@ -11,6 +11,18 @@  menuconfig MOTION
 
 if MOTION
 
+
+config TMC5240
+	tristate "TMC5240 stepper motor driver"
+	depends on SPI
+	select REGMAP_SPI
+	help
+	  Say Y here if you have an Analog Devices TMC5240 stepper
+	  motor controller.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called tmc5240.
+
 config MOTION_HELPERS
 	bool
 	depends on MOTION
diff --git a/drivers/motion/Makefile b/drivers/motion/Makefile
index ed912a8ed605..4f4e31138503 100644
--- a/drivers/motion/Makefile
+++ b/drivers/motion/Makefile
@@ -1,3 +1,4 @@ 
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_MOTION)		+= motion-core.o
 obj-$(CONFIG_MOTION_HELPERS)	+= motion-helpers.o
+obj-$(CONFIG_TMC5240)		+= tmc5240.o
diff --git a/drivers/motion/tmc5240.c b/drivers/motion/tmc5240.c
new file mode 100644
index 000000000000..a6723b8ce66b
--- /dev/null
+++ b/drivers/motion/tmc5240.c
@@ -0,0 +1,1157 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TMC5240 Stepper motor controller driver
+ *
+ * Copyright (C) 2024 Protonic Holland
+ *      David Jander <david@protonic.nl>
+ */
+
+#include <linux/bug.h>
+#include <asm-generic/errno-base.h>
+#include <linux/bitfield.h>
+#include <linux/array_size.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/motion.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/gfp_types.h>
+#include <linux/types.h>
+#include <linux/regmap.h>
+#include <linux/clk.h>
+#include <asm/unaligned.h>
+
+#include "motion-core.h"
+
+/* General Configuration Registers */
+#define TMC_REG_GCONF		0x00
+#define TMC_REG_GSTAT		0x01
+#define TMC_REG_IFCNT		0x02
+#define TMC_REG_NODECONF	0x03
+#define TMC_REG_IOIN		0x04
+#define TMC_IOIN_VERSION_MASK	GENMASK(31, 24)
+#define TMC_IOIN_REVISION_MASK	GENMASK(18, 16)
+#define TMC_REG_X_COMPARE	0x05
+#define TMC_REG_X_COMPARE_REP	0x06
+#define TMC_REG_DRV_CONF	0x0a
+#define TMC_DRV_CURRENT_RANGE	GENMASK(1, 0)
+#define TMC_DRV_SLOPE_CONTROL	GENMASK(5, 4)
+#define TMC_REG_GLOBAL_SCALER	0x0b
+/* Velocity Dependent Configuration Registers */
+#define TMC_REG_IHOLD_IRUN	0x10
+#define TMC_IHOLD_IRUNDELAY	GENMASK(27, 24)
+#define TMC_IHOLD_IHOLDDELAY	GENMASK(19, 16)
+#define TMC_IHOLD_IRUN		GENMASK(12, 8)
+#define TMC_IHOLD_IHOLD		GENMASK(4, 0)
+#define TMC_REG_TPOWERDOWN	0x11
+#define TMC_REG_TSTEP		0x12
+#define TMC_REG_TPWMTHRS	0x13
+#define TMC_REG_TCOOLTHRS	0x14
+#define TMC_REG_THIGH		0x15
+/* Ramp Generator Registers */
+#define TMC_REG_RAMPMODE	0x20
+#define TMC_RAMPMODE_POSITION	0
+#define TMC_RAMPMODE_POS_VEL	1
+#define TMC_RAMPMODE_NEG_VEL	2
+#define TMC_RAMPMODE_HOLD	3
+#define TMC_REG_XACTUAL		0x21
+#define TMC_REG_VACTUAL		0x22
+#define TMC_VACTUAL_SIGN_BIT	23
+#define TMC_VACTUAL_MASK	GENMASK(TMC_VACTUAL_SIGN_BIT, 0)
+#define TMC_REG_VSTART		0x23
+#define TMC_REG_A1		0x24
+#define TMC_REG_V1		0x25
+#define TMC_REG_AMAX		0x26
+#define TMC_MAX_ACCELERATION	GENMASK(17, 0)
+#define TMC_REG_VMAX		0x27
+#define TMC_REG_DMAX		0x28
+#define TMC_REG_TVMAX		0x29
+#define TMC_REG_D1		0x2a
+#define TMC_D1_MIN		10
+#define TMC_REG_VSTOP		0x2b
+#define TMC_VSTOP_MIN		10
+#define TMC_REG_TZEROWAIT	0x2c
+#define TMC_REG_XTARGET		0x2d
+#define TMC_REG_V2		0x2e
+#define TMC_REG_A2		0x2f
+#define TMC_REG_D2		0x30
+#define TMC_D2_MIN		10
+/* Ramp Generator Driver Feature Control Registers */
+#define TMC_REG_VDCMIN		0x33
+#define TMC_REG_SW_MODE		0x34
+#define TMC_SW_VIRT_STOP_ENC	BIT(14)
+#define TMC_SW_EN_VIRT_STOP_R	BIT(13)
+#define TMC_SW_EN_VIRT_STOP_L	BIT(12)
+#define TMC_SW_EN_SOFTSTOP	BIT(11)
+#define TMC_SW_LATCH_R_ACTIVE	BIT(7)
+#define TMC_SW_LATCH_L_ACTIVE	BIT(5)
+#define TMC_SW_SWAP_LR		BIT(4)
+#define TMC_SW_STOP_R_POL	BIT(3)
+#define TMC_SW_STOP_L_POL	BIT(2)
+#define TMC_SW_STOP_R_ENABLE	BIT(1)
+#define TMC_SW_STOP_L_ENABLE	BIT(0)
+#define TMC_REG_RAMP_STAT	0x35
+#define TMC_RAMP_VIRT_R		BIT(15)
+#define TMC_RAMP_VIRT_L		BIT(14)
+#define TMC_VIRT_STOPS_MASK	GENMASK(15, 14)
+#define TMC_RAMP_SECOND_MOVE	BIT(12)
+#define TMC_RAMP_POS_REACHED	BIT(7)
+#define TMC_RAMP_STOP_SG	BIT(6)
+#define TMC_RAMP_STOP_R		BIT(5)
+#define TMC_RAMP_STOP_L		BIT(4)
+#define TMC_RAMP_LATCH_R	BIT(3)
+#define TMC_RAMP_LATCH_L	BIT(2)
+#define TMC_RAMP_ST_STOP_R	BIT(1)
+#define TMC_RAMP_ST_STOP_L	BIT(0)
+#define TMC_STOPS_MASK		GENMASK(1, 0)
+#define TMC_RAMP_CLEAR_FLAGS	(TMC_RAMP_SECOND_MOVE | TMC_RAMP_POS_REACHED | \
+		TMC_RAMP_STOP_SG | TMC_RAMP_LATCH_R | TMC_RAMP_LATCH_L)
+#define TMC_REG_XLATCH		0x36
+/* Encoder registers */
+#define TMC_REG_ENCMODE		0x38
+#define TMC_REG_X_ENC		0x39
+#define TMC_REG_ENC_CONST	0x3a
+#define TMC_REG_ENC_STATUS	0x3b
+#define TMC_REG_ENC_LATCH	0x3c
+#define TMC_REG_ENC_DEVIATION	0x3d
+#define TMC_REG_VIRTUAL_STOP_L	0x3e
+#define TMC_REG_VIRTUAL_STOP_R	0x3f
+/* ADC Registers */
+#define TMC_REG_ADC_VSUPLLY_AIN	0x50
+#define TMC_REG_ADC_TEMP	0x51
+#define TMC_REG_OTW_OV_VTH	0x52
+/* Motoro Driver Registers */
+#define TMC_REG_MSLUT(X)	(0x60+X)
+#define TMC_REG_MSLUTSEL	0x68
+#define TMC_REG_MSLUTSTART	0x69
+#define TMC_REG_MSCNT		0x6a
+#define TMC_REG_MSCURACT	0x6b
+#define TMC_REG_CHOPCONF	0x6c
+#define TMC_CHOP_DISS2VS	BIT(31)
+#define TMC_CHOP_DISS2G		BIT(30)
+#define TMC_CHOP_INTPOL		BIT(28)
+#define TMC_CHOP_MRES		GENMASK(27, 24)
+#define TMC_CHOP_TPFP		GENMASK(23, 20)
+#define TMC_CHOP_VHIGHCHM	BIT(19)
+#define TMC_CHOP_VHIGHFS	BIT(18)
+#define TMC_CHOP_TBL		GENMASK(16, 15)
+#define TMC_CHOP_CHM		BIT(14)
+#define TMC_CHOP_DISFDCC	BIT(12)
+#define TMC_CHOP_FD3		BIT(11)
+#define TMC_CHOP_HEND_OFFSET	GENMASK(10, 7)
+#define TMC_CHOP_HSTRT_TFD210	GENMASK(6, 4)
+#define TMC_CHOP_TOFF		GENMASK(3, 0)
+#define TMC_REG_COOLCONF	0x6d
+#define TMC_REG_DCCTRL		0x6e
+#define TMC_REG_DRV_STATUS	0x6f
+#define TMC_REG_PWMCONF		0x70
+#define TMC_REG_PWM_SCALE	0x71
+#define TMC_REG_PWM_AUTO	0x72
+#define TMC_REG_SG4_THRS	0x74
+#define TMC_REG_SG4_RESULT	0x75
+#define TMC_REG_SG4_IND		0x76
+
+/*
+ * Internal triggers: REFL, REFR, VIRTL, VIRTR and X_COMPARE
+ * FIXME: For now X_COMPARE is not yet supported
+ */
+enum {
+	TMC_STOP_REFL = 0,
+	TMC_STOP_REFR,
+	TMC_STOP_VIRTL,
+	TMC_STOP_VIRTR,
+	TMC_MAX_INTERNAL_STOPS
+};
+
+struct tmc_spi_buf {
+	u8 buf[5];
+} ____cacheline_aligned;
+
+struct tmc5240 {
+	struct spi_device *spi;
+	struct motion_device mdev;
+	struct spi_transfer wxfer;
+	struct spi_transfer rxfers[2];
+	struct spi_message wmsg;
+	struct spi_message rmsg;
+	struct tmc_spi_buf rtxbuf[2]; /* read buffers contain 2 40bit transfers */
+	struct tmc_spi_buf rrxbuf[2];
+	struct tmc_spi_buf wtxbuf;
+	struct tmc_spi_buf wrxbuf;
+	struct regmap *map;
+	u8 spi_status;
+	struct clk *clk;
+	u32 clk_rate;
+	bool use_encoder;
+	int current_index;
+	s32 next_dist;
+	unsigned int next_index;
+	s32 int_trig_dist;
+	unsigned int int_trg_index;
+	s32 ext_trig_dist;
+	unsigned int ext_trg_index;
+	ktime_t irq_ts;
+	unsigned int stop_functions[TMC_MAX_INTERNAL_STOPS];
+};
+
+static int tmc5240_read_reg(void *ctx, unsigned int reg, unsigned int *val)
+{
+	struct tmc5240 *tmc = (struct tmc5240 *)ctx;
+	int ret;
+	u8 *rxb0 = &tmc->rrxbuf[0].buf[0];
+	u8 *rxb1 = &tmc->rrxbuf[1].buf[0];
+	u8 *txb0 = &tmc->rtxbuf[0].buf[0];
+	u8 *txb1 = &tmc->rtxbuf[1].buf[0];
+
+	txb0[0] = reg;
+	txb1[0] = reg;
+	ret = spi_sync(tmc->spi, &tmc->rmsg);
+	if (ret)
+		return ret;
+	*val = get_unaligned_be32(&rxb1[1]);
+	tmc->spi_status = rxb0[0];
+
+	return 0;
+}
+
+static int tmc5240_write_reg(void *ctx, unsigned int reg, unsigned int val)
+{
+	struct tmc5240 *tmc = (struct tmc5240 *)ctx;
+	u8 *txb = &tmc->wtxbuf.buf[0];
+
+	txb[0] = reg | 0x80;
+	put_unaligned_be32(val, &txb[1]);
+
+	return spi_sync(tmc->spi, &tmc->wmsg);
+}
+
+/* regmap wrappers */
+static int tmc5240_read(struct tmc5240 *tmc, unsigned int reg, unsigned int *val)
+{
+	int ret = regmap_read(tmc->map, reg, val);
+
+	if (ret)
+		dev_err_ratelimited(tmc->mdev.dev,
+				"Regmap read error %d at reg: %04x.\n",
+				ret, reg);
+	return ret;
+}
+
+static int tmc5240_write(struct tmc5240 *tmc, unsigned int reg, unsigned int val)
+{
+	int ret = regmap_write(tmc->map, reg, val);
+
+	if (ret)
+		dev_err_ratelimited(tmc->mdev.dev,
+				"Regmap write error %d at reg: %04x.\n",
+				ret, reg);
+	return ret;
+}
+
+static int tmc5240_update_bits(struct tmc5240 *tmc, unsigned int reg,
+		unsigned int mask, unsigned int val)
+{
+	int ret = regmap_update_bits(tmc->map, reg, mask, val);
+
+	if (ret)
+		dev_err_ratelimited(tmc->mdev.dev,
+				"Regmap update bits error %d at reg: %04x.\n",
+				ret, reg);
+	return ret;
+}
+
+/* FIXME: Maybe make this into MOTION_ATTR_XX() macro's? */
+#define _tmc_attr_show(_name, _reg, _mask) \
+static ssize_t _name##_show(struct device *dev, \
+				    struct device_attribute *dev_attr, \
+				    char *buf) \
+{ \
+	struct motion_device *mdev = dev_get_drvdata(dev); \
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev); \
+	unsigned int val; \
+	ssize_t err; \
+	err = tmc5240_read(tmc, _reg, &val); \
+	if (err) \
+		return err; \
+	/* NOLINTNEXTLINE(clang-analyzer-security.insecureAPI*) */ \
+	return sprintf(buf, "%lu\n", FIELD_GET(_mask, val)); \
+}
+
+#define _tmc_attr_store(_name, _reg, _mask) \
+static ssize_t _name##_store(struct device *dev, \
+				     struct device_attribute *dev_attr, \
+				     const char *buf, size_t len) \
+{ \
+	struct motion_device *mdev = dev_get_drvdata(dev); \
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev); \
+	unsigned long val; \
+	ssize_t err; \
+	err = kstrtoul(buf, 10, &val); \
+	if (err) \
+		return err; \
+	if (!FIELD_FIT(_mask, val)) \
+		return -EINVAL; \
+	mutex_lock(&mdev->mutex); \
+	err = tmc5240_update_bits(tmc, _reg, _mask, FIELD_PREP(_mask, val)); \
+	mutex_unlock(&mdev->mutex); \
+	if (err) \
+		return err; \
+	return len; \
+}
+
+#define TMC_ATTR_RW(_name, _reg, _mask) \
+	_tmc_attr_show(_name, _reg, _mask) \
+	_tmc_attr_store(_name, _reg, _mask) \
+	static DEVICE_ATTR_RW(_name)
+
+#define TMC_ATTR_RO(_name, _reg, _mask) \
+	_tmc_attr_show(_name, _reg, _mask) \
+	static DEVICE_ATTR_RO(_name)
+
+TMC_ATTR_RW(sw_mode_swap_lr, TMC_REG_SW_MODE, TMC_SW_SWAP_LR);
+TMC_ATTR_RW(drv_conf_current_range, TMC_REG_DRV_CONF, TMC_DRV_CURRENT_RANGE);
+TMC_ATTR_RW(drv_conf_slope_control, TMC_REG_DRV_CONF, TMC_DRV_SLOPE_CONTROL);
+TMC_ATTR_RW(global_scaler, TMC_REG_GLOBAL_SCALER, GENMASK(7, 0));
+TMC_ATTR_RW(t_power_down, TMC_REG_TPOWERDOWN, GENMASK(7, 0));
+TMC_ATTR_RO(t_step, TMC_REG_TSTEP, GENMASK(19, 0));
+TMC_ATTR_RW(t_pwm_threshold, TMC_REG_TPWMTHRS, GENMASK(19, 0));
+TMC_ATTR_RW(t_cool_threshold, TMC_REG_TCOOLTHRS, GENMASK(19, 0));
+TMC_ATTR_RW(t_high, TMC_REG_THIGH, GENMASK(19, 0));
+TMC_ATTR_RW(irun_delay, TMC_REG_IHOLD_IRUN, TMC_IHOLD_IRUNDELAY);
+TMC_ATTR_RW(ihold_delay, TMC_REG_IHOLD_IRUN, TMC_IHOLD_IHOLDDELAY);
+TMC_ATTR_RW(irun, TMC_REG_IHOLD_IRUN, TMC_IHOLD_IRUN);
+TMC_ATTR_RW(ihold, TMC_REG_IHOLD_IRUN, TMC_IHOLD_IHOLD);
+TMC_ATTR_RO(x_latch, TMC_REG_XLATCH, GENMASK(31, 0));
+TMC_ATTR_RW(x_encoder, TMC_REG_X_ENC, GENMASK(31, 0));
+TMC_ATTR_RW(encoder_constant, TMC_REG_ENC_CONST, GENMASK(31, 0));
+TMC_ATTR_RO(encoder_latch, TMC_REG_ENC_LATCH, GENMASK(31, 0));
+TMC_ATTR_RO(x_actual, TMC_REG_XACTUAL, GENMASK(31, 0));
+TMC_ATTR_RW(virtual_stop_l, TMC_REG_VIRTUAL_STOP_L, GENMASK(31, 0));
+TMC_ATTR_RW(virtual_stop_r, TMC_REG_VIRTUAL_STOP_R, GENMASK(31, 0));
+
+static struct attribute *tmc5240_attributes[] = {
+	&dev_attr_sw_mode_swap_lr.attr,
+	&dev_attr_drv_conf_current_range.attr,
+	&dev_attr_drv_conf_slope_control.attr,
+	&dev_attr_global_scaler.attr,
+	&dev_attr_t_power_down.attr,
+	&dev_attr_t_step.attr,
+	&dev_attr_t_pwm_threshold.attr,
+	&dev_attr_t_cool_threshold.attr,
+	&dev_attr_t_high.attr,
+	&dev_attr_irun_delay.attr,
+	&dev_attr_ihold_delay.attr,
+	&dev_attr_irun.attr,
+	&dev_attr_ihold.attr,
+	&dev_attr_x_latch.attr,
+	&dev_attr_x_encoder.attr,
+	&dev_attr_encoder_constant.attr,
+	&dev_attr_encoder_latch.attr,
+	&dev_attr_x_actual.attr,
+	&dev_attr_virtual_stop_l.attr,
+	&dev_attr_virtual_stop_r.attr,
+	NULL
+};
+
+static const struct attribute_group tmc5240_group = {
+	.attrs = tmc5240_attributes,
+};
+
+static irqreturn_t tmc5240_hard_irq(int irq, void *handle)
+{
+	struct tmc5240 *tmc = handle;
+
+	dev_info(tmc->mdev.dev, "Hard IRQ spistat = 0x%02x\n", tmc->spi_status);
+	tmc->irq_ts = ktime_get();
+
+	return IRQ_WAKE_THREAD;
+}
+
+static int tmc5240_motion_with_index(struct motion_device *mdev,
+		unsigned int index, s32 d);
+
+static irqreturn_t tmc5240_irq(int irq, void *handle)
+{
+	struct tmc5240 *tmc = handle;
+	struct motion_device *mdev = &tmc->mdev;
+	struct mot_event evt = {0};
+	u32 val;
+
+	evt.timestamp = ktime2mot_time(tmc->irq_ts);
+	tmc5240_read(tmc, TMC_REG_RAMP_STAT, &val);
+	tmc5240_read(tmc, TMC_REG_XACTUAL, &evt.position);
+	tmc5240_read(tmc, TMC_REG_VACTUAL, &evt.speed);
+
+	if (val & (TMC_RAMP_STOP_L | TMC_RAMP_STOP_R)) {
+		if ((val & TMC_RAMP_VIRT_L) &&
+				(tmc->stop_functions[TMC_STOP_VIRTL]))
+			evt.input_index = TMC_STOP_VIRTL;
+		else if ((val & TMC_RAMP_VIRT_R) &&
+				(tmc->stop_functions[TMC_STOP_VIRTR]))
+			evt.input_index = TMC_STOP_VIRTR;
+		else
+			evt.input_index = (val & TMC_RAMP_STOP_R) ?
+				TMC_STOP_REFR : TMC_STOP_REFL;
+		evt.event = MOT_EVENT_STOP;
+
+		/* Check if we have a trigger motion waiting */
+		mutex_lock(&mdev->mutex);
+		if (tmc->int_trig_dist) {
+			tmc5240_motion_with_index(mdev, tmc->int_trg_index,
+					tmc->int_trig_dist);
+			tmc->int_trig_dist = 0;
+		}
+		motion_report_event(mdev, &evt);
+		mutex_unlock(&mdev->mutex);
+	} else if (val & TMC_RAMP_POS_REACHED) {
+		evt.event = MOT_EVENT_TARGET;
+
+		/* Check if we have a next motion waiting */
+		mutex_lock(&mdev->mutex);
+		if (tmc->next_dist) {
+			tmc5240_motion_with_index(mdev, tmc->next_index,
+					tmc->next_dist);
+			tmc->next_dist = 0;
+		}
+		motion_report_event(mdev, &evt);
+		mutex_unlock(&mdev->mutex);
+	} else {
+		dev_info(tmc->mdev.dev,
+			"Unknown IRQ source 0x%04x. SPI status = 0x%02x\n",
+			val, tmc->spi_status);
+	}
+
+	/* Clear interrupt flags */
+	tmc5240_write(tmc, TMC_REG_RAMP_STAT, val & TMC_RAMP_CLEAR_FLAGS);
+
+	return IRQ_HANDLED;
+}
+
+static void tmc5240_prepare_message(struct tmc5240 *tmc)
+{
+	/*
+	 * TMC5240 sends back read register contents in the _next_ transfer,
+	 * so for read actions we need to do 2 transfers with a CS change in
+	 * between.
+	 */
+	tmc->rxfers[0].tx_buf = &tmc->rtxbuf[0].buf[0];
+	tmc->rxfers[0].rx_buf = &tmc->rrxbuf[0].buf[0];
+	tmc->rxfers[0].len = 5;
+	tmc->rxfers[0].cs_change = 1;
+	tmc->rxfers[1].tx_buf = &tmc->rtxbuf[1].buf[0];
+	tmc->rxfers[1].rx_buf = &tmc->rrxbuf[1].buf[0];
+	tmc->rxfers[1].len = 5;
+	spi_message_init_with_transfers(&tmc->rmsg, &tmc->rxfers[0], 2);
+
+	/*
+	 * For write register operations, a single transfer is sufficient.
+	 * We do not re-use the same buffers, in order to avoid clobbering
+	 * bytes 1-4 of the _read_ TX buffer, so we don't need to clear it
+	 * on each _read_ transfer.
+	 */
+	tmc->wxfer.tx_buf = &tmc->wtxbuf.buf[0];
+	tmc->wxfer.rx_buf = &tmc->wrxbuf.buf[0];
+	tmc->wxfer.len = 5;
+	spi_message_init_with_transfers(&tmc->wmsg, &tmc->wxfer, 1);
+}
+
+static int tmc5240_ramp_set_vel(struct tmc5240 *tmc, u32 vstart, u32 v1, u32 v2,
+		u32 vmax, u32 vstop)
+{
+	int ret;
+
+	ret = tmc5240_write(tmc, TMC_REG_VSTART, vstart);
+	if (ret)
+		return ret;
+	ret = tmc5240_write(tmc, TMC_REG_V1, v1);
+	if (ret)
+		return ret;
+	ret = tmc5240_write(tmc, TMC_REG_V2, v2);
+	if (ret)
+		return ret;
+	ret = tmc5240_write(tmc, TMC_REG_VMAX, vmax);
+	if (ret)
+		return ret;
+	return tmc5240_write(tmc, TMC_REG_VSTOP, max(vstop, TMC_VSTOP_MIN));
+}
+
+static int tmc5240_ramp_set_acc(struct tmc5240 *tmc, u32 a0, u32 a1, u32 a2,
+		u32 a3, u32 a4, u32 a5)
+{
+	int ret;
+
+	ret = tmc5240_write(tmc, TMC_REG_A1, a0);
+	if (ret)
+		return ret;
+	ret = tmc5240_write(tmc, TMC_REG_A2, a1);
+	if (ret)
+		return ret;
+	ret = tmc5240_write(tmc, TMC_REG_AMAX, a2);
+	if (ret)
+		return ret;
+	ret = tmc5240_write(tmc, TMC_REG_DMAX, a3);
+	if (ret)
+		return ret;
+	ret = tmc5240_write(tmc, TMC_REG_D2, max(a4, TMC_D2_MIN));
+	if (ret)
+		return ret;
+	return tmc5240_write(tmc, TMC_REG_D1, max(a5, TMC_D1_MIN));
+}
+
+static int tmc5240_set_ramp_simple(struct tmc5240 *tmc, u32 vmax, u32 amax)
+{
+	int ret;
+
+	/*
+	 * Setup the most simple profile with only one acceleration and
+	 * one speed value. Meant to be used in basic movements without
+	 * profile support.
+	 */
+	ret = tmc5240_ramp_set_vel(tmc, 0, 0, 0, vmax, 0);
+	if (ret)
+		return ret;
+	ret = tmc5240_ramp_set_acc(tmc, 0, 0, amax, amax, 0, 0);
+
+	return ret;
+}
+
+static int tmc5240_open(struct motion_device *mdev)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+	u32 val;
+	int ret;
+
+	tmc->next_dist = 0;
+	tmc->int_trig_dist = 0;
+	tmc->ext_trig_dist = 0;
+
+	ret = tmc5240_read(tmc, TMC_REG_GSTAT, &val);
+	if (ret)
+		return ret;
+	/* Clear reset condition */
+	ret = tmc5240_write(tmc, TMC_REG_GSTAT, val);
+	if (ret)
+		return ret;
+	ret = tmc5240_write(tmc, TMC_REG_GCONF, 0x00000000);
+	if (ret)
+		return ret;
+	ret = tmc5240_update_bits(tmc, TMC_REG_CHOPCONF, TMC_CHOP_TOFF,
+			FIELD_PREP(TMC_CHOP_TOFF, 3));
+	if (ret)
+		return ret;
+	ret = tmc5240_read(tmc, TMC_REG_SW_MODE, &val);
+	if (ret)
+		return ret;
+	if (tmc->use_encoder)
+		val |= TMC_SW_VIRT_STOP_ENC;
+	else
+		val &= ~TMC_SW_VIRT_STOP_ENC;
+	return tmc5240_write(tmc, TMC_REG_SW_MODE, val);
+}
+
+static int tmc5240_release(struct motion_device *mdev)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+	u32 val;
+
+	tmc->current_index = -1;
+
+	/* Turn off RAMP and chopper */
+	tmc5240_write(tmc, TMC_REG_RAMPMODE, TMC_RAMPMODE_POS_VEL);
+	tmc5240_write(tmc, TMC_REG_VMAX, 0);
+	tmc5240_update_bits(tmc, TMC_REG_CHOPCONF, TMC_CHOP_TOFF,
+			FIELD_PREP(TMC_CHOP_TOFF, 0));
+
+	/* Disable event interrupts sources */
+	tmc5240_read(tmc, TMC_REG_SW_MODE, &val);
+	val &= ~(TMC_SW_EN_VIRT_STOP_L | TMC_SW_EN_VIRT_STOP_R |
+			TMC_SW_STOP_L_ENABLE | TMC_SW_STOP_R_ENABLE);
+	return tmc5240_write(tmc, TMC_REG_SW_MODE, val);
+}
+
+static int _sign_extend(u32 val, u8 sbit)
+{
+	return ((int)(val << (31 - sbit)) >> (31 - sbit));
+}
+
+static int tmc5240_get_status(struct motion_device *mdev, struct mot_status *st)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+	u32 val;
+	int ret;
+
+	ret = tmc5240_read(tmc, TMC_REG_XACTUAL, &val);
+	if (ret)
+		return ret;
+	st->position = val;
+	ret = tmc5240_read(tmc, TMC_REG_VACTUAL, &val);
+	if (ret)
+		return ret;
+	st->speed = _sign_extend(val, TMC_VACTUAL_SIGN_BIT);
+	ret = tmc5240_read(tmc, TMC_REG_RAMP_STAT, &val);
+	if (ret)
+		return ret;
+	st->local_inputs = (FIELD_GET(TMC_VIRT_STOPS_MASK, val) << 2) |
+		FIELD_GET(TMC_STOPS_MASK, val);
+
+	return 0;
+}
+
+static int tmc5240_basic_run(struct motion_device *mdev, u32 ch, s32 v)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+	int ret;
+
+	tmc->current_index = -1; /* Invalidate current index */
+	if (v >= 0) {
+		ret = tmc5240_write(tmc, TMC_REG_RAMPMODE, TMC_RAMPMODE_POS_VEL);
+		if (ret)
+			return ret;
+	} else {
+		v = -v;
+		ret = tmc5240_write(tmc, TMC_REG_RAMPMODE, TMC_RAMPMODE_NEG_VEL);
+		if (ret)
+			return ret;
+	}
+	ret = tmc5240_write(tmc, TMC_REG_AMAX, TMC_MAX_ACCELERATION / 2);
+	if (ret)
+		return ret;
+	return tmc5240_write(tmc, TMC_REG_VMAX, v);
+}
+
+static int tmc5240_basic_stop(struct motion_device *mdev, channel_mask_t ch)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+
+	tmc5240_write(tmc, TMC_REG_RAMPMODE, TMC_RAMPMODE_POS_VEL);
+	tmc5240_write(tmc, TMC_REG_AMAX, TMC_MAX_ACCELERATION / 2);
+	tmc->current_index = -1; /* Invalidate current index */
+
+	return tmc5240_write(tmc, TMC_REG_VMAX, 0);
+}
+
+static int tmc5240_set_distance(struct tmc5240 *tmc, s32 d)
+{
+	u32 x;
+	int ret;
+
+	ret = tmc5240_write(tmc, TMC_REG_RAMPMODE, TMC_RAMPMODE_POSITION);
+	if (ret)
+		return ret;
+
+	/* Clear position reached */
+	ret = tmc5240_write(tmc, TMC_REG_RAMP_STAT, TMC_RAMP_POS_REACHED);
+	if (ret)
+		return ret;
+	ret = tmc5240_read(tmc, TMC_REG_XACTUAL, &x);
+	if (ret)
+		return ret;
+	return tmc5240_write(tmc, TMC_REG_XTARGET, x + d);
+}
+
+static int tmc5240_move_distance(struct motion_device *mdev, u32 ch, s32 v, u32 d)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+	int ret;
+
+	ret = tmc5240_set_ramp_simple(tmc, v, 1<<17);
+	if (ret)
+		return ret;
+	return tmc5240_set_distance(tmc, d);
+}
+
+static int tmc5240_move_timed(struct motion_device *mdev, u32 ch, s32 v, ktime_t t)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+
+	dev_info(mdev->dev, "timed ch: 0x%08x: v: %d, duration: %llu\n", ch, v, t);
+	return 0;
+}
+
+#define _check_u(v, n) ((v < 0) || (v >= (1 << n)))
+
+static int tmc5240_validate_profile(struct motion_device *mdev,
+		struct mot_profile *p)
+{
+	speed_raw_t *v = &p->vel[0];
+	accel_raw_t *a = &p->acc[0];
+
+	if (_check_u(v[0], 18) || _check_u(a[0], 18) || _check_u(a[1], 18))
+		return -EINVAL;
+
+	/* Trapezoidal profile */
+	if ((p->na == 2) && (p->nv == 3)) {
+		if (_check_u(v[1], 23) || _check_u(v[2], 18))
+			return -EINVAL;
+		v[2] = max(v[2], TMC_VSTOP_MIN); /* VSTOP (see DS), can't be 0 */
+		return 0;
+	}
+
+	if (_check_u(v[1], 20) || _check_u(a[2], 18) || _check_u(a[3], 18))
+		return -EINVAL;
+
+	/* Dual slope profile */
+	if ((p->na == 4) && (p->nv == 4)) {
+		if (_check_u(v[2], 23) || _check_u(v[3], 18))
+			return -EINVAL;
+		a[3] = max(a[3], TMC_D1_MIN); /* D1 (see DS), can't be 0 */
+		v[3] = max(v[3], TMC_VSTOP_MIN); /* VSTOP (see DS), can't be 0 */
+		return 0;
+
+	}
+
+	/* S-curve (8-point) profile */
+	if ((p->na == 6) && (p->nv == 5)) {
+		if (_check_u(v[2], 20) || _check_u(v[3], 23) || _check_u(v[4], 18))
+			return -EINVAL;
+		if (_check_u(a[4], 18) || _check_u(a[5], 18))
+			return -EINVAL;
+		a[4] = max(a[4], TMC_D2_MIN); /* D2 (see DS), can't be 0 */
+		a[5] = max(a[5], TMC_D1_MIN); /* D1 (see DS), can't be 0 */
+		v[4] = max(v[4], TMC_VSTOP_MIN); /* VSTOP (see DS), can't be 0 */
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static inline u32 tmc5240_time2tv(struct tmc5240 *tmc, mot_time_t t)
+{
+	/* TMC5240 time values are in 512 clock cycle units */
+	return (u32)div_u64((t >> 9) * tmc->clk_rate, NSEC_PER_SEC);
+}
+
+static int tmc5240_set_profile(struct motion_device *mdev, struct mot_profile *p)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+	speed_raw_t *v = &p->vel[0];
+	accel_raw_t *a = &p->acc[0];
+	int ret;
+
+	ret = tmc5240_write(tmc, TMC_REG_TVMAX,
+			tmc5240_time2tv(tmc, p->tvmax));
+	if (ret)
+		return ret;
+	ret = tmc5240_write(tmc, TMC_REG_TZEROWAIT,
+			tmc5240_time2tv(tmc, p->tvzero));
+	if (ret)
+		return ret;
+
+	/* Trapezoidal profile */
+	if ((p->na == 2) && (p->nv == 3)) {
+		ret = tmc5240_ramp_set_vel(tmc, v[0], 0, 0, v[1], v[2]);
+		if (ret)
+			return ret;
+		return tmc5240_ramp_set_acc(tmc, 0, 0, a[0], a[1], 0, 0);
+	}
+
+	/* Dual slope profile */
+	if ((p->na == 4) && (p->nv == 4)) {
+		ret = tmc5240_ramp_set_vel(tmc, v[0], v[1], 0, v[2], v[3]);
+		if (ret)
+			return ret;
+		return tmc5240_ramp_set_acc(tmc, a[0], 0, a[1], a[2], 0, a[3]);
+	}
+
+	/* S-curve (8-point) profile */
+	if ((p->na == 6) && (p->nv == 5)) {
+		tmc5240_ramp_set_vel(tmc, v[0], v[1], v[2], v[3], v[4]);
+		if (ret)
+			return ret;
+		return tmc5240_ramp_set_acc(tmc, a[0], a[1], a[2], a[3], a[4],
+				a[5]);
+	}
+
+	/* We should never get here, since the validate handler
+	 * is supposed to have been called on this profile before.
+	 */
+	WARN_ONCE(1, "motion profile should be valid, but isn't.");
+
+	return -EINVAL;
+}
+
+static int tmc5240_motion_with_index(struct motion_device *mdev,
+		unsigned int index, s32 d)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+	int ret;
+
+	if (tmc->current_index != index) {
+		ret = tmc5240_set_profile(mdev, &mdev->profiles[index]);
+		if (ret)
+			return ret;
+	}
+	tmc->current_index = index;
+
+	return tmc5240_set_distance(tmc, d);
+}
+
+static int tmc5240_motion_distance(struct motion_device *mdev,
+		unsigned int channel, unsigned int index, s32 d, unsigned int when)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+	int ret = 0;
+
+	switch (when) {
+	case MOT_WHEN_NEXT:
+		if (tmc->next_dist) {
+			ret = -EAGAIN;
+		} else {
+			tmc->next_dist = d;
+			tmc->next_index = index;
+		}
+		break;
+	case MOT_WHEN_EXT_TRIGGER:
+		if (tmc->ext_trig_dist) {
+			ret = -EAGAIN;
+		} else {
+			tmc->ext_trig_dist = d;
+			tmc->ext_trg_index = index;
+		}
+		break;
+	case MOT_WHEN_INT_TRIGGER:
+		if (tmc->int_trig_dist) {
+			ret = -EAGAIN;
+		} else {
+			tmc->int_trig_dist = d;
+			tmc->int_trg_index = index;
+		}
+		break;
+	case MOT_WHEN_IMMEDIATE:
+		tmc5240_motion_with_index(mdev, index, d);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int tmc5240_motion_stop(struct motion_device *mdev, channel_mask_t chmsk,
+		unsigned int when)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+
+	if (when != MOT_WHEN_IMMEDIATE)
+		return -EINVAL;
+	/*
+	 * See DS page 55 "Early Ramp Termination".
+	 * The correct way to stop using all *decelaration* parameters is
+	 * weirdly complex and not practial. Best thing we can do here, is to
+	 * use the second closest option which uses AMAX, A1 and A2:
+	 */
+	tmc->current_index = -1; /* Invalidate current index */
+	tmc5240_write(tmc, TMC_REG_VSTART, 0);
+
+	return tmc5240_write(tmc, TMC_REG_VMAX, 0);
+
+}
+
+static int tmc5240_config_trigger(struct motion_device *mdev, unsigned int index,
+		unsigned int func, unsigned int edge, channel_mask_t ch)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+	u32 val;
+	int idxbit;
+	int ret;
+
+	ret = tmc5240_read(tmc, TMC_REG_SW_MODE, &val);
+	if (ret)
+		return ret;
+
+	switch (index) {
+	case TMC_STOP_REFL:
+		idxbit = TMC_SW_STOP_L_ENABLE;
+		if (edge == MOT_EDGE_FALLING)
+			val |= TMC_SW_STOP_L_POL;
+		else
+			val &= ~TMC_SW_STOP_L_POL;
+		break;
+	case TMC_STOP_REFR:
+		idxbit = TMC_SW_STOP_R_ENABLE;
+		if (edge == MOT_EDGE_FALLING)
+			val |= TMC_SW_STOP_R_POL;
+		else
+			val &= ~TMC_SW_STOP_R_POL;
+		break;
+	case TMC_STOP_VIRTL:
+		idxbit = TMC_SW_EN_VIRT_STOP_L;
+		break;
+	case TMC_STOP_VIRTR:
+		idxbit = TMC_SW_EN_VIRT_STOP_R;
+		break;
+	default:
+		/* Should never occur */
+		return -EINVAL;
+	}
+
+	/* Store stop function for interrupt handler */
+	tmc->stop_functions[index] = func;
+
+	switch (func) {
+	case MOT_INP_FUNC_NONE:
+		val &= ~idxbit;
+		break;
+	case MOT_INP_FUNC_DECEL:
+	case MOT_INP_FUNC_DECEL_NEG:
+	case MOT_INP_FUNC_DECEL_POS:
+		val |= TMC_SW_EN_SOFTSTOP;
+		val |= idxbit;
+		break;
+	case MOT_INP_FUNC_STOP:
+	case MOT_INP_FUNC_STOP_NEG:
+	case MOT_INP_FUNC_STOP_POS:
+		val &= ~TMC_SW_EN_SOFTSTOP;
+		val |= idxbit;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return tmc5240_write(tmc, TMC_REG_SW_MODE, val);
+}
+
+static void tmc5240_ext_trigger(struct motion_device *mdev, unsigned int index,
+			channel_mask_t ch)
+{
+	struct tmc5240 *tmc = container_of(mdev, struct tmc5240, mdev);
+
+	if (tmc->ext_trig_dist) {
+		tmc5240_motion_with_index(mdev, tmc->ext_trg_index,
+				tmc->ext_trig_dist);
+		tmc->ext_trig_dist = 0;
+	}
+}
+
+static const struct motion_ops tmc5240_motion_ops = {
+	.device_open = tmc5240_open,
+	.device_release = tmc5240_release,
+	.get_status = tmc5240_get_status,
+	.basic_run = tmc5240_basic_run,
+	.basic_stop = tmc5240_basic_stop,
+	.move_timed = tmc5240_move_timed,
+	.move_distance = tmc5240_move_distance,
+	.validate_profile = tmc5240_validate_profile,
+	.set_profile = tmc5240_set_profile,
+	.motion_distance = tmc5240_motion_distance,
+	.motion_stop = tmc5240_motion_stop,
+	.config_trigger = tmc5240_config_trigger,
+	.external_trigger = tmc5240_ext_trigger
+};
+
+static const struct regmap_range tmc_readable_reg_ranges[] = {
+	regmap_reg_range(TMC_REG_GCONF, TMC_REG_X_COMPARE_REP),
+	regmap_reg_range(TMC_REG_DRV_CONF, TMC_REG_GLOBAL_SCALER),
+	regmap_reg_range(TMC_REG_IHOLD_IRUN, TMC_REG_THIGH),
+	regmap_reg_range(TMC_REG_RAMPMODE, TMC_REG_D2),
+	regmap_reg_range(TMC_REG_VDCMIN, TMC_REG_XLATCH),
+	regmap_reg_range(TMC_REG_ENCMODE, TMC_REG_VIRTUAL_STOP_R),
+	regmap_reg_range(TMC_REG_ADC_VSUPLLY_AIN, TMC_REG_OTW_OV_VTH),
+	regmap_reg_range(TMC_REG_MSLUT(0), TMC_REG_PWM_AUTO),
+	regmap_reg_range(TMC_REG_SG4_THRS, TMC_REG_SG4_IND),
+};
+
+static const struct regmap_access_table tmc_regmap_readable_regs = {
+	.yes_ranges = tmc_readable_reg_ranges,
+	.n_yes_ranges = ARRAY_SIZE(tmc_readable_reg_ranges),
+};
+
+static const struct regmap_range tmc_readonly_reg_ranges[] = {
+	regmap_reg_range(TMC_REG_IFCNT, TMC_REG_IFCNT),
+	regmap_reg_range(TMC_REG_IOIN, TMC_REG_IOIN),
+	regmap_reg_range(TMC_REG_TSTEP, TMC_REG_TSTEP),
+	regmap_reg_range(TMC_REG_VACTUAL, TMC_REG_VACTUAL),
+	regmap_reg_range(TMC_REG_XLATCH, TMC_REG_XLATCH),
+	regmap_reg_range(TMC_REG_ENC_LATCH, TMC_REG_ENC_LATCH),
+	regmap_reg_range(TMC_REG_ADC_VSUPLLY_AIN, TMC_REG_ADC_TEMP),
+	regmap_reg_range(TMC_REG_MSCNT, TMC_REG_MSCURACT),
+	regmap_reg_range(TMC_REG_DRV_STATUS, TMC_REG_DRV_STATUS),
+	regmap_reg_range(TMC_REG_PWM_SCALE, TMC_REG_PWM_AUTO),
+	regmap_reg_range(TMC_REG_SG4_RESULT, TMC_REG_SG4_IND),
+};
+
+static const struct regmap_access_table tmc_regmap_writeable_regs = {
+	.no_ranges = tmc_readonly_reg_ranges,
+	.n_no_ranges = ARRAY_SIZE(tmc_readonly_reg_ranges),
+};
+
+static const struct regmap_range tmc_non_volatile_reg_ranges[] = {
+	regmap_reg_range(TMC_REG_GCONF, TMC_REG_GCONF),
+	regmap_reg_range(TMC_REG_NODECONF, TMC_REG_NODECONF),
+	regmap_reg_range(TMC_REG_X_COMPARE, TMC_REG_TPOWERDOWN),
+	regmap_reg_range(TMC_REG_TPWMTHRS, TMC_REG_RAMPMODE),
+	regmap_reg_range(TMC_REG_VSTART, TMC_REG_SW_MODE),
+	regmap_reg_range(TMC_REG_ENCMODE, TMC_REG_ENCMODE),
+	regmap_reg_range(TMC_REG_ENC_CONST, TMC_REG_ENC_CONST),
+	regmap_reg_range(TMC_REG_ENC_DEVIATION, TMC_REG_VIRTUAL_STOP_R),
+	regmap_reg_range(TMC_REG_OTW_OV_VTH, TMC_REG_MSLUTSTART),
+	regmap_reg_range(TMC_REG_CHOPCONF, TMC_REG_DCCTRL),
+	regmap_reg_range(TMC_REG_PWMCONF, TMC_REG_PWMCONF),
+	regmap_reg_range(TMC_REG_SG4_THRS, TMC_REG_SG4_THRS),
+};
+
+static const struct regmap_access_table tmc_regmap_volatile_regs = {
+	.no_ranges = tmc_non_volatile_reg_ranges,
+	.n_no_ranges = ARRAY_SIZE(tmc_non_volatile_reg_ranges),
+};
+
+static const struct regmap_config mapcfg = {
+	.reg_bits = 8,
+	.val_bits = 32,
+	.max_register = TMC_REG_SG4_IND,
+	.write_flag_mask = BIT(7),
+	.reg_read = tmc5240_read_reg,
+	.reg_write = tmc5240_write_reg,
+	.use_single_read = true,
+	.use_single_write = true,
+	.rd_table = &tmc_regmap_readable_regs,
+	.wr_table = &tmc_regmap_writeable_regs,
+	.volatile_table = &tmc_regmap_volatile_regs,
+	// .cache_type = REGCACHE_MAPLE,
+	.cache_type = REGCACHE_NONE,
+};
+
+static int tmc5240_probe(struct spi_device *spi)
+{
+	struct tmc5240 *tmc;
+	struct device *dev = &spi->dev;
+	unsigned long irq_flags;
+	int err;
+	u32 val, ver, rev;
+
+	/* FIXME: Limited functionality should be possible without IRQ */
+	if (spi->irq <= 0) {
+		dev_dbg(dev, "no IRQ?\n");
+		return -EINVAL;
+	}
+
+	spi->bits_per_word = 8;
+	spi->mode &= ~SPI_MODE_X_MASK;
+	spi->mode |= SPI_MODE_3;
+	err = spi_setup(spi);
+	if (err < 0)
+		return err;
+
+	tmc = devm_kzalloc(dev, sizeof(struct tmc5240), GFP_KERNEL);
+	if (!tmc)
+		return -ENOMEM;
+
+	tmc->mdev.parent = dev;
+	tmc->mdev.ops = tmc5240_motion_ops;
+	tmc->mdev.groups[0] = &tmc5240_group;
+	tmc->mdev.capabilities.features = MOT_FEATURE_SPEED |
+		MOT_FEATURE_ACCEL | MOT_FEATURE_ENCODER | MOT_FEATURE_PROFILE;
+	tmc->mdev.capabilities.num_channels = 1;
+	tmc->mdev.capabilities.max_apoints = 6;
+	tmc->mdev.capabilities.max_vpoints = 5;
+	tmc->mdev.capabilities.num_int_triggers = TMC_MAX_INTERNAL_STOPS;
+	tmc->mdev.capabilities.num_ext_triggers = 0; /* Filled in by core */
+	tmc->mdev.capabilities.type = MOT_TYPE_STEPPER;
+	tmc->mdev.capabilities.subdiv = 256;
+
+	tmc->current_index = -1;
+
+	spi_set_drvdata(spi, tmc);
+	tmc->spi = spi;
+
+	tmc->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(tmc->clk)) {
+		err = PTR_ERR(tmc->clk);
+		dev_err(dev, "clk get failed: %d\n", err);
+		return err;
+	}
+
+	err = clk_prepare_enable(tmc->clk);
+	if (err) {
+		dev_err(dev, "clk enable failed: %d\n", err);
+		return err;
+	}
+	tmc->clk_rate = clk_get_rate(tmc->clk);
+	if (!tmc->clk_rate) {
+		dev_err(dev, "clk rate = 0\n");
+		err = -EINVAL;
+		goto err_clk_disable;
+	}
+
+	/*
+	 * See datasheet table 19:
+	 * v[Hz] = v[TMC5240] * ( fCLK[Hz] / 2^24 )
+	 * a[Hz/s] = a[TMC5240] * ( fCLK[Hz]^2 / 2^42 )
+	 * Since for acceleration, numbers are squared and too big for u32,
+	 * we need to pre-scale both operands by the same amount.
+	 * Clock frequencies are almost always whole kHz values, so we can
+	 * divide by 1000 without losing precision. The divider would then
+	 * become (1ULL<<42)/1000000, which is (rounded) 4398047.
+	 */
+	tmc->mdev.capabilities.speed_conv_mul = tmc->clk_rate;
+	tmc->mdev.capabilities.speed_conv_div = (1UL << 24);
+	tmc->mdev.capabilities.accel_conv_mul = (tmc->clk_rate / 1000); /* Pre-scale */
+	tmc->mdev.capabilities.accel_conv_mul *=
+		tmc->mdev.capabilities.accel_conv_mul; /* squared */
+	tmc->mdev.capabilities.accel_conv_div = 4398047; /* (1 << 42) / 1000000 */
+
+	tmc->map = devm_regmap_init(&spi->dev, NULL, tmc, &mapcfg);
+	if (IS_ERR(tmc->map)) {
+		err = PTR_ERR(tmc->map);
+		goto err_clk_disable;
+	}
+
+	tmc5240_prepare_message(tmc);
+	err = tmc5240_read(tmc, TMC_REG_IOIN, &val);
+	if (err)
+		goto err_clk_disable;
+
+	ver = FIELD_GET(TMC_IOIN_VERSION_MASK, val);
+
+	/* Sanity check. Currently 0x40 and 0x41 are known valid versions */
+	if ((ver < 0x40) || (ver > 0x5f)) {
+		dev_err(dev, "TMC5240 version number invalid (%02x)\n", ver);
+		err = -EINVAL;
+		goto err_clk_disable;
+	}
+
+	rev = FIELD_GET(TMC_IOIN_REVISION_MASK, val);
+	dev_info(dev, "TMC5240 version %02x, rev %d, clock %dHz detected.\n",
+			ver, rev, tmc->clk_rate);
+
+	irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+
+	err = devm_request_threaded_irq(dev, spi->irq,
+					tmc5240_hard_irq, tmc5240_irq,
+					irq_flags, dev->driver->name, tmc);
+	if (err)
+		goto err_clk_disable;
+
+	return motion_register_device(&tmc->mdev);
+err_clk_disable:
+	clk_disable_unprepare(tmc->clk);
+
+	return err;
+}
+
+static void tmc5240_remove(struct spi_device *spi)
+{
+	struct tmc5240 *tmc = spi_get_drvdata(spi);
+
+	clk_disable_unprepare(tmc->clk);
+}
+
+static const struct of_device_id tmc5240_of_match[] = {
+	{ .compatible = "adi,tmc5240" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, tmc5240_of_match);
+
+static struct spi_driver tmc5240_driver = {
+	.driver = {
+		.name = "tmc5240",
+		.of_match_table = tmc5240_of_match,
+	},
+	.probe = tmc5240_probe,
+	.remove = tmc5240_remove,
+};
+module_spi_driver(tmc5240_driver);
+
+MODULE_AUTHOR("David Jander <david@protonic.nl>");
+MODULE_DESCRIPTION("Analog Devices TMC5240 stepper motor controller driver");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/motion.h b/include/uapi/linux/motion.h
index 72a7e564114d..64cea65cd7f4 100644
--- a/include/uapi/linux/motion.h
+++ b/include/uapi/linux/motion.h
@@ -128,7 +128,7 @@  struct mot_status {
 	__u32 channel;
 	pos_raw_t position;
 	speed_raw_t speed;
-	__u32 reserved;
+	__u32 local_inputs;
 };
 
 struct mot_input {