diff options
Diffstat (limited to 'drivers/iio/trigger')
| -rw-r--r-- | drivers/iio/trigger/Kconfig | 47 | ||||
| -rw-r--r-- | drivers/iio/trigger/Makefile | 7 | ||||
| -rw-r--r-- | drivers/iio/trigger/iio-trig-hrtimer.c | 200 | ||||
| -rw-r--r-- | drivers/iio/trigger/iio-trig-interrupt.c | 28 | ||||
| -rw-r--r-- | drivers/iio/trigger/iio-trig-loop.c | 141 | ||||
| -rw-r--r-- | drivers/iio/trigger/iio-trig-sysfs.c | 68 | ||||
| -rw-r--r-- | drivers/iio/trigger/stm32-lptimer-trigger.c | 153 | ||||
| -rw-r--r-- | drivers/iio/trigger/stm32-timer-trigger.c | 942 |
8 files changed, 1530 insertions, 56 deletions
diff --git a/drivers/iio/trigger/Kconfig b/drivers/iio/trigger/Kconfig index 360fd508b088..7ecb69725b1d 100644 --- a/drivers/iio/trigger/Kconfig +++ b/drivers/iio/trigger/Kconfig @@ -1,8 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only # # Industrial I/O standalone triggers # +# When adding new entries keep the list in alphabetical order + menu "Triggers - standalone" +config IIO_HRTIMER_TRIGGER + tristate "High resolution timer trigger" + depends on IIO_SW_TRIGGER + help + Provides a frequency based IIO trigger using high resolution + timers as interrupt source. + + To compile this driver as a module, choose M here: the + module will be called iio-trig-hrtimer. + config IIO_INTERRUPT_TRIGGER tristate "Generic interrupt trigger" help @@ -12,12 +25,44 @@ config IIO_INTERRUPT_TRIGGER To compile this driver as a module, choose M here: the module will be called iio-trig-interrupt. +config IIO_STM32_LPTIMER_TRIGGER + tristate "STM32 Low-Power Timer Trigger" + depends on MFD_STM32_LPTIMER || COMPILE_TEST + help + Select this option to enable STM32 Low-Power Timer Trigger. + This can be used as trigger source for STM32 internal ADC + and/or DAC. + + To compile this driver as a module, choose M here: the + module will be called stm32-lptimer-trigger. + +config IIO_STM32_TIMER_TRIGGER + tristate "STM32 Timer Trigger" + depends on (ARCH_STM32 && MFD_STM32_TIMERS) || COMPILE_TEST + help + Select this option to enable STM32 Timer Trigger + + To compile this driver as a module, choose M here: the + module will be called stm32-timer-trigger. + +config IIO_TIGHTLOOP_TRIGGER + tristate "A kthread based hammering loop trigger" + depends on IIO_SW_TRIGGER + help + An experimental trigger, used to allow sensors to be sampled as fast + as possible under the limitations of whatever else is going on. + Uses a tight loop in a kthread. Will only work with lower half only + trigger consumers. + + To compile this driver as a module, choose M here: the + module will be called iio-trig-loop. + config IIO_SYSFS_TRIGGER tristate "SYSFS trigger" depends on SYSFS select IRQ_WORK help - Provides support for using SYSFS entry as IIO triggers. + Provides support for using SYSFS entries as IIO triggers. If unsure, say N (but it's safe to say "Y"). To compile this driver as a module, choose M here: the diff --git a/drivers/iio/trigger/Makefile b/drivers/iio/trigger/Makefile index ce319a51b6af..f3d11acb8a0b 100644 --- a/drivers/iio/trigger/Makefile +++ b/drivers/iio/trigger/Makefile @@ -1,6 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 # # Makefile for triggers not associated with iio-devices # +# When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_IIO_HRTIMER_TRIGGER) += iio-trig-hrtimer.o obj-$(CONFIG_IIO_INTERRUPT_TRIGGER) += iio-trig-interrupt.o +obj-$(CONFIG_IIO_STM32_LPTIMER_TRIGGER) += stm32-lptimer-trigger.o +obj-$(CONFIG_IIO_STM32_TIMER_TRIGGER) += stm32-timer-trigger.o obj-$(CONFIG_IIO_SYSFS_TRIGGER) += iio-trig-sysfs.o +obj-$(CONFIG_IIO_TIGHTLOOP_TRIGGER) += iio-trig-loop.o diff --git a/drivers/iio/trigger/iio-trig-hrtimer.c b/drivers/iio/trigger/iio-trig-hrtimer.c new file mode 100644 index 000000000000..82c72baccb62 --- /dev/null +++ b/drivers/iio/trigger/iio-trig-hrtimer.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * The industrial I/O periodic hrtimer trigger driver + * + * Copyright (C) Intuitive Aerial AB + * Written by Marten Svanfeldt, marten@intuitiveaerial.com + * Copyright (C) 2012, Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * Copyright (C) 2015, Intel Corporation + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/hrtimer.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/iio/sw_trigger.h> + +/* Defined locally, not in time64.h yet. */ +#define PSEC_PER_SEC 1000000000000LL + +/* default sampling frequency - 100Hz */ +#define HRTIMER_DEFAULT_SAMPLING_FREQUENCY 100 + +struct iio_hrtimer_info { + struct iio_sw_trigger swt; + struct hrtimer timer; + int sampling_frequency[2]; + ktime_t period; +}; + +static const struct config_item_type iio_hrtimer_type = { + .ct_owner = THIS_MODULE, +}; + +static +ssize_t iio_hrtimer_show_sampling_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_hrtimer_info *info = iio_trigger_get_drvdata(trig); + + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, + ARRAY_SIZE(info->sampling_frequency), + info->sampling_frequency); +} + +static +ssize_t iio_hrtimer_store_sampling_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_hrtimer_info *info = iio_trigger_get_drvdata(trig); + unsigned long long val; + u64 period; + int integer, fract, ret; + + ret = iio_str_to_fixpoint(buf, 100, &integer, &fract); + if (ret) + return ret; + if (integer < 0 || fract < 0) + return -ERANGE; + + val = fract + 1000ULL * integer; /* mHz */ + + if (!val || val > UINT_MAX) + return -EINVAL; + + info->sampling_frequency[0] = integer; /* Hz */ + info->sampling_frequency[1] = fract * 1000; /* uHz */ + period = PSEC_PER_SEC; + do_div(period, val); + info->period = period; /* nS */ + + return len; +} + +static DEVICE_ATTR(sampling_frequency, S_IRUGO | S_IWUSR, + iio_hrtimer_show_sampling_frequency, + iio_hrtimer_store_sampling_frequency); + +static struct attribute *iio_hrtimer_attrs[] = { + &dev_attr_sampling_frequency.attr, + NULL +}; + +static const struct attribute_group iio_hrtimer_attr_group = { + .attrs = iio_hrtimer_attrs, +}; + +static const struct attribute_group *iio_hrtimer_attr_groups[] = { + &iio_hrtimer_attr_group, + NULL +}; + +static enum hrtimer_restart iio_hrtimer_trig_handler(struct hrtimer *timer) +{ + struct iio_hrtimer_info *info; + + info = container_of(timer, struct iio_hrtimer_info, timer); + + hrtimer_forward_now(timer, info->period); + iio_trigger_poll(info->swt.trigger); + + return HRTIMER_RESTART; +} + +static int iio_trig_hrtimer_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_hrtimer_info *trig_info; + + trig_info = iio_trigger_get_drvdata(trig); + + if (state) + hrtimer_start(&trig_info->timer, trig_info->period, + HRTIMER_MODE_REL_HARD); + else + hrtimer_cancel(&trig_info->timer); + + return 0; +} + +static const struct iio_trigger_ops iio_hrtimer_trigger_ops = { + .set_trigger_state = iio_trig_hrtimer_set_state, +}; + +static struct iio_sw_trigger *iio_trig_hrtimer_probe(const char *name) +{ + struct iio_hrtimer_info *trig_info; + int ret; + + trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); + if (!trig_info) + return ERR_PTR(-ENOMEM); + + trig_info->swt.trigger = iio_trigger_alloc(NULL, "%s", name); + if (!trig_info->swt.trigger) { + ret = -ENOMEM; + goto err_free_trig_info; + } + + iio_trigger_set_drvdata(trig_info->swt.trigger, trig_info); + trig_info->swt.trigger->ops = &iio_hrtimer_trigger_ops; + trig_info->swt.trigger->dev.groups = iio_hrtimer_attr_groups; + + hrtimer_setup(&trig_info->timer, iio_hrtimer_trig_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL_HARD); + + trig_info->sampling_frequency[0] = HRTIMER_DEFAULT_SAMPLING_FREQUENCY; + trig_info->period = NSEC_PER_SEC / trig_info->sampling_frequency[0]; + + ret = iio_trigger_register(trig_info->swt.trigger); + if (ret) + goto err_free_trigger; + + iio_swt_group_init_type_name(&trig_info->swt, name, &iio_hrtimer_type); + return &trig_info->swt; +err_free_trigger: + iio_trigger_free(trig_info->swt.trigger); +err_free_trig_info: + kfree(trig_info); + + return ERR_PTR(ret); +} + +static int iio_trig_hrtimer_remove(struct iio_sw_trigger *swt) +{ + struct iio_hrtimer_info *trig_info; + + trig_info = iio_trigger_get_drvdata(swt->trigger); + + iio_trigger_unregister(swt->trigger); + + /* cancel the timer after unreg to make sure no one rearms it */ + hrtimer_cancel(&trig_info->timer); + iio_trigger_free(swt->trigger); + kfree(trig_info); + + return 0; +} + +static const struct iio_sw_trigger_ops iio_trig_hrtimer_ops = { + .probe = iio_trig_hrtimer_probe, + .remove = iio_trig_hrtimer_remove, +}; + +static struct iio_sw_trigger_type iio_trig_hrtimer = { + .name = "hrtimer", + .owner = THIS_MODULE, + .ops = &iio_trig_hrtimer_ops, +}; + +module_iio_sw_trigger_driver(iio_trig_hrtimer); + +MODULE_AUTHOR("Marten Svanfeldt <marten@intuitiveaerial.com>"); +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>"); +MODULE_DESCRIPTION("Periodic hrtimer trigger for the IIO subsystem"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/trigger/iio-trig-interrupt.c b/drivers/iio/trigger/iio-trig-interrupt.c index 02577ec54c6b..21c6b6292a72 100644 --- a/drivers/iio/trigger/iio-trig-interrupt.c +++ b/drivers/iio/trigger/iio-trig-interrupt.c @@ -1,11 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Industrial I/O - generic interrupt based trigger support * * Copyright (c) 2008-2013 Jonathan Cameron - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published by - * the Free Software Foundation. */ #include <linux/kernel.h> @@ -24,15 +21,10 @@ struct iio_interrupt_trigger_info { static irqreturn_t iio_interrupt_trigger_poll(int irq, void *private) { - /* Timestamp not currently provided */ - iio_trigger_poll(private, 0); + iio_trigger_poll(private); return IRQ_HANDLED; } -static const struct iio_trigger_ops iio_interrupt_trigger_ops = { - .owner = THIS_MODULE, -}; - static int iio_interrupt_trigger_probe(struct platform_device *pdev) { struct iio_interrupt_trigger_info *trig_info; @@ -50,7 +42,7 @@ static int iio_interrupt_trigger_probe(struct platform_device *pdev) irq = irq_res->start; - trig = iio_trigger_alloc("irqtrig%d", irq); + trig = iio_trigger_alloc(NULL, "irqtrig%d", irq); if (!trig) { ret = -ENOMEM; goto error_ret; @@ -59,11 +51,10 @@ static int iio_interrupt_trigger_probe(struct platform_device *pdev) trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); if (!trig_info) { ret = -ENOMEM; - goto error_put_trigger; + goto error_free_trigger; } iio_trigger_set_drvdata(trig, trig_info); trig_info->irq = irq; - trig->ops = &iio_interrupt_trigger_ops; ret = request_irq(irq, iio_interrupt_trigger_poll, irqflags, trig->name, trig); if (ret) { @@ -84,13 +75,13 @@ error_release_irq: free_irq(irq, trig); error_free_trig_info: kfree(trig_info); -error_put_trigger: - iio_trigger_put(trig); +error_free_trigger: + iio_trigger_free(trig); error_ret: return ret; } -static int iio_interrupt_trigger_remove(struct platform_device *pdev) +static void iio_interrupt_trigger_remove(struct platform_device *pdev) { struct iio_trigger *trig; struct iio_interrupt_trigger_info *trig_info; @@ -100,9 +91,7 @@ static int iio_interrupt_trigger_remove(struct platform_device *pdev) iio_trigger_unregister(trig); free_irq(trig_info->irq, trig); kfree(trig_info); - iio_trigger_put(trig); - - return 0; + iio_trigger_free(trig); } static struct platform_driver iio_interrupt_trigger_driver = { @@ -110,7 +99,6 @@ static struct platform_driver iio_interrupt_trigger_driver = { .remove = iio_interrupt_trigger_remove, .driver = { .name = "iio_interrupt_trigger", - .owner = THIS_MODULE, }, }; diff --git a/drivers/iio/trigger/iio-trig-loop.c b/drivers/iio/trigger/iio-trig-loop.c new file mode 100644 index 000000000000..7aaed0611899 --- /dev/null +++ b/drivers/iio/trigger/iio-trig-loop.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2016 Jonathan Cameron <jic23@kernel.org> + * + * Based on a mashup of the hrtimer trigger and continuous sampling proposal of + * Gregor Boirie <gregor.boirie@parrot.com> + * + * Note this is still rather experimental and may eat babies. + * + * Todo + * * Protect against connection of devices that 'need' the top half + * handler. + * * Work out how to run top half handlers in this context if it is + * safe to do so (timestamp grabbing for example) + * + * Tested against a max1363. Used about 33% cpu for the thread and 20% + * for generic_buffer piping to /dev/null. Watermark set at 64 on a 128 + * element kfifo buffer. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/irq_work.h> +#include <linux/kthread.h> +#include <linux/freezer.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/iio/sw_trigger.h> + +struct iio_loop_info { + struct iio_sw_trigger swt; + struct task_struct *task; +}; + +static const struct config_item_type iio_loop_type = { + .ct_owner = THIS_MODULE, +}; + +static int iio_loop_thread(void *data) +{ + struct iio_trigger *trig = data; + + set_freezable(); + + do { + iio_trigger_poll_nested(trig); + } while (likely(!kthread_freezable_should_stop(NULL))); + + return 0; +} + +static int iio_loop_trigger_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_loop_info *loop_trig = iio_trigger_get_drvdata(trig); + + if (state) { + loop_trig->task = kthread_run(iio_loop_thread, + trig, trig->name); + if (IS_ERR(loop_trig->task)) { + dev_err(&trig->dev, + "failed to create trigger loop thread\n"); + return PTR_ERR(loop_trig->task); + } + } else { + kthread_stop(loop_trig->task); + } + + return 0; +} + +static const struct iio_trigger_ops iio_loop_trigger_ops = { + .set_trigger_state = iio_loop_trigger_set_state, +}; + +static struct iio_sw_trigger *iio_trig_loop_probe(const char *name) +{ + struct iio_loop_info *trig_info; + int ret; + + trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); + if (!trig_info) + return ERR_PTR(-ENOMEM); + + trig_info->swt.trigger = iio_trigger_alloc(NULL, "%s", name); + if (!trig_info->swt.trigger) { + ret = -ENOMEM; + goto err_free_trig_info; + } + + iio_trigger_set_drvdata(trig_info->swt.trigger, trig_info); + trig_info->swt.trigger->ops = &iio_loop_trigger_ops; + + ret = iio_trigger_register(trig_info->swt.trigger); + if (ret) + goto err_free_trigger; + + iio_swt_group_init_type_name(&trig_info->swt, name, &iio_loop_type); + + return &trig_info->swt; + +err_free_trigger: + iio_trigger_free(trig_info->swt.trigger); +err_free_trig_info: + kfree(trig_info); + + return ERR_PTR(ret); +} + +static int iio_trig_loop_remove(struct iio_sw_trigger *swt) +{ + struct iio_loop_info *trig_info; + + trig_info = iio_trigger_get_drvdata(swt->trigger); + + iio_trigger_unregister(swt->trigger); + iio_trigger_free(swt->trigger); + kfree(trig_info); + + return 0; +} + +static const struct iio_sw_trigger_ops iio_trig_loop_ops = { + .probe = iio_trig_loop_probe, + .remove = iio_trig_loop_remove, +}; + +static struct iio_sw_trigger_type iio_trig_loop = { + .name = "loop", + .owner = THIS_MODULE, + .ops = &iio_trig_loop_ops, +}; + +module_iio_sw_trigger_driver(iio_trig_loop); + +MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>"); +MODULE_DESCRIPTION("Loop based trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:iio-trig-loop"); diff --git a/drivers/iio/trigger/iio-trig-sysfs.c b/drivers/iio/trigger/iio-trig-sysfs.c index effcd0ac98d8..575d725696a9 100644 --- a/drivers/iio/trigger/iio-trig-sysfs.c +++ b/drivers/iio/trigger/iio-trig-sysfs.c @@ -1,8 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright 2011 Analog Devices Inc. - * - * Licensed under the GPL-2. - * */ #include <linux/kernel.h> @@ -23,7 +21,7 @@ struct iio_sysfs_trig { }; static LIST_HEAD(iio_sysfs_trig_list); -static DEFINE_MUTEX(iio_syfs_trig_list_mut); +static DEFINE_MUTEX(iio_sysfs_trig_list_mut); static int iio_sysfs_trigger_probe(int id); static ssize_t iio_sysfs_trig_add(struct device *dev, @@ -96,7 +94,7 @@ static void iio_sysfs_trigger_work(struct irq_work *work) struct iio_sysfs_trig *trig = container_of(work, struct iio_sysfs_trig, work); - iio_trigger_poll(trig->trig, 0); + iio_trigger_poll(trig->trig); } static ssize_t iio_sysfs_trigger_poll(struct device *dev, @@ -126,16 +124,13 @@ static const struct attribute_group *iio_sysfs_trigger_attr_groups[] = { NULL }; -static const struct iio_trigger_ops iio_sysfs_trigger_ops = { - .owner = THIS_MODULE, -}; - static int iio_sysfs_trigger_probe(int id) { struct iio_sysfs_trig *t; int ret; bool foundit = false; - mutex_lock(&iio_syfs_trig_list_mut); + + mutex_lock(&iio_sysfs_trig_list_mut); list_for_each_entry(t, &iio_sysfs_trig_list, l) if (id == t->id) { foundit = true; @@ -143,75 +138,78 @@ static int iio_sysfs_trigger_probe(int id) } if (foundit) { ret = -EINVAL; - goto out1; + goto err_unlock; } t = kmalloc(sizeof(*t), GFP_KERNEL); if (t == NULL) { ret = -ENOMEM; - goto out1; + goto err_unlock; } t->id = id; - t->trig = iio_trigger_alloc("sysfstrig%d", id); + t->trig = iio_trigger_alloc(&iio_sysfs_trig_dev, "sysfstrig%d", id); if (!t->trig) { ret = -ENOMEM; - goto free_t; + goto err_free_sys_trig; } t->trig->dev.groups = iio_sysfs_trigger_attr_groups; - t->trig->ops = &iio_sysfs_trigger_ops; - t->trig->dev.parent = &iio_sysfs_trig_dev; iio_trigger_set_drvdata(t->trig, t); - init_irq_work(&t->work, iio_sysfs_trigger_work); + t->work = IRQ_WORK_INIT_HARD(iio_sysfs_trigger_work); ret = iio_trigger_register(t->trig); if (ret) - goto out2; + goto err_free_trig; list_add(&t->l, &iio_sysfs_trig_list); __module_get(THIS_MODULE); - mutex_unlock(&iio_syfs_trig_list_mut); + mutex_unlock(&iio_sysfs_trig_list_mut); return 0; -out2: - iio_trigger_put(t->trig); -free_t: +err_free_trig: + iio_trigger_free(t->trig); +err_free_sys_trig: kfree(t); -out1: - mutex_unlock(&iio_syfs_trig_list_mut); +err_unlock: + mutex_unlock(&iio_sysfs_trig_list_mut); return ret; } static int iio_sysfs_trigger_remove(int id) { - bool foundit = false; - struct iio_sysfs_trig *t; - mutex_lock(&iio_syfs_trig_list_mut); - list_for_each_entry(t, &iio_sysfs_trig_list, l) - if (id == t->id) { - foundit = true; + struct iio_sysfs_trig *t = NULL, *iter; + + mutex_lock(&iio_sysfs_trig_list_mut); + list_for_each_entry(iter, &iio_sysfs_trig_list, l) + if (id == iter->id) { + t = iter; break; } - if (!foundit) { - mutex_unlock(&iio_syfs_trig_list_mut); + if (!t) { + mutex_unlock(&iio_sysfs_trig_list_mut); return -EINVAL; } iio_trigger_unregister(t->trig); + irq_work_sync(&t->work); iio_trigger_free(t->trig); list_del(&t->l); kfree(t); module_put(THIS_MODULE); - mutex_unlock(&iio_syfs_trig_list_mut); + mutex_unlock(&iio_sysfs_trig_list_mut); return 0; } static int __init iio_sysfs_trig_init(void) { + int ret; device_initialize(&iio_sysfs_trig_dev); dev_set_name(&iio_sysfs_trig_dev, "iio_sysfs_trigger"); - return device_add(&iio_sysfs_trig_dev); + ret = device_add(&iio_sysfs_trig_dev); + if (ret) + put_device(&iio_sysfs_trig_dev); + return ret; } module_init(iio_sysfs_trig_init); @@ -221,7 +219,7 @@ static void __exit iio_sysfs_trig_exit(void) } module_exit(iio_sysfs_trig_exit); -MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>"); MODULE_DESCRIPTION("Sysfs based trigger for the iio subsystem"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:iio-trig-sysfs"); diff --git a/drivers/iio/trigger/stm32-lptimer-trigger.c b/drivers/iio/trigger/stm32-lptimer-trigger.c new file mode 100644 index 000000000000..c7bab18221c7 --- /dev/null +++ b/drivers/iio/trigger/stm32-lptimer-trigger.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * STM32 Low-Power Timer Trigger driver + * + * Copyright (C) STMicroelectronics 2017 + * + * Author: Fabrice Gasnier <fabrice.gasnier@st.com>. + * + * Inspired by Benjamin Gaignard's stm32-timer-trigger driver + */ + +#include <linux/export.h> +#include <linux/iio/timer/stm32-lptim-trigger.h> +#include <linux/mfd/stm32-lptimer.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> + +/* Maximum triggers + one trailing null entry to indicate the end of array */ +#define MAX_TRIGGERS 3 + +struct stm32_lptim_cfg { + const char * const (*triggers)[MAX_TRIGGERS]; + unsigned int nb_triggers; +}; + +/* List Low-Power Timer triggers for H7, MP13, MP15 */ +static const char * const stm32_lptim_triggers[][MAX_TRIGGERS] = { + { LPTIM1_OUT,}, + { LPTIM2_OUT,}, + { LPTIM3_OUT,}, +}; + +/* List Low-Power Timer triggers for STM32MP25 */ +static const char * const stm32mp25_lptim_triggers[][MAX_TRIGGERS] = { + { LPTIM1_CH1, LPTIM1_CH2, }, + { LPTIM2_CH1, LPTIM2_CH2, }, + { LPTIM3_CH1,}, + { LPTIM4_CH1,}, + { LPTIM5_OUT,}, +}; + +static const struct stm32_lptim_cfg stm32mp15_lptim_cfg = { + .triggers = stm32_lptim_triggers, + .nb_triggers = ARRAY_SIZE(stm32_lptim_triggers), +}; + +static const struct stm32_lptim_cfg stm32mp25_lptim_cfg = { + .triggers = stm32mp25_lptim_triggers, + .nb_triggers = ARRAY_SIZE(stm32mp25_lptim_triggers), +}; + +struct stm32_lptim_trigger { + struct device *dev; + const char * const *triggers; +}; + +static int stm32_lptim_validate_device(struct iio_trigger *trig, + struct iio_dev *indio_dev) +{ + if (indio_dev->modes & INDIO_HARDWARE_TRIGGERED) + return 0; + + return -EINVAL; +} + +static const struct iio_trigger_ops stm32_lptim_trigger_ops = { + .validate_device = stm32_lptim_validate_device, +}; + +/** + * is_stm32_lptim_trigger + * @trig: trigger to be checked + * + * return true if the trigger is a valid STM32 IIO Low-Power Timer Trigger + * either return false + */ +bool is_stm32_lptim_trigger(struct iio_trigger *trig) +{ + return (trig->ops == &stm32_lptim_trigger_ops); +} +EXPORT_SYMBOL(is_stm32_lptim_trigger); + +static int stm32_lptim_setup_trig(struct stm32_lptim_trigger *priv) +{ + const char * const *cur = priv->triggers; + int ret; + + while (cur && *cur) { + struct iio_trigger *trig; + + trig = devm_iio_trigger_alloc(priv->dev, "%s", *cur); + if (!trig) + return -ENOMEM; + + trig->dev.parent = priv->dev->parent; + trig->ops = &stm32_lptim_trigger_ops; + iio_trigger_set_drvdata(trig, priv); + + ret = devm_iio_trigger_register(priv->dev, trig); + if (ret) + return ret; + cur++; + } + + return 0; +} + +static int stm32_lptim_trigger_probe(struct platform_device *pdev) +{ + struct stm32_lptim_trigger *priv; + struct stm32_lptim_cfg const *lptim_cfg; + u32 index; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (device_property_read_u32(&pdev->dev, "reg", &index)) + return -EINVAL; + + lptim_cfg = device_get_match_data(&pdev->dev); + + if (index >= lptim_cfg->nb_triggers) + return -EINVAL; + + priv->dev = &pdev->dev; + priv->triggers = lptim_cfg->triggers[index]; + + return stm32_lptim_setup_trig(priv); +} + +static const struct of_device_id stm32_lptim_trig_of_match[] = { + { .compatible = "st,stm32-lptimer-trigger", .data = &stm32mp15_lptim_cfg }, + { .compatible = "st,stm32mp25-lptimer-trigger", .data = &stm32mp25_lptim_cfg}, + { } +}; +MODULE_DEVICE_TABLE(of, stm32_lptim_trig_of_match); + +static struct platform_driver stm32_lptim_trigger_driver = { + .probe = stm32_lptim_trigger_probe, + .driver = { + .name = "stm32-lptimer-trigger", + .of_match_table = stm32_lptim_trig_of_match, + }, +}; +module_platform_driver(stm32_lptim_trigger_driver); + +MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>"); +MODULE_ALIAS("platform:stm32-lptimer-trigger"); +MODULE_DESCRIPTION("STMicroelectronics STM32 LPTIM trigger driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/trigger/stm32-timer-trigger.c b/drivers/iio/trigger/stm32-timer-trigger.c new file mode 100644 index 000000000000..3b9a3a6cbb25 --- /dev/null +++ b/drivers/iio/trigger/stm32-timer-trigger.c @@ -0,0 +1,942 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics 2016 + * + * Author: Benjamin Gaignard <benjamin.gaignard@st.com> + * + */ + +#include <linux/export.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/timer/stm32-timer-trigger.h> +#include <linux/iio/trigger.h> +#include <linux/mfd/stm32-timers.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> + +#define MAX_TRIGGERS 7 +#define MAX_VALIDS 5 + +/* List the triggers created by each timer */ +static const void *triggers_table[][MAX_TRIGGERS] = { + { TIM1_TRGO, TIM1_TRGO2, TIM1_CH1, TIM1_CH2, TIM1_CH3, TIM1_CH4,}, + { TIM2_TRGO, TIM2_CH1, TIM2_CH2, TIM2_CH3, TIM2_CH4,}, + { TIM3_TRGO, TIM3_CH1, TIM3_CH2, TIM3_CH3, TIM3_CH4,}, + { TIM4_TRGO, TIM4_CH1, TIM4_CH2, TIM4_CH3, TIM4_CH4,}, + { TIM5_TRGO, TIM5_CH1, TIM5_CH2, TIM5_CH3, TIM5_CH4,}, + { TIM6_TRGO,}, + { TIM7_TRGO,}, + { TIM8_TRGO, TIM8_TRGO2, TIM8_CH1, TIM8_CH2, TIM8_CH3, TIM8_CH4,}, + { TIM9_TRGO, TIM9_CH1, TIM9_CH2,}, + { TIM10_OC1,}, + { TIM11_OC1,}, + { TIM12_TRGO, TIM12_CH1, TIM12_CH2,}, + { TIM13_OC1,}, + { TIM14_OC1,}, + { TIM15_TRGO,}, + { TIM16_OC1,}, + { TIM17_OC1,}, + { }, /* timer 18 */ + { }, /* timer 19 */ + { TIM20_TRGO, TIM20_TRGO2, TIM20_OC1, TIM20_OC2, TIM20_OC3, }, +}; + +/* List the triggers accepted by each timer */ +static const void *valids_table[][MAX_VALIDS] = { + { TIM5_TRGO, TIM2_TRGO, TIM3_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM8_TRGO, TIM3_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM2_TRGO, TIM5_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM2_TRGO, TIM3_TRGO, TIM8_TRGO,}, + { TIM2_TRGO, TIM3_TRGO, TIM4_TRGO, TIM8_TRGO,}, + { }, /* timer 6 */ + { }, /* timer 7 */ + { TIM1_TRGO, TIM2_TRGO, TIM4_TRGO, TIM5_TRGO,}, + { TIM2_TRGO, TIM3_TRGO, TIM10_OC1, TIM11_OC1,}, + { }, /* timer 10 */ + { }, /* timer 11 */ + { TIM4_TRGO, TIM5_TRGO, TIM13_OC1, TIM14_OC1,}, +}; + +static const void *stm32h7_valids_table[][MAX_VALIDS] = { + { TIM15_TRGO, TIM2_TRGO, TIM3_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM8_TRGO, TIM3_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM2_TRGO, TIM15_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM2_TRGO, TIM3_TRGO, TIM8_TRGO,}, + { TIM1_TRGO, TIM8_TRGO, TIM3_TRGO, TIM4_TRGO,}, + { }, /* timer 6 */ + { }, /* timer 7 */ + { TIM1_TRGO, TIM2_TRGO, TIM4_TRGO, TIM5_TRGO,}, + { }, /* timer 9 */ + { }, /* timer 10 */ + { }, /* timer 11 */ + { TIM4_TRGO, TIM5_TRGO, TIM13_OC1, TIM14_OC1,}, + { }, /* timer 13 */ + { }, /* timer 14 */ + { TIM1_TRGO, TIM3_TRGO, TIM16_OC1, TIM17_OC1,}, + { }, /* timer 16 */ + { }, /* timer 17 */ +}; + +struct stm32_timer_trigger_regs { + u32 cr1; + u32 cr2; + u32 psc; + u32 arr; + u32 cnt; + u32 smcr; +}; + +struct stm32_timer_trigger { + struct device *dev; + struct regmap *regmap; + struct clk *clk; + bool enabled; + u32 max_arr; + const void *triggers; + const void *valids; + bool has_trgo2; + struct mutex lock; /* concurrent sysfs configuration */ + struct list_head tr_list; + struct stm32_timer_trigger_regs bak; +}; + +struct stm32_timer_trigger_cfg { + const void *(*valids_table)[MAX_VALIDS]; + const unsigned int num_valids_table; +}; + +static bool stm32_timer_is_trgo2_name(const char *name) +{ + return !!strstr(name, "trgo2"); +} + +static bool stm32_timer_is_trgo_name(const char *name) +{ + return (!!strstr(name, "trgo") && !strstr(name, "trgo2")); +} + +static int stm32_timer_start(struct stm32_timer_trigger *priv, + struct iio_trigger *trig, + unsigned int frequency) +{ + unsigned long long prd, div; + int prescaler = 0, ret; + u32 ccer; + + /* Period and prescaler values depends of clock rate */ + div = (unsigned long long)clk_get_rate(priv->clk); + + do_div(div, frequency); + + prd = div; + + /* + * Increase prescaler value until we get a result that fit + * with auto reload register maximum value. + */ + while (div > priv->max_arr) { + prescaler++; + div = prd; + do_div(div, (prescaler + 1)); + } + prd = div; + + if (prescaler > MAX_TIM_PSC) { + dev_err(priv->dev, "prescaler exceeds the maximum value\n"); + return -EINVAL; + } + + /* Check if nobody else use the timer */ + regmap_read(priv->regmap, TIM_CCER, &ccer); + if (ccer & TIM_CCER_CCXE) + return -EBUSY; + + guard(mutex)(&priv->lock); + if (!priv->enabled) { + priv->enabled = true; + ret = clk_enable(priv->clk); + if (ret) + return ret; + } + + regmap_write(priv->regmap, TIM_PSC, prescaler); + regmap_write(priv->regmap, TIM_ARR, prd - 1); + regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE); + + /* Force master mode to update mode */ + if (stm32_timer_is_trgo2_name(trig->name)) + regmap_update_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS2, + 0x2 << TIM_CR2_MMS2_SHIFT); + else + regmap_update_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS, + 0x2 << TIM_CR2_MMS_SHIFT); + + /* Make sure that registers are updated */ + regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG); + + /* Enable controller */ + regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + + return 0; +} + +static void stm32_timer_stop(struct stm32_timer_trigger *priv, + struct iio_trigger *trig) +{ + u32 ccer; + + regmap_read(priv->regmap, TIM_CCER, &ccer); + if (ccer & TIM_CCER_CCXE) + return; + + mutex_lock(&priv->lock); + /* Stop timer */ + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE); + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + regmap_write(priv->regmap, TIM_PSC, 0); + regmap_write(priv->regmap, TIM_ARR, 0); + + /* Force disable master mode */ + if (stm32_timer_is_trgo2_name(trig->name)) + regmap_clear_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS2); + else + regmap_clear_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS); + + /* Make sure that registers are updated */ + regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG); + + if (priv->enabled) { + priv->enabled = false; + clk_disable(priv->clk); + } + mutex_unlock(&priv->lock); +} + +static ssize_t stm32_tt_store_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct stm32_timer_trigger *priv = iio_trigger_get_drvdata(trig); + unsigned int freq; + int ret; + + ret = kstrtouint(buf, 10, &freq); + if (ret) + return ret; + + if (freq == 0) { + stm32_timer_stop(priv, trig); + } else { + ret = stm32_timer_start(priv, trig, freq); + if (ret) + return ret; + } + + return len; +} + +static ssize_t stm32_tt_read_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct stm32_timer_trigger *priv = iio_trigger_get_drvdata(trig); + u32 psc, arr, cr1; + unsigned long long freq = 0; + + regmap_read(priv->regmap, TIM_CR1, &cr1); + regmap_read(priv->regmap, TIM_PSC, &psc); + regmap_read(priv->regmap, TIM_ARR, &arr); + + if (cr1 & TIM_CR1_CEN) { + freq = (unsigned long long)clk_get_rate(priv->clk); + do_div(freq, psc + 1); + do_div(freq, arr + 1); + } + + return sprintf(buf, "%d\n", (unsigned int)freq); +} + +static IIO_DEV_ATTR_SAMP_FREQ(0660, + stm32_tt_read_frequency, + stm32_tt_store_frequency); + +#define MASTER_MODE_MAX 7 +#define MASTER_MODE2_MAX 15 + +static char *master_mode_table[] = { + "reset", + "enable", + "update", + "compare_pulse", + "OC1REF", + "OC2REF", + "OC3REF", + "OC4REF", + /* Master mode selection 2 only */ + "OC5REF", + "OC6REF", + "compare_pulse_OC4REF", + "compare_pulse_OC6REF", + "compare_pulse_OC4REF_r_or_OC6REF_r", + "compare_pulse_OC4REF_r_or_OC6REF_f", + "compare_pulse_OC5REF_r_or_OC6REF_r", + "compare_pulse_OC5REF_r_or_OC6REF_f", +}; + +static ssize_t stm32_tt_show_master_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct stm32_timer_trigger *priv = dev_get_drvdata(dev); + struct iio_trigger *trig = to_iio_trigger(dev); + u32 cr2; + + regmap_read(priv->regmap, TIM_CR2, &cr2); + + if (stm32_timer_is_trgo2_name(trig->name)) + cr2 = (cr2 & TIM_CR2_MMS2) >> TIM_CR2_MMS2_SHIFT; + else + cr2 = (cr2 & TIM_CR2_MMS) >> TIM_CR2_MMS_SHIFT; + + return sysfs_emit(buf, "%s\n", master_mode_table[cr2]); +} + +static ssize_t stm32_tt_store_master_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct stm32_timer_trigger *priv = dev_get_drvdata(dev); + struct iio_trigger *trig = to_iio_trigger(dev); + u32 mask, shift, master_mode_max; + int i, ret; + + if (stm32_timer_is_trgo2_name(trig->name)) { + mask = TIM_CR2_MMS2; + shift = TIM_CR2_MMS2_SHIFT; + master_mode_max = MASTER_MODE2_MAX; + } else { + mask = TIM_CR2_MMS; + shift = TIM_CR2_MMS_SHIFT; + master_mode_max = MASTER_MODE_MAX; + } + + for (i = 0; i <= master_mode_max; i++) { + if (!strncmp(master_mode_table[i], buf, + strlen(master_mode_table[i]))) { + guard(mutex)(&priv->lock); + if (!priv->enabled) { + /* Clock should be enabled first */ + priv->enabled = true; + ret = clk_enable(priv->clk); + if (ret) + return ret; + } + regmap_update_bits(priv->regmap, TIM_CR2, mask, + i << shift); + return len; + } + } + + return -EINVAL; +} + +static ssize_t stm32_tt_show_master_mode_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + unsigned int i, master_mode_max; + size_t len = 0; + + if (stm32_timer_is_trgo2_name(trig->name)) + master_mode_max = MASTER_MODE2_MAX; + else + master_mode_max = MASTER_MODE_MAX; + + for (i = 0; i <= master_mode_max; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%s ", master_mode_table[i]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEVICE_ATTR(master_mode_available, 0444, + stm32_tt_show_master_mode_avail, NULL, 0); + +static IIO_DEVICE_ATTR(master_mode, 0660, + stm32_tt_show_master_mode, + stm32_tt_store_master_mode, + 0); + +static struct attribute *stm32_trigger_attrs[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_master_mode.dev_attr.attr, + &iio_dev_attr_master_mode_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group stm32_trigger_attr_group = { + .attrs = stm32_trigger_attrs, +}; + +static const struct attribute_group *stm32_trigger_attr_groups[] = { + &stm32_trigger_attr_group, + NULL, +}; + +static const struct iio_trigger_ops timer_trigger_ops = { +}; + +static void stm32_unregister_iio_triggers(struct stm32_timer_trigger *priv) +{ + struct iio_trigger *tr; + + list_for_each_entry(tr, &priv->tr_list, alloc_list) + iio_trigger_unregister(tr); +} + +static int stm32_register_iio_triggers(struct stm32_timer_trigger *priv) +{ + int ret; + const char * const *cur = priv->triggers; + + INIT_LIST_HEAD(&priv->tr_list); + + while (cur && *cur) { + struct iio_trigger *trig; + bool cur_is_trgo = stm32_timer_is_trgo_name(*cur); + bool cur_is_trgo2 = stm32_timer_is_trgo2_name(*cur); + + if (cur_is_trgo2 && !priv->has_trgo2) { + cur++; + continue; + } + + trig = devm_iio_trigger_alloc(priv->dev, "%s", *cur); + if (!trig) + return -ENOMEM; + + trig->dev.parent = priv->dev->parent; + trig->ops = &timer_trigger_ops; + + /* + * sampling frequency and master mode attributes + * should only be available on trgo/trgo2 triggers + */ + if (cur_is_trgo || cur_is_trgo2) + trig->dev.groups = stm32_trigger_attr_groups; + + iio_trigger_set_drvdata(trig, priv); + + ret = iio_trigger_register(trig); + if (ret) { + stm32_unregister_iio_triggers(priv); + return ret; + } + + list_add_tail(&trig->alloc_list, &priv->tr_list); + cur++; + } + + return 0; +} + +static int stm32_counter_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 dat; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + regmap_read(priv->regmap, TIM_CNT, &dat); + *val = dat; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_ENABLE: + regmap_read(priv->regmap, TIM_CR1, &dat); + *val = (dat & TIM_CR1_CEN) ? 1 : 0; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + regmap_read(priv->regmap, TIM_SMCR, &dat); + dat &= TIM_SMCR_SMS; + + *val = 1; + *val2 = 0; + + /* in quadrature case scale = 0.25 */ + if (dat == 3) + *val2 = 2; + + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static int stm32_counter_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return regmap_write(priv->regmap, TIM_CNT, val); + + case IIO_CHAN_INFO_SCALE: + /* fixed scale */ + return -EINVAL; + + case IIO_CHAN_INFO_ENABLE: { + guard(mutex)(&priv->lock); + if (val) { + if (!priv->enabled) { + priv->enabled = true; + ret = clk_enable(priv->clk); + if (ret) + return ret; + } + regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + } else { + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + if (priv->enabled) { + priv->enabled = false; + clk_disable(priv->clk); + } + } + + return 0; + } + default: + return -EINVAL; + } +} + +static int stm32_counter_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + const char * const *cur = priv->valids; + unsigned int i = 0; + + if (!is_stm32_timer_trigger(trig)) + return -EINVAL; + + while (cur && *cur) { + if (!strncmp(trig->name, *cur, strlen(trig->name))) { + regmap_update_bits(priv->regmap, + TIM_SMCR, TIM_SMCR_TS, + i << TIM_SMCR_TS_SHIFT); + return 0; + } + cur++; + i++; + } + + return -EINVAL; +} + +static const struct iio_info stm32_trigger_info = { + .validate_trigger = stm32_counter_validate_trigger, + .read_raw = stm32_counter_read_raw, + .write_raw = stm32_counter_write_raw +}; + +static const char *const stm32_trigger_modes[] = { + "trigger", +}; + +static int stm32_set_trigger_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + + regmap_set_bits(priv->regmap, TIM_SMCR, TIM_SMCR_SMS); + + return 0; +} + +static int stm32_get_trigger_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 smcr; + + regmap_read(priv->regmap, TIM_SMCR, &smcr); + + return (smcr & TIM_SMCR_SMS) == TIM_SMCR_SMS ? 0 : -EINVAL; +} + +static const struct iio_enum stm32_trigger_mode_enum = { + .items = stm32_trigger_modes, + .num_items = ARRAY_SIZE(stm32_trigger_modes), + .set = stm32_set_trigger_mode, + .get = stm32_get_trigger_mode +}; + +static const char *const stm32_enable_modes[] = { + "always", + "gated", + "triggered", +}; + +static int stm32_enable_mode2sms(int mode) +{ + switch (mode) { + case 0: + return 0; + case 1: + return 5; + case 2: + return 6; + } + + return -EINVAL; +} + +static int stm32_set_enable_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + int sms = stm32_enable_mode2sms(mode); + int ret; + + if (sms < 0) + return sms; + /* + * Triggered mode sets CEN bit automatically by hardware. So, first + * enable counter clock, so it can use it. Keeps it in sync with CEN. + */ + scoped_guard(mutex, &priv->lock) { + if (sms == 6 && !priv->enabled) { + ret = clk_enable(priv->clk); + if (ret) + return ret; + + priv->enabled = true; + } + } + + regmap_update_bits(priv->regmap, TIM_SMCR, TIM_SMCR_SMS, sms); + + return 0; +} + +static int stm32_sms2enable_mode(int mode) +{ + switch (mode) { + case 0: + return 0; + case 5: + return 1; + case 6: + return 2; + } + + return -EINVAL; +} + +static int stm32_get_enable_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 smcr; + + regmap_read(priv->regmap, TIM_SMCR, &smcr); + smcr &= TIM_SMCR_SMS; + + return stm32_sms2enable_mode(smcr); +} + +static const struct iio_enum stm32_enable_mode_enum = { + .items = stm32_enable_modes, + .num_items = ARRAY_SIZE(stm32_enable_modes), + .set = stm32_set_enable_mode, + .get = stm32_get_enable_mode +}; + +static ssize_t stm32_count_get_preset(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 arr; + + regmap_read(priv->regmap, TIM_ARR, &arr); + + return snprintf(buf, PAGE_SIZE, "%u\n", arr); +} + +static ssize_t stm32_count_set_preset(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + unsigned int preset; + int ret; + + ret = kstrtouint(buf, 0, &preset); + if (ret) + return ret; + + /* TIMx_ARR register shouldn't be buffered (ARPE=0) */ + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE); + regmap_write(priv->regmap, TIM_ARR, preset); + + return len; +} + +static const struct iio_chan_spec_ext_info stm32_trigger_count_info[] = { + { + .name = "preset", + .shared = IIO_SEPARATE, + .read = stm32_count_get_preset, + .write = stm32_count_set_preset + }, + IIO_ENUM("enable_mode", IIO_SEPARATE, &stm32_enable_mode_enum), + IIO_ENUM_AVAILABLE("enable_mode", IIO_SHARED_BY_TYPE, &stm32_enable_mode_enum), + IIO_ENUM("trigger_mode", IIO_SEPARATE, &stm32_trigger_mode_enum), + IIO_ENUM_AVAILABLE("trigger_mode", IIO_SHARED_BY_TYPE, &stm32_trigger_mode_enum), + { } +}; + +static const struct iio_chan_spec stm32_trigger_channel = { + .type = IIO_COUNT, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_SCALE), + .ext_info = stm32_trigger_count_info, + .indexed = 1 +}; + +static struct stm32_timer_trigger *stm32_setup_counter_device(struct device *dev) +{ + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, + sizeof(struct stm32_timer_trigger)); + if (!indio_dev) + return NULL; + + indio_dev->name = dev_name(dev); + indio_dev->info = &stm32_trigger_info; + indio_dev->modes = INDIO_HARDWARE_TRIGGERED; + indio_dev->num_channels = 1; + indio_dev->channels = &stm32_trigger_channel; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return NULL; + + return iio_priv(indio_dev); +} + +/** + * is_stm32_timer_trigger + * @trig: trigger to be checked + * + * return true if the trigger is a valid stm32 iio timer trigger + * either return false + */ +bool is_stm32_timer_trigger(struct iio_trigger *trig) +{ + return (trig->ops == &timer_trigger_ops); +} +EXPORT_SYMBOL(is_stm32_timer_trigger); + +static void stm32_timer_detect_trgo2(struct stm32_timer_trigger *priv) +{ + u32 val; + + /* + * Master mode selection 2 bits can only be written and read back when + * timer supports it. + */ + regmap_set_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS2); + regmap_read(priv->regmap, TIM_CR2, &val); + regmap_clear_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS2); + priv->has_trgo2 = !!val; +} + +static int stm32_timer_trigger_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct stm32_timer_trigger *priv; + struct stm32_timers *ddata = dev_get_drvdata(pdev->dev.parent); + const struct stm32_timer_trigger_cfg *cfg; + unsigned int index; + int ret; + + ret = device_property_read_u32(dev, "reg", &index); + if (ret) + return ret; + + cfg = device_get_match_data(dev); + + if (index >= ARRAY_SIZE(triggers_table) || + index >= cfg->num_valids_table) + return -EINVAL; + + /* Create an IIO device only if we have triggers to be validated */ + if (cfg->valids_table && *cfg->valids_table[index]) + priv = stm32_setup_counter_device(dev); + else + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->regmap = ddata->regmap; + priv->clk = ddata->clk; + priv->max_arr = ddata->max_arr; + priv->triggers = triggers_table[index]; + if (cfg->valids_table && *cfg->valids_table[index]) + priv->valids = cfg->valids_table[index]; + stm32_timer_detect_trgo2(priv); + mutex_init(&priv->lock); + + ret = stm32_register_iio_triggers(priv); + if (ret) + return ret; + + platform_set_drvdata(pdev, priv); + + return 0; +} + +static void stm32_timer_trigger_remove(struct platform_device *pdev) +{ + struct stm32_timer_trigger *priv = platform_get_drvdata(pdev); + u32 val; + + /* Unregister triggers before everything can be safely turned off */ + stm32_unregister_iio_triggers(priv); + + /* Check if nobody else use the timer, then disable it */ + regmap_read(priv->regmap, TIM_CCER, &val); + if (!(val & TIM_CCER_CCXE)) + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + + if (priv->enabled) + clk_disable(priv->clk); +} + +static int stm32_timer_trigger_suspend(struct device *dev) +{ + struct stm32_timer_trigger *priv = dev_get_drvdata(dev); + + /* Only take care of enabled timer: don't disturb other MFD child */ + if (priv->enabled) { + /* Backup registers that may get lost in low power mode */ + regmap_read(priv->regmap, TIM_CR1, &priv->bak.cr1); + regmap_read(priv->regmap, TIM_CR2, &priv->bak.cr2); + regmap_read(priv->regmap, TIM_PSC, &priv->bak.psc); + regmap_read(priv->regmap, TIM_ARR, &priv->bak.arr); + regmap_read(priv->regmap, TIM_CNT, &priv->bak.cnt); + regmap_read(priv->regmap, TIM_SMCR, &priv->bak.smcr); + + /* Disable the timer */ + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + clk_disable(priv->clk); + } + + return 0; +} + +static int stm32_timer_trigger_resume(struct device *dev) +{ + struct stm32_timer_trigger *priv = dev_get_drvdata(dev); + int ret; + + if (priv->enabled) { + ret = clk_enable(priv->clk); + if (ret) + return ret; + + /* restore master/slave modes */ + regmap_write(priv->regmap, TIM_SMCR, priv->bak.smcr); + regmap_write(priv->regmap, TIM_CR2, priv->bak.cr2); + + /* restore sampling_frequency (trgo / trgo2 triggers) */ + regmap_write(priv->regmap, TIM_PSC, priv->bak.psc); + regmap_write(priv->regmap, TIM_ARR, priv->bak.arr); + regmap_write(priv->regmap, TIM_CNT, priv->bak.cnt); + + /* Also re-enables the timer */ + regmap_write(priv->regmap, TIM_CR1, priv->bak.cr1); + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(stm32_timer_trigger_pm_ops, + stm32_timer_trigger_suspend, + stm32_timer_trigger_resume); + +static const struct stm32_timer_trigger_cfg stm32_timer_trg_cfg = { + .valids_table = valids_table, + .num_valids_table = ARRAY_SIZE(valids_table), +}; + +static const struct stm32_timer_trigger_cfg stm32h7_timer_trg_cfg = { + .valids_table = stm32h7_valids_table, + .num_valids_table = ARRAY_SIZE(stm32h7_valids_table), +}; + +static const struct stm32_timer_trigger_cfg stm32mp25_timer_trg_cfg = { + /* + * valids_table not used: counter framework is now superseding the deprecated IIO + * counter interface (IIO_COUNT), so don't support it. num_valids_table is only + * kept here to register the IIO HW triggers. valids_table should be moved at some + * point to the stm32-timer-cnt driver instead. + */ + .num_valids_table = ARRAY_SIZE(triggers_table), +}; + +static const struct of_device_id stm32_trig_of_match[] = { + { + .compatible = "st,stm32-timer-trigger", + .data = (void *)&stm32_timer_trg_cfg, + }, { + .compatible = "st,stm32h7-timer-trigger", + .data = (void *)&stm32h7_timer_trg_cfg, + }, { + .compatible = "st,stm32mp25-timer-trigger", + .data = (void *)&stm32mp25_timer_trg_cfg, + }, + { } +}; +MODULE_DEVICE_TABLE(of, stm32_trig_of_match); + +static struct platform_driver stm32_timer_trigger_driver = { + .probe = stm32_timer_trigger_probe, + .remove = stm32_timer_trigger_remove, + .driver = { + .name = "stm32-timer-trigger", + .of_match_table = stm32_trig_of_match, + .pm = pm_sleep_ptr(&stm32_timer_trigger_pm_ops), + }, +}; +module_platform_driver(stm32_timer_trigger_driver); + +MODULE_ALIAS("platform:stm32-timer-trigger"); +MODULE_DESCRIPTION("STMicroelectronics STM32 Timer Trigger driver"); +MODULE_LICENSE("GPL v2"); |
