diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-24 15:44:26 -1000 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-24 15:44:26 -1000 |
| commit | 330517c63aa9b48227b6ab45f01003effac77ae4 (patch) | |
| tree | 51b6e38214a50c0253eba6dc05eeed6ae0f5b995 /drivers | |
| parent | 168151cdb0f93d26557f9f3c7aae659b8d6b1863 (diff) | |
| parent | 985955d8e6b2b15cfd65b7ac6893e3a5a90c5870 (diff) | |
Merge tag 'char-misc-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull Char/Misc/IIO/others driver updates from Greg KH:
"Here is the big set of char/misc/android/iio and other small driver
subsystem patches for 7.4-rc1. Loads of stuff in here, larger than
normal. Highlights include:
- removal of the C binder code, the rust version is stable and will
be used going forward in a few billion devices. Great job by those
developers
- loads and loads of IIO driver updates and feature additions
- speakup driver fixes and updates
- mhi driver updates
- mei driver updates
- xillybus driver fixes
- comedi driver fixes
- lots of coresight driver updates
- Interconnect driver updates
- nvmem driver updates and fixes
- slimbus driver updates and fixes
- spmi driver updates and fixes
- lots of tiny misc and char driver fixes based on people scanning
for things
All of these have been in linux-next for a while with no reported
issues, other than a merge conflict with one of the rust tree files,
which should be simple to resolve"
* tag 'char-misc-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (446 commits)
rust_binder: wake epoll on self-work queued to poll thread
rust_binder: avoid overflow in get_node_debug_info()
MAINTAINERS: Add Abel as SPMI co-maintainer
spmi: mtk-pmif: Fix clock reference leak in mtk_spmi_bus_probe()
iio: adc: qcom-spmi-iadc: Remove regmap R/W wrapper functions
iio: adc: qcom-spmi-iadc: Migrate to devm_spmi_subdevice_alloc_and_add()
misc: qcom-coincell: Migrate to devm_spmi_subdevice_alloc_and_add()
phy: qualcomm: eusb2-repeater: Migrate to devm_spmi_subdevice_alloc_and_add()
power: reset: qcom-pon: Migrate to devm_spmi_subdevice_alloc_and_add()
nvmem: qcom-spmi-sdam: Migrate to devm_spmi_subdevice_alloc_and_add()
spmi: Add helper to get a parent SPMI device
spmi: Implement spmi_subdevice_alloc_and_add() and devm variant
spmi: Remove unneeded goto in spmi_device_add() error path
spmi: Print error status with %pe format
spmi: Remove redundant dev_name() print in spmi_device_add()
spmi: Fix potential use-after-free by grabbing of_node reference
spmi: mtk-pmif: Add workaround for FSM lockup/error in read operation
MAINTAINERS: Add Qualcomm SPMI controller entry
spmi: pmic-arb: return 0 in spmi_pmic_arb_register_buses() on success
comedi: addi_apci_3xxx: fix subdevice count underallocation
...
Diffstat (limited to 'drivers')
322 files changed, 18673 insertions, 15825 deletions
diff --git a/drivers/accessibility/speakup/main.c b/drivers/accessibility/speakup/main.c index e9b7c2761f6f..a7c14531f2ce 100644 --- a/drivers/accessibility/speakup/main.c +++ b/drivers/accessibility/speakup/main.c @@ -1980,7 +1980,7 @@ static int handle_goto(struct vc_data *vc, u_char type, u_char ch, u16 key) maxlen = (*goto_buf >= '0') ? 3 : 4; if ((ch == '+' || ch == '-') && num == 1) return 1; - if (ch >= '0' && ch <= '9' && num < maxlen) + if (isdigit(ch) && num < maxlen) return 1; if (num < maxlen - 1 || num > maxlen) goto oops; diff --git a/drivers/accessibility/speakup/selection.c b/drivers/accessibility/speakup/selection.c index 1713ce4e0ba5..a04a7e8c307b 100644 --- a/drivers/accessibility/speakup/selection.c +++ b/drivers/accessibility/speakup/selection.c @@ -61,6 +61,9 @@ static struct speakup_selection_work speakup_sel_work = { int speakup_set_selection(struct tty_struct *tty) { + if (!tty) + return -ENODEV; + /* we get kref here first in order to avoid a subtle race when * cancelling selection work. getting kref first establishes the * invariant that if speakup_sel_work.tty is not NULL when @@ -120,6 +123,9 @@ static struct speakup_selection_work speakup_paste_work = { int speakup_paste_selection(struct tty_struct *tty) { + if (!tty) + return -ENODEV; + tty_kref_get(tty); if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) { tty_kref_put(tty); diff --git a/drivers/accessibility/speakup/serialio.c b/drivers/accessibility/speakup/serialio.c index 3418ea31d28f..c344296be9f6 100644 --- a/drivers/accessibility/speakup/serialio.c +++ b/drivers/accessibility/speakup/serialio.c @@ -8,22 +8,8 @@ #include "serialio.h" #include <linux/serial_core.h> -/* WARNING: Do not change this to <linux/serial.h> without testing that - * SERIAL_PORT_DFNS does get defined to the appropriate value. - */ -#include <asm/serial.h> +#include <linux/serial.h> -#ifndef SERIAL_PORT_DFNS -#define SERIAL_PORT_DFNS -#endif - -static void start_serial_interrupt(int irq); - -static const struct old_serial_port rs_table[] = { - SERIAL_PORT_DFNS -}; - -static const struct old_serial_port *serstate; static int timeouts; static int spk_serial_out(struct spk_synth *in_synth, const char ch); @@ -45,111 +31,6 @@ struct spk_io_ops spk_serial_io_ops = { }; EXPORT_SYMBOL_GPL(spk_serial_io_ops); -const struct old_serial_port *spk_serial_init(int index) -{ - int baud = 9600, quot = 0; - unsigned int cval = 0; - int cflag = CREAD | HUPCL | CLOCAL | B9600 | CS8; - const struct old_serial_port *ser; - int err; - - if (index >= ARRAY_SIZE(rs_table)) { - pr_info("no port info for ttyS%d\n", index); - return NULL; - } - ser = rs_table + index; - - /* Divisor, byte size and parity */ - quot = ser->baud_base / baud; - cval = cflag & (CSIZE | CSTOPB); -#if defined(__powerpc__) || defined(__alpha__) - cval >>= 8; -#else /* !__powerpc__ && !__alpha__ */ - cval >>= 4; -#endif /* !__powerpc__ && !__alpha__ */ - if (cflag & PARENB) - cval |= UART_LCR_PARITY; - if (!(cflag & PARODD)) - cval |= UART_LCR_EPAR; - if (synth_request_region(ser->port, 8)) { - /* try to take it back. */ - pr_info("Ports not available, trying to steal them\n"); - __release_region(&ioport_resource, ser->port, 8); - err = synth_request_region(ser->port, 8); - if (err) { - pr_warn("Unable to allocate port at %x, errno %i", - ser->port, err); - return NULL; - } - } - - /* Disable UART interrupts, set DTR and RTS high - * and set speed. - */ - outb(cval | UART_LCR_DLAB, ser->port + UART_LCR); /* set DLAB */ - outb(quot & 0xff, ser->port + UART_DLL); /* LS of divisor */ - outb(quot >> 8, ser->port + UART_DLM); /* MS of divisor */ - outb(cval, ser->port + UART_LCR); /* reset DLAB */ - - /* Turn off Interrupts */ - outb(0, ser->port + UART_IER); - outb(UART_MCR_DTR | UART_MCR_RTS, ser->port + UART_MCR); - - /* If we read 0xff from the LSR, there is no UART here. */ - if (inb(ser->port + UART_LSR) == 0xff) { - synth_release_region(ser->port, 8); - serstate = NULL; - return NULL; - } - - mdelay(1); - speakup_info.port_tts = ser->port; - serstate = ser; - - start_serial_interrupt(ser->irq); - - return ser; -} - -static irqreturn_t synth_readbuf_handler(int irq, void *dev_id) -{ - unsigned long flags; - int c; - - spin_lock_irqsave(&speakup_info.spinlock, flags); - while (inb_p(speakup_info.port_tts + UART_LSR) & UART_LSR_DR) { - c = inb_p(speakup_info.port_tts + UART_RX); - synth->read_buff_add((u_char)c); - } - spin_unlock_irqrestore(&speakup_info.spinlock, flags); - return IRQ_HANDLED; -} - -static void start_serial_interrupt(int irq) -{ - int rv; - - if (!synth->read_buff_add) - return; - - rv = request_irq(irq, synth_readbuf_handler, IRQF_SHARED, - "serial", (void *)synth_readbuf_handler); - - if (rv) - pr_err("Unable to request Speakup serial I R Q\n"); - /* Set MCR */ - outb(UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2, - speakup_info.port_tts + UART_MCR); - /* Turn on Interrupts */ - outb(UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI, - speakup_info.port_tts + UART_IER); - inb(speakup_info.port_tts + UART_LSR); - inb(speakup_info.port_tts + UART_RX); - inb(speakup_info.port_tts + UART_IIR); - inb(speakup_info.port_tts + UART_MSR); - outb(1, speakup_info.port_tts + UART_FCR); /* Turn FIFO On */ -} - static void spk_serial_send_xchar(struct spk_synth *synth, char ch) { int timeout = SPK_XMITR_TIMEOUT; @@ -169,50 +50,6 @@ static void spk_serial_tiocmset(struct spk_synth *in_synth, unsigned int set, un outb((old & ~clear) | set, speakup_info.port_tts + UART_MCR); } -int spk_serial_synth_probe(struct spk_synth *synth) -{ - const struct old_serial_port *ser; - int failed = 0; - - if ((synth->ser >= SPK_LO_TTY) && (synth->ser <= SPK_HI_TTY)) { - ser = spk_serial_init(synth->ser); - if (!ser) { - failed = -1; - } else { - outb_p(0, ser->port); - mdelay(1); - outb_p('\r', ser->port); - } - } else { - failed = -1; - pr_warn("ttyS%i is an invalid port\n", synth->ser); - } - if (failed) { - pr_info("%s: not found\n", synth->long_name); - return -ENODEV; - } - pr_info("%s: ttyS%i, Driver Version %s\n", - synth->long_name, synth->ser, synth->version); - synth->alive = 1; - return 0; -} -EXPORT_SYMBOL_GPL(spk_serial_synth_probe); - -void spk_stop_serial_interrupt(void) -{ - if (speakup_info.port_tts == 0) - return; - - if (!synth->read_buff_add) - return; - - /* Turn off interrupts */ - outb(0, speakup_info.port_tts + UART_IER); - /* Free IRQ */ - free_irq(serstate->irq, (void *)synth_readbuf_handler); -} -EXPORT_SYMBOL_GPL(spk_stop_serial_interrupt); - static int spk_serial_wait_for_xmitr(struct spk_synth *in_synth) { int tmout = SPK_XMITR_TIMEOUT; @@ -288,31 +125,3 @@ static int spk_serial_out(struct spk_synth *in_synth, const char ch) } return 0; } - -const char *spk_serial_synth_immediate(struct spk_synth *synth, - const char *buff) -{ - u_char ch; - - while ((ch = *buff)) { - if (ch == '\n') - ch = synth->procspeech; - if (spk_serial_wait_for_xmitr(synth)) - outb(ch, speakup_info.port_tts); - else - return buff; - buff++; - } - return NULL; -} -EXPORT_SYMBOL_GPL(spk_serial_synth_immediate); - -void spk_serial_release(struct spk_synth *synth) -{ - spk_stop_serial_interrupt(); - if (speakup_info.port_tts == 0) - return; - synth_release_region(speakup_info.port_tts, 8); - speakup_info.port_tts = 0; -} -EXPORT_SYMBOL_GPL(spk_serial_release); diff --git a/drivers/accessibility/speakup/serialio.h b/drivers/accessibility/speakup/serialio.h index b4f9a1925b81..189c0dd9ce41 100644 --- a/drivers/accessibility/speakup/serialio.h +++ b/drivers/accessibility/speakup/serialio.h @@ -8,31 +8,14 @@ #include "spk_priv.h" -/* - * this is cut&paste from 8250.h. Get rid of the structure, the definitions - * and this whole broken driver. - */ -struct old_serial_port { - unsigned int uart; /* unused */ - unsigned int baud_base; - unsigned int port; - unsigned int irq; - upf_t flags; /* unused */ -}; - /* countdown values for serial timeouts in us */ #define SPK_SERIAL_TIMEOUT SPK_SYNTH_TIMEOUT /* countdown values transmitter/dsr timeouts in us */ #define SPK_XMITR_TIMEOUT 100000 /* countdown values cts timeouts in us */ #define SPK_CTS_TIMEOUT 100000 -/* check ttyS0 ... ttyS3 */ -#define SPK_LO_TTY 0 -#define SPK_HI_TTY 3 /* # of timeouts permitted before disable */ #define NUM_DISABLE_TIMEOUTS 3 -/* buffer timeout in ms */ -#define SPK_TIMEOUT 100 #define spk_serial_tx_busy() \ (!uart_lsr_tx_empty(inb(speakup_info.port_tts + UART_LSR))) diff --git a/drivers/accessibility/speakup/speakup_acntpc.c b/drivers/accessibility/speakup/speakup_acntpc.c index a27e6bbf05da..1cc6147cafac 100644 --- a/drivers/accessibility/speakup/speakup_acntpc.c +++ b/drivers/accessibility/speakup/speakup_acntpc.c @@ -305,7 +305,6 @@ static int synth_probe(struct spk_synth *synth) static void accent_release(struct spk_synth *synth) { - spk_stop_serial_interrupt(); if (speakup_info.port_tts) synth_release_region(speakup_info.port_tts - 1, SYNTH_IO_EXTENT); diff --git a/drivers/accessibility/speakup/speakup_decpc.c b/drivers/accessibility/speakup/speakup_decpc.c index 1bf36d1e5477..4837cac8bafa 100644 --- a/drivers/accessibility/speakup/speakup_decpc.c +++ b/drivers/accessibility/speakup/speakup_decpc.c @@ -487,7 +487,6 @@ static int synth_probe(struct spk_synth *synth) static void dtpc_release(struct spk_synth *synth) { - spk_stop_serial_interrupt(); if (speakup_info.port_tts) synth_release_region(speakup_info.port_tts, SYNTH_IO_EXTENT); speakup_info.port_tts = 0; diff --git a/drivers/accessibility/speakup/speakup_dtlk.c b/drivers/accessibility/speakup/speakup_dtlk.c index fa826568937b..0a57f7ea894c 100644 --- a/drivers/accessibility/speakup/speakup_dtlk.c +++ b/drivers/accessibility/speakup/speakup_dtlk.c @@ -377,7 +377,6 @@ static int synth_probe(struct spk_synth *synth) static void dtlk_release(struct spk_synth *synth) { - spk_stop_serial_interrupt(); if (speakup_info.port_tts) synth_release_region(speakup_info.port_tts - 1, SYNTH_IO_EXTENT); diff --git a/drivers/accessibility/speakup/speakup_keypc.c b/drivers/accessibility/speakup/speakup_keypc.c index 9356f6379560..1e2edc5c4b8b 100644 --- a/drivers/accessibility/speakup/speakup_keypc.c +++ b/drivers/accessibility/speakup/speakup_keypc.c @@ -306,7 +306,6 @@ static int synth_probe(struct spk_synth *synth) static void keynote_release(struct spk_synth *synth) { - spk_stop_serial_interrupt(); if (synth_port) synth_release_region(synth_port, SYNTH_IO_EXTENT); synth_port = 0; diff --git a/drivers/accessibility/speakup/spk_priv.h b/drivers/accessibility/speakup/spk_priv.h index 9da57ead17cb..704ff9f120d3 100644 --- a/drivers/accessibility/speakup/spk_priv.h +++ b/drivers/accessibility/speakup/spk_priv.h @@ -32,9 +32,6 @@ #define SYNTH_DEFAULT_DEV "ttyS0" #define SYNTH_DEFAULT_SER 0 -const struct old_serial_port *spk_serial_init(int index); -void spk_stop_serial_interrupt(void); -void spk_serial_release(struct spk_synth *synth); void spk_ttyio_release(struct spk_synth *synth); void spk_ttyio_register_ldisc(void); void spk_ttyio_unregister_ldisc(void); @@ -49,10 +46,7 @@ ssize_t spk_var_show(struct kobject *kobj, struct kobj_attribute *attr, ssize_t spk_var_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count); -int spk_serial_synth_probe(struct spk_synth *synth); int spk_ttyio_synth_probe(struct spk_synth *synth); -const char *spk_serial_synth_immediate(struct spk_synth *synth, - const char *buff); const char *spk_ttyio_synth_immediate(struct spk_synth *synth, const char *buff); void spk_do_catch_up(struct spk_synth *synth); diff --git a/drivers/accessibility/speakup/varhandlers.c b/drivers/accessibility/speakup/varhandlers.c index 462f8d879053..c6d3b4cc1af8 100644 --- a/drivers/accessibility/speakup/varhandlers.c +++ b/drivers/accessibility/speakup/varhandlers.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/ctype.h> +#include <linux/string.h> #include "spk_types.h" #include "spk_priv.h" #include "speakup.h" @@ -265,10 +266,10 @@ int spk_set_string_var(const char *page, struct st_var_header *var, int len) if (!var->p_val) var->p_val = var_data->u.s.default_val; if (var->p_val != var_data->u.s.default_val) - strcpy((char *)var->p_val, var_data->u.s.default_val); + strscpy((char *)var->p_val, var_data->u.s.default_val, MAXVARLEN + 1); return -ERESTART; } else if (var->p_val) { - strcpy((char *)var->p_val, page); + strscpy((char *)var->p_val, page, MAXVARLEN + 1); } else { return -E2BIG; } diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig index 606a9d07f774..c920d52c0221 100644 --- a/drivers/android/Kconfig +++ b/drivers/android/Kconfig @@ -1,10 +1,11 @@ # SPDX-License-Identifier: GPL-2.0 menu "Android" -config ANDROID_BINDER_IPC +config ANDROID_BINDER_IPC_RUST bool "Android Binder IPC Driver" depends on MMU depends on NET + depends on RUST default n help Binder is used in Android for both communication between processes, @@ -14,34 +15,9 @@ config ANDROID_BINDER_IPC Android process, using Binder to identify, invoke and pass arguments between said processes. -config ANDROID_BINDER_IPC_RUST - bool "Rust version of Android Binder IPC Driver" - depends on RUST && MMU && NET && !ANDROID_BINDER_IPC - help - This enables the Rust implementation of the Binder driver. - - Binder is used in Android for both communication between processes, - and remote method invocation. - - This means one Android process can call a method/routine in another - Android process, using Binder to identify, invoke and pass arguments - between said processes. - -config ANDROID_BINDERFS - bool "Android Binderfs filesystem" - depends on ANDROID_BINDER_IPC - default n - help - Binderfs is a pseudo-filesystem for the Android Binder IPC driver - which can be mounted per-ipc namespace allowing to run multiple - instances of Android. - Each binderfs mount initially only contains a binder-control device. - It can be used to dynamically allocate new binder IPC devices via - ioctls. - config ANDROID_BINDER_DEVICES string "Android Binder devices" - depends on ANDROID_BINDER_IPC || ANDROID_BINDER_IPC_RUST + depends on ANDROID_BINDER_IPC_RUST default "binder,hwbinder,vndbinder" help Default value for the binder.devices parameter. @@ -51,15 +27,4 @@ config ANDROID_BINDER_DEVICES created. Each binder device has its own context manager, and is therefore logically separated from the other devices. -config ANDROID_BINDER_ALLOC_KUNIT_TEST - tristate "KUnit Tests for Android Binder Alloc" if !KUNIT_ALL_TESTS - depends on ANDROID_BINDER_IPC && KUNIT - default KUNIT_ALL_TESTS - help - This feature builds the binder alloc KUnit tests. - - Each test case runs using a pared-down binder_alloc struct and - test-specific freelist, which allows this KUnit module to be loaded - for testing without interfering with a running system. - endmenu diff --git a/drivers/android/Makefile b/drivers/android/Makefile index e0c650d3898e..f83d39d16f6e 100644 --- a/drivers/android/Makefile +++ b/drivers/android/Makefile @@ -1,7 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only ccflags-y += -I$(src) # needed for trace events -obj-$(CONFIG_ANDROID_BINDERFS) += binderfs.o -obj-$(CONFIG_ANDROID_BINDER_IPC) += binder.o binder_alloc.o binder_netlink.o -obj-$(CONFIG_ANDROID_BINDER_ALLOC_KUNIT_TEST) += tests/ obj-$(CONFIG_ANDROID_BINDER_IPC_RUST) += binder/ diff --git a/drivers/android/binder.c b/drivers/android/binder.c deleted file mode 100644 index bc8bc9ee43a5..000000000000 --- a/drivers/android/binder.c +++ /dev/null @@ -1,7187 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* binder.c - * - * Android IPC Subsystem - * - * Copyright (C) 2007-2008 Google, Inc. - */ - -/* - * Locking overview - * - * There are 3 main spinlocks which must be acquired in the - * order shown: - * - * 1) proc->outer_lock : protects binder_ref - * binder_proc_lock() and binder_proc_unlock() are - * used to acq/rel. - * 2) node->lock : protects most fields of binder_node. - * binder_node_lock() and binder_node_unlock() are - * used to acq/rel - * 3) proc->inner_lock : protects the thread and node lists - * (proc->threads, proc->waiting_threads, proc->nodes) - * and all todo lists associated with the binder_proc - * (proc->todo, thread->todo, proc->delivered_death and - * node->async_todo), as well as thread->transaction_stack - * binder_inner_proc_lock() and binder_inner_proc_unlock() - * are used to acq/rel - * - * Any lock under procA must never be nested under any lock at the same - * level or below on procB. - * - * Functions that require a lock held on entry indicate which lock - * in the suffix of the function name: - * - * foo_olocked() : requires node->outer_lock - * foo_nlocked() : requires node->lock - * foo_ilocked() : requires proc->inner_lock - * foo_oilocked(): requires proc->outer_lock and proc->inner_lock - * foo_nilocked(): requires node->lock and proc->inner_lock - * ... - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/fdtable.h> -#include <linux/file.h> -#include <linux/freezer.h> -#include <linux/fs.h> -#include <linux/list.h> -#include <linux/miscdevice.h> -#include <linux/module.h> -#include <linux/mutex.h> -#include <linux/nsproxy.h> -#include <linux/poll.h> -#include <linux/debugfs.h> -#include <linux/rbtree.h> -#include <linux/sched/signal.h> -#include <linux/sched/mm.h> -#include <linux/seq_file.h> -#include <linux/string.h> -#include <linux/uaccess.h> -#include <linux/pid_namespace.h> -#include <linux/security.h> -#include <linux/spinlock.h> -#include <linux/ratelimit.h> -#include <linux/syscalls.h> -#include <linux/task_work.h> -#include <linux/sizes.h> -#include <linux/ktime.h> - -#include <kunit/visibility.h> - -#include <uapi/linux/android/binder.h> - -#include <linux/cacheflush.h> - -#include "binder_netlink.h" -#include "binder_internal.h" -#include "binder_trace.h" - -static HLIST_HEAD(binder_deferred_list); -static DEFINE_MUTEX(binder_deferred_lock); - -static HLIST_HEAD(binder_devices); -static DEFINE_SPINLOCK(binder_devices_lock); - -static HLIST_HEAD(binder_procs); -static DEFINE_MUTEX(binder_procs_lock); - -static HLIST_HEAD(binder_dead_nodes); -static DEFINE_SPINLOCK(binder_dead_nodes_lock); - -static struct dentry *binder_debugfs_dir_entry_root; -static struct dentry *binder_debugfs_dir_entry_proc; -static atomic_t binder_last_id; - -static int proc_show(struct seq_file *m, void *unused); -DEFINE_SHOW_ATTRIBUTE(proc); - -#define FORBIDDEN_MMAP_FLAGS (VM_WRITE) - -enum { - BINDER_DEBUG_USER_ERROR = 1U << 0, - BINDER_DEBUG_FAILED_TRANSACTION = 1U << 1, - BINDER_DEBUG_DEAD_TRANSACTION = 1U << 2, - BINDER_DEBUG_OPEN_CLOSE = 1U << 3, - BINDER_DEBUG_DEAD_BINDER = 1U << 4, - BINDER_DEBUG_DEATH_NOTIFICATION = 1U << 5, - BINDER_DEBUG_READ_WRITE = 1U << 6, - BINDER_DEBUG_USER_REFS = 1U << 7, - BINDER_DEBUG_THREADS = 1U << 8, - BINDER_DEBUG_TRANSACTION = 1U << 9, - BINDER_DEBUG_TRANSACTION_COMPLETE = 1U << 10, - BINDER_DEBUG_FREE_BUFFER = 1U << 11, - BINDER_DEBUG_INTERNAL_REFS = 1U << 12, - BINDER_DEBUG_PRIORITY_CAP = 1U << 13, - BINDER_DEBUG_SPINLOCKS = 1U << 14, -}; -static uint32_t binder_debug_mask = BINDER_DEBUG_USER_ERROR | - BINDER_DEBUG_FAILED_TRANSACTION | BINDER_DEBUG_DEAD_TRANSACTION; -module_param_named(debug_mask, binder_debug_mask, uint, 0644); - -char *binder_devices_param = CONFIG_ANDROID_BINDER_DEVICES; -module_param_named(devices, binder_devices_param, charp, 0444); - -static DECLARE_WAIT_QUEUE_HEAD(binder_user_error_wait); -static int binder_stop_on_user_error; - -static int binder_set_stop_on_user_error(const char *val, - const struct kernel_param *kp) -{ - int ret; - - ret = param_set_int(val, kp); - if (binder_stop_on_user_error < 2) - wake_up(&binder_user_error_wait); - return ret; -} -module_param_call(stop_on_user_error, binder_set_stop_on_user_error, - param_get_int, &binder_stop_on_user_error, 0644); - -static __printf(2, 3) void binder_debug(int mask, const char *format, ...) -{ - struct va_format vaf; - va_list args; - - if (binder_debug_mask & mask) { - va_start(args, format); - vaf.va = &args; - vaf.fmt = format; - pr_info_ratelimited("%pV", &vaf); - va_end(args); - } -} - -#define binder_txn_error(x...) \ - binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, x) - -static __printf(1, 2) void binder_user_error(const char *format, ...) -{ - struct va_format vaf; - va_list args; - - if (binder_debug_mask & BINDER_DEBUG_USER_ERROR) { - va_start(args, format); - vaf.va = &args; - vaf.fmt = format; - pr_info_ratelimited("%pV", &vaf); - va_end(args); - } - - if (binder_stop_on_user_error) - binder_stop_on_user_error = 2; -} - -#define binder_set_extended_error(ee, _id, _command, _param) \ - do { \ - (ee)->id = _id; \ - (ee)->command = _command; \ - (ee)->param = _param; \ - } while (0) - -#define to_flat_binder_object(hdr) \ - container_of(hdr, struct flat_binder_object, hdr) - -#define to_binder_fd_object(hdr) container_of(hdr, struct binder_fd_object, hdr) - -#define to_binder_buffer_object(hdr) \ - container_of(hdr, struct binder_buffer_object, hdr) - -#define to_binder_fd_array_object(hdr) \ - container_of(hdr, struct binder_fd_array_object, hdr) - -static struct binder_stats binder_stats; - -static inline void binder_stats_deleted(enum binder_stat_types type) -{ - atomic_inc(&binder_stats.obj_deleted[type]); -} - -static inline void binder_stats_created(enum binder_stat_types type) -{ - atomic_inc(&binder_stats.obj_created[type]); -} - -struct binder_transaction_log_entry { - int debug_id; - int debug_id_done; - int call_type; - int from_proc; - int from_thread; - int target_handle; - int to_proc; - int to_thread; - int to_node; - int data_size; - int offsets_size; - int return_error_line; - uint32_t return_error; - uint32_t return_error_param; - char context_name[BINDERFS_MAX_NAME + 1]; -}; - -struct binder_transaction_log { - atomic_t cur; - bool full; - struct binder_transaction_log_entry entry[32]; -}; - -static struct binder_transaction_log binder_transaction_log; -static struct binder_transaction_log binder_transaction_log_failed; - -static struct binder_transaction_log_entry *binder_transaction_log_add( - struct binder_transaction_log *log) -{ - struct binder_transaction_log_entry *e; - unsigned int cur = atomic_inc_return(&log->cur); - - if (cur >= ARRAY_SIZE(log->entry)) - log->full = true; - e = &log->entry[cur % ARRAY_SIZE(log->entry)]; - WRITE_ONCE(e->debug_id_done, 0); - /* - * write-barrier to synchronize access to e->debug_id_done. - * We make sure the initialized 0 value is seen before - * memset() other fields are zeroed by memset. - */ - smp_wmb(); - memset(e, 0, sizeof(*e)); - return e; -} - -enum binder_deferred_state { - BINDER_DEFERRED_FLUSH = 0x01, - BINDER_DEFERRED_RELEASE = 0x02, -}; - -enum { - BINDER_LOOPER_STATE_REGISTERED = 0x01, - BINDER_LOOPER_STATE_ENTERED = 0x02, - BINDER_LOOPER_STATE_EXITED = 0x04, - BINDER_LOOPER_STATE_INVALID = 0x08, - BINDER_LOOPER_STATE_WAITING = 0x10, - BINDER_LOOPER_STATE_POLL = 0x20, -}; - -/** - * binder_proc_lock() - Acquire outer lock for given binder_proc - * @proc: struct binder_proc to acquire - * - * Acquires proc->outer_lock. Used to protect binder_ref - * structures associated with the given proc. - */ -#define binder_proc_lock(proc) _binder_proc_lock(proc, __LINE__) -static void -_binder_proc_lock(struct binder_proc *proc, int line) - __acquires(&proc->outer_lock) -{ - binder_debug(BINDER_DEBUG_SPINLOCKS, - "%s: line=%d\n", __func__, line); - spin_lock(&proc->outer_lock); -} - -/** - * binder_proc_unlock() - Release outer lock for given binder_proc - * @proc: struct binder_proc to acquire - * - * Release lock acquired via binder_proc_lock() - */ -#define binder_proc_unlock(proc) _binder_proc_unlock(proc, __LINE__) -static void -_binder_proc_unlock(struct binder_proc *proc, int line) - __releases(&proc->outer_lock) -{ - binder_debug(BINDER_DEBUG_SPINLOCKS, - "%s: line=%d\n", __func__, line); - spin_unlock(&proc->outer_lock); -} - -/** - * binder_inner_proc_lock() - Acquire inner lock for given binder_proc - * @proc: struct binder_proc to acquire - * - * Acquires proc->inner_lock. Used to protect todo lists - */ -#define binder_inner_proc_lock(proc) _binder_inner_proc_lock(proc, __LINE__) -static void -_binder_inner_proc_lock(struct binder_proc *proc, int line) - __acquires(&proc->inner_lock) -{ - binder_debug(BINDER_DEBUG_SPINLOCKS, - "%s: line=%d\n", __func__, line); - spin_lock(&proc->inner_lock); -} - -/** - * binder_inner_proc_unlock() - Release inner lock for given binder_proc - * @proc: struct binder_proc to acquire - * - * Release lock acquired via binder_inner_proc_lock() - */ -#define binder_inner_proc_unlock(proc) _binder_inner_proc_unlock(proc, __LINE__) -static void -_binder_inner_proc_unlock(struct binder_proc *proc, int line) - __releases(&proc->inner_lock) -{ - binder_debug(BINDER_DEBUG_SPINLOCKS, - "%s: line=%d\n", __func__, line); - spin_unlock(&proc->inner_lock); -} - -/** - * binder_node_lock() - Acquire spinlock for given binder_node - * @node: struct binder_node to acquire - * - * Acquires node->lock. Used to protect binder_node fields - */ -#define binder_node_lock(node) _binder_node_lock(node, __LINE__) -static void -_binder_node_lock(struct binder_node *node, int line) - __acquires(&node->lock) -{ - binder_debug(BINDER_DEBUG_SPINLOCKS, - "%s: line=%d\n", __func__, line); - spin_lock(&node->lock); -} - -/** - * binder_node_unlock() - Release spinlock for given binder_proc - * @node: struct binder_node to acquire - * - * Release lock acquired via binder_node_lock() - */ -#define binder_node_unlock(node) _binder_node_unlock(node, __LINE__) -static void -_binder_node_unlock(struct binder_node *node, int line) - __releases(&node->lock) -{ - binder_debug(BINDER_DEBUG_SPINLOCKS, - "%s: line=%d\n", __func__, line); - spin_unlock(&node->lock); -} - -/** - * binder_node_inner_lock() - Acquire node and inner locks - * @node: struct binder_node to acquire - * - * Acquires node->lock. If node->proc also acquires - * proc->inner_lock. Used to protect binder_node fields - */ -#define binder_node_inner_lock(node) _binder_node_inner_lock(node, __LINE__) -static void -_binder_node_inner_lock(struct binder_node *node, int line) - __acquires(&node->lock) __acquires(&node->proc->inner_lock) -{ - binder_debug(BINDER_DEBUG_SPINLOCKS, - "%s: line=%d\n", __func__, line); - spin_lock(&node->lock); - if (node->proc) - binder_inner_proc_lock(node->proc); - else - /* annotation for sparse */ - __acquire(&node->proc->inner_lock); -} - -/** - * binder_node_inner_unlock() - Release node and inner locks - * @node: struct binder_node to acquire - * - * Release lock acquired via binder_node_lock() - */ -#define binder_node_inner_unlock(node) _binder_node_inner_unlock(node, __LINE__) -static void -_binder_node_inner_unlock(struct binder_node *node, int line) - __releases(&node->lock) __releases(&node->proc->inner_lock) -{ - struct binder_proc *proc = node->proc; - - binder_debug(BINDER_DEBUG_SPINLOCKS, - "%s: line=%d\n", __func__, line); - if (proc) - binder_inner_proc_unlock(proc); - else - /* annotation for sparse */ - __release(&node->proc->inner_lock); - spin_unlock(&node->lock); -} - -static bool binder_worklist_empty_ilocked(struct list_head *list) -{ - return list_empty(list); -} - -/** - * binder_worklist_empty() - Check if no items on the work list - * @proc: binder_proc associated with list - * @list: list to check - * - * Return: true if there are no items on list, else false - */ -static bool binder_worklist_empty(struct binder_proc *proc, - struct list_head *list) -{ - bool ret; - - binder_inner_proc_lock(proc); - ret = binder_worklist_empty_ilocked(list); - binder_inner_proc_unlock(proc); - return ret; -} - -/** - * binder_enqueue_work_ilocked() - Add an item to the work list - * @work: struct binder_work to add to list - * @target_list: list to add work to - * - * Adds the work to the specified list. Asserts that work - * is not already on a list. - * - * Requires the proc->inner_lock to be held. - */ -static void -binder_enqueue_work_ilocked(struct binder_work *work, - struct list_head *target_list) -{ - BUG_ON(target_list == NULL); - BUG_ON(work->entry.next && !list_empty(&work->entry)); - list_add_tail(&work->entry, target_list); -} - -/** - * binder_enqueue_deferred_thread_work_ilocked() - Add deferred thread work - * @thread: thread to queue work to - * @work: struct binder_work to add to list - * - * Adds the work to the todo list of the thread. Doesn't set the process_todo - * flag, which means that (if it wasn't already set) the thread will go to - * sleep without handling this work when it calls read. - * - * Requires the proc->inner_lock to be held. - */ -static void -binder_enqueue_deferred_thread_work_ilocked(struct binder_thread *thread, - struct binder_work *work) -{ - WARN_ON(!list_empty(&thread->waiting_thread_node)); - binder_enqueue_work_ilocked(work, &thread->todo); -} - -/** - * binder_enqueue_thread_work_ilocked() - Add an item to the thread work list - * @thread: thread to queue work to - * @work: struct binder_work to add to list - * - * Adds the work to the todo list of the thread, and enables processing - * of the todo queue. - * - * Requires the proc->inner_lock to be held. - */ -static void -binder_enqueue_thread_work_ilocked(struct binder_thread *thread, - struct binder_work *work) -{ - WARN_ON(!list_empty(&thread->waiting_thread_node)); - binder_enqueue_work_ilocked(work, &thread->todo); - - /* (e)poll-based threads require an explicit wakeup signal when - * queuing their own work; they rely on these events to consume - * messages without I/O block. Without it, threads risk waiting - * indefinitely without handling the work. - */ - if (thread->looper & BINDER_LOOPER_STATE_POLL && - thread->pid == current->pid && !thread->process_todo) - wake_up_interruptible_sync(&thread->wait); - - thread->process_todo = true; -} - -/** - * binder_enqueue_thread_work() - Add an item to the thread work list - * @thread: thread to queue work to - * @work: struct binder_work to add to list - * - * Adds the work to the todo list of the thread, and enables processing - * of the todo queue. - */ -static void -binder_enqueue_thread_work(struct binder_thread *thread, - struct binder_work *work) -{ - binder_inner_proc_lock(thread->proc); - binder_enqueue_thread_work_ilocked(thread, work); - binder_inner_proc_unlock(thread->proc); -} - -static void -binder_dequeue_work_ilocked(struct binder_work *work) -{ - list_del_init(&work->entry); -} - -/** - * binder_dequeue_work() - Removes an item from the work list - * @proc: binder_proc associated with list - * @work: struct binder_work to remove from list - * - * Removes the specified work item from whatever list it is on. - * Can safely be called if work is not on any list. - */ -static void -binder_dequeue_work(struct binder_proc *proc, struct binder_work *work) -{ - binder_inner_proc_lock(proc); - binder_dequeue_work_ilocked(work); - binder_inner_proc_unlock(proc); -} - -static struct binder_work *binder_dequeue_work_head_ilocked( - struct list_head *list) -{ - struct binder_work *w; - - w = list_first_entry_or_null(list, struct binder_work, entry); - if (w) - list_del_init(&w->entry); - return w; -} - -static void -binder_defer_work(struct binder_proc *proc, enum binder_deferred_state defer); -static void binder_free_thread(struct binder_thread *thread); -static void binder_free_proc(struct binder_proc *proc); -static void binder_inc_node_tmpref_ilocked(struct binder_node *node); - -static bool binder_has_work_ilocked(struct binder_thread *thread, - bool do_proc_work) -{ - return thread->process_todo || - thread->looper_need_return || - (do_proc_work && - !binder_worklist_empty_ilocked(&thread->proc->todo)); -} - -static bool binder_has_work(struct binder_thread *thread, bool do_proc_work) -{ - bool has_work; - - binder_inner_proc_lock(thread->proc); - has_work = binder_has_work_ilocked(thread, do_proc_work); - binder_inner_proc_unlock(thread->proc); - - return has_work; -} - -static bool binder_available_for_proc_work_ilocked(struct binder_thread *thread) -{ - return !thread->transaction_stack && - binder_worklist_empty_ilocked(&thread->todo); -} - -static void binder_wakeup_poll_threads_ilocked(struct binder_proc *proc, - bool sync) -{ - struct rb_node *n; - struct binder_thread *thread; - - for (n = rb_first(&proc->threads); n != NULL; n = rb_next(n)) { - thread = rb_entry(n, struct binder_thread, rb_node); - if (thread->looper & BINDER_LOOPER_STATE_POLL && - binder_available_for_proc_work_ilocked(thread)) { - if (sync) - wake_up_interruptible_sync(&thread->wait); - else - wake_up_interruptible(&thread->wait); - } - } -} - -/** - * binder_select_thread_ilocked() - selects a thread for doing proc work. - * @proc: process to select a thread from - * - * Note that calling this function moves the thread off the waiting_threads - * list, so it can only be woken up by the caller of this function, or a - * signal. Therefore, callers *should* always wake up the thread this function - * returns. - * - * Return: If there's a thread currently waiting for process work, - * returns that thread. Otherwise returns NULL. - */ -static struct binder_thread * -binder_select_thread_ilocked(struct binder_proc *proc) -{ - struct binder_thread *thread; - - assert_spin_locked(&proc->inner_lock); - thread = list_first_entry_or_null(&proc->waiting_threads, - struct binder_thread, - waiting_thread_node); - - if (thread) - list_del_init(&thread->waiting_thread_node); - - return thread; -} - -/** - * binder_wakeup_thread_ilocked() - wakes up a thread for doing proc work. - * @proc: process to wake up a thread in - * @thread: specific thread to wake-up (may be NULL) - * @sync: whether to do a synchronous wake-up - * - * This function wakes up a thread in the @proc process. - * The caller may provide a specific thread to wake-up in - * the @thread parameter. If @thread is NULL, this function - * will wake up threads that have called poll(). - * - * Note that for this function to work as expected, callers - * should first call binder_select_thread() to find a thread - * to handle the work (if they don't have a thread already), - * and pass the result into the @thread parameter. - */ -static void binder_wakeup_thread_ilocked(struct binder_proc *proc, - struct binder_thread *thread, - bool sync) -{ - assert_spin_locked(&proc->inner_lock); - - if (thread) { - if (sync) - wake_up_interruptible_sync(&thread->wait); - else - wake_up_interruptible(&thread->wait); - return; - } - - /* Didn't find a thread waiting for proc work; this can happen - * in two scenarios: - * 1. All threads are busy handling transactions - * In that case, one of those threads should call back into - * the kernel driver soon and pick up this work. - * 2. Threads are using the (e)poll interface, in which case - * they may be blocked on the waitqueue without having been - * added to waiting_threads. For this case, we just iterate - * over all threads not handling transaction work, and - * wake them all up. We wake all because we don't know whether - * a thread that called into (e)poll is handling non-binder - * work currently. - */ - binder_wakeup_poll_threads_ilocked(proc, sync); -} - -static void binder_wakeup_proc_ilocked(struct binder_proc *proc) -{ - struct binder_thread *thread = binder_select_thread_ilocked(proc); - - binder_wakeup_thread_ilocked(proc, thread, /* sync = */false); -} - -static void binder_set_nice(long nice) -{ - long min_nice; - - if (can_nice(current, nice)) { - set_user_nice(current, nice); - return; - } - min_nice = rlimit_to_nice(rlimit(RLIMIT_NICE)); - binder_debug(BINDER_DEBUG_PRIORITY_CAP, - "%d: nice value %ld not allowed use %ld instead\n", - current->pid, nice, min_nice); - set_user_nice(current, min_nice); - if (min_nice <= MAX_NICE) - return; - binder_user_error("%d RLIMIT_NICE not set\n", current->pid); -} - -static struct binder_node *binder_get_node_ilocked(struct binder_proc *proc, - binder_uintptr_t ptr) -{ - struct rb_node *n = proc->nodes.rb_node; - struct binder_node *node; - - assert_spin_locked(&proc->inner_lock); - - while (n) { - node = rb_entry(n, struct binder_node, rb_node); - - if (ptr < node->ptr) - n = n->rb_left; - else if (ptr > node->ptr) - n = n->rb_right; - else { - /* - * take an implicit weak reference - * to ensure node stays alive until - * call to binder_put_node() - */ - binder_inc_node_tmpref_ilocked(node); - return node; - } - } - return NULL; -} - -static struct binder_node *binder_get_node(struct binder_proc *proc, - binder_uintptr_t ptr) -{ - struct binder_node *node; - - binder_inner_proc_lock(proc); - node = binder_get_node_ilocked(proc, ptr); - binder_inner_proc_unlock(proc); - return node; -} - -static struct binder_node *binder_init_node_ilocked( - struct binder_proc *proc, - struct binder_node *new_node, - struct flat_binder_object *fp) -{ - struct rb_node **p = &proc->nodes.rb_node; - struct rb_node *parent = NULL; - struct binder_node *node; - binder_uintptr_t ptr = fp ? fp->binder : 0; - binder_uintptr_t cookie = fp ? fp->cookie : 0; - __u32 flags = fp ? fp->flags : 0; - - assert_spin_locked(&proc->inner_lock); - - while (*p) { - - parent = *p; - node = rb_entry(parent, struct binder_node, rb_node); - - if (ptr < node->ptr) - p = &(*p)->rb_left; - else if (ptr > node->ptr) - p = &(*p)->rb_right; - else { - /* - * A matching node is already in - * the rb tree. Abandon the init - * and return it. - */ - binder_inc_node_tmpref_ilocked(node); - return node; - } - } - node = new_node; - binder_stats_created(BINDER_STAT_NODE); - node->tmp_refs++; - rb_link_node(&node->rb_node, parent, p); - rb_insert_color(&node->rb_node, &proc->nodes); - node->debug_id = atomic_inc_return(&binder_last_id); - node->proc = proc; - node->ptr = ptr; - node->cookie = cookie; - node->work.type = BINDER_WORK_NODE; - node->min_priority = flags & FLAT_BINDER_FLAG_PRIORITY_MASK; - node->accept_fds = !!(flags & FLAT_BINDER_FLAG_ACCEPTS_FDS); - node->txn_security_ctx = !!(flags & FLAT_BINDER_FLAG_TXN_SECURITY_CTX); - spin_lock_init(&node->lock); - INIT_LIST_HEAD(&node->work.entry); - INIT_LIST_HEAD(&node->async_todo); - binder_debug(BINDER_DEBUG_INTERNAL_REFS, - "%d:%d node %d u%016llx c%016llx created\n", - proc->pid, current->pid, node->debug_id, - (u64)node->ptr, (u64)node->cookie); - - return node; -} - -static struct binder_node *binder_new_node(struct binder_proc *proc, - struct flat_binder_object *fp) -{ - struct binder_node *node; - struct binder_node *new_node = kzalloc_obj(*node); - - if (!new_node) - return NULL; - binder_inner_proc_lock(proc); - node = binder_init_node_ilocked(proc, new_node, fp); - binder_inner_proc_unlock(proc); - if (node != new_node) - /* - * The node was already added by another thread - */ - kfree(new_node); - - return node; -} - -static void binder_free_node(struct binder_node *node) -{ - kfree(node); - binder_stats_deleted(BINDER_STAT_NODE); -} - -static int binder_inc_node_nilocked(struct binder_node *node, int strong, - int internal, - struct list_head *target_list) -{ - struct binder_proc *proc = node->proc; - - assert_spin_locked(&node->lock); - if (proc) - assert_spin_locked(&proc->inner_lock); - if (strong) { - if (internal) { - if (target_list == NULL && - node->internal_strong_refs == 0 && - !(node->proc && - node == node->proc->context->binder_context_mgr_node && - node->has_strong_ref)) { - pr_err("invalid inc strong node for %d\n", - node->debug_id); - return -EINVAL; - } - node->internal_strong_refs++; - } else - node->local_strong_refs++; - if (!node->has_strong_ref && target_list) { - struct binder_thread *thread = container_of(target_list, - struct binder_thread, todo); - binder_dequeue_work_ilocked(&node->work); - BUG_ON(&thread->todo != target_list); - binder_enqueue_deferred_thread_work_ilocked(thread, - &node->work); - } - } else { - if (!internal) - node->local_weak_refs++; - if (!node->has_weak_ref && target_list && list_empty(&node->work.entry)) - binder_enqueue_work_ilocked(&node->work, target_list); - } - return 0; -} - -static int binder_inc_node(struct binder_node *node, int strong, int internal, - struct list_head *target_list) -{ - int ret; - - binder_node_inner_lock(node); - ret = binder_inc_node_nilocked(node, strong, internal, target_list); - binder_node_inner_unlock(node); - - return ret; -} - -static bool binder_dec_node_nilocked(struct binder_node *node, - int strong, int internal) -{ - struct binder_proc *proc = node->proc; - - assert_spin_locked(&node->lock); - if (proc) - assert_spin_locked(&proc->inner_lock); - if (strong) { - if (internal) - node->internal_strong_refs--; - else - node->local_strong_refs--; - if (node->local_strong_refs || node->internal_strong_refs) - return false; - } else { - if (!internal) - node->local_weak_refs--; - if (node->local_weak_refs || node->tmp_refs || - !hlist_empty(&node->refs)) - return false; - } - - if (proc && (node->has_strong_ref || node->has_weak_ref)) { - if (list_empty(&node->work.entry)) { - binder_enqueue_work_ilocked(&node->work, &proc->todo); - binder_wakeup_proc_ilocked(proc); - } - } else { - if (hlist_empty(&node->refs) && !node->local_strong_refs && - !node->local_weak_refs && !node->tmp_refs) { - if (proc) { - binder_dequeue_work_ilocked(&node->work); - rb_erase(&node->rb_node, &proc->nodes); - binder_debug(BINDER_DEBUG_INTERNAL_REFS, - "refless node %d deleted\n", - node->debug_id); - } else { - BUG_ON(!list_empty(&node->work.entry)); - spin_lock(&binder_dead_nodes_lock); - /* - * tmp_refs could have changed so - * check it again - */ - if (node->tmp_refs) { - spin_unlock(&binder_dead_nodes_lock); - return false; - } - hlist_del(&node->dead_node); - spin_unlock(&binder_dead_nodes_lock); - binder_debug(BINDER_DEBUG_INTERNAL_REFS, - "dead node %d deleted\n", - node->debug_id); - } - return true; - } - } - return false; -} - -static void binder_dec_node(struct binder_node *node, int strong, int internal) -{ - bool free_node; - - binder_node_inner_lock(node); - free_node = binder_dec_node_nilocked(node, strong, internal); - binder_node_inner_unlock(node); - if (free_node) - binder_free_node(node); -} - -static void binder_inc_node_tmpref_ilocked(struct binder_node *node) -{ - /* - * No call to binder_inc_node() is needed since we - * don't need to inform userspace of any changes to - * tmp_refs - */ - node->tmp_refs++; -} - -/** - * binder_inc_node_tmpref() - take a temporary reference on node - * @node: node to reference - * - * Take reference on node to prevent the node from being freed - * while referenced only by a local variable. The inner lock is - * needed to serialize with the node work on the queue (which - * isn't needed after the node is dead). If the node is dead - * (node->proc is NULL), use binder_dead_nodes_lock to protect - * node->tmp_refs against dead-node-only cases where the node - * lock cannot be acquired (eg traversing the dead node list to - * print nodes) - */ -static void binder_inc_node_tmpref(struct binder_node *node) -{ - binder_node_lock(node); - if (node->proc) - binder_inner_proc_lock(node->proc); - else - spin_lock(&binder_dead_nodes_lock); - binder_inc_node_tmpref_ilocked(node); - if (node->proc) - binder_inner_proc_unlock(node->proc); - else - spin_unlock(&binder_dead_nodes_lock); - binder_node_unlock(node); -} - -/** - * binder_dec_node_tmpref() - remove a temporary reference on node - * @node: node to reference - * - * Release temporary reference on node taken via binder_inc_node_tmpref() - */ -static void binder_dec_node_tmpref(struct binder_node *node) -{ - bool free_node; - - binder_node_inner_lock(node); - if (!node->proc) - spin_lock(&binder_dead_nodes_lock); - else - __acquire(&binder_dead_nodes_lock); - node->tmp_refs--; - BUG_ON(node->tmp_refs < 0); - if (!node->proc) - spin_unlock(&binder_dead_nodes_lock); - else - __release(&binder_dead_nodes_lock); - /* - * Call binder_dec_node() to check if all refcounts are 0 - * and cleanup is needed. Calling with strong=0 and internal=1 - * causes no actual reference to be released in binder_dec_node(). - * If that changes, a change is needed here too. - */ - free_node = binder_dec_node_nilocked(node, 0, 1); - binder_node_inner_unlock(node); - if (free_node) - binder_free_node(node); -} - -static void binder_put_node(struct binder_node *node) -{ - binder_dec_node_tmpref(node); -} - -static struct binder_ref *binder_get_ref_olocked(struct binder_proc *proc, - u32 desc, bool need_strong_ref) -{ - struct rb_node *n = proc->refs_by_desc.rb_node; - struct binder_ref *ref; - - while (n) { - ref = rb_entry(n, struct binder_ref, rb_node_desc); - - if (desc < ref->data.desc) { - n = n->rb_left; - } else if (desc > ref->data.desc) { - n = n->rb_right; - } else if (need_strong_ref && !ref->data.strong) { - binder_user_error("tried to use weak ref as strong ref\n"); - return NULL; - } else { - return ref; - } - } - return NULL; -} - -/* Find the smallest unused descriptor the "slow way" */ -static u32 slow_desc_lookup_olocked(struct binder_proc *proc, u32 offset) -{ - struct binder_ref *ref; - struct rb_node *n; - u32 desc; - - desc = offset; - for (n = rb_first(&proc->refs_by_desc); n; n = rb_next(n)) { - ref = rb_entry(n, struct binder_ref, rb_node_desc); - if (ref->data.desc > desc) - break; - desc = ref->data.desc + 1; - } - - return desc; -} - -/* - * Find an available reference descriptor ID. The proc->outer_lock might - * be released in the process, in which case -EAGAIN is returned and the - * @desc should be considered invalid. - */ -static int get_ref_desc_olocked(struct binder_proc *proc, - struct binder_node *node, - u32 *desc) -{ - struct dbitmap *dmap = &proc->dmap; - unsigned int nbits, offset; - unsigned long *new, bit; - - /* 0 is reserved for the context manager */ - offset = (node == proc->context->binder_context_mgr_node) ? 0 : 1; - - if (!dbitmap_enabled(dmap)) { - *desc = slow_desc_lookup_olocked(proc, offset); - return 0; - } - - if (dbitmap_acquire_next_zero_bit(dmap, offset, &bit) == 0) { - *desc = bit; - return 0; - } - - /* - * The dbitmap is full and needs to grow. The proc->outer_lock - * is briefly released to allocate the new bitmap safely. - */ - nbits = dbitmap_grow_nbits(dmap); - binder_proc_unlock(proc); - new = bitmap_zalloc(nbits, GFP_KERNEL); - binder_proc_lock(proc); - dbitmap_grow(dmap, new, nbits); - - return -EAGAIN; -} - -/** - * binder_get_ref_for_node_olocked() - get the ref associated with given node - * @proc: binder_proc that owns the ref - * @node: binder_node of target - * @new_ref: newly allocated binder_ref to be initialized or %NULL - * - * Look up the ref for the given node and return it if it exists - * - * If it doesn't exist and the caller provides a newly allocated - * ref, initialize the fields of the newly allocated ref and insert - * into the given proc rb_trees and node refs list. - * - * Return: the ref for node. It is possible that another thread - * allocated/initialized the ref first in which case the - * returned ref would be different than the passed-in - * new_ref. new_ref must be kfree'd by the caller in - * this case. - */ -static struct binder_ref *binder_get_ref_for_node_olocked( - struct binder_proc *proc, - struct binder_node *node, - struct binder_ref *new_ref) -{ - struct binder_ref *ref; - struct rb_node *parent; - struct rb_node **p; - u32 desc; - -retry: - p = &proc->refs_by_node.rb_node; - parent = NULL; - while (*p) { - parent = *p; - ref = rb_entry(parent, struct binder_ref, rb_node_node); - - if (node < ref->node) - p = &(*p)->rb_left; - else if (node > ref->node) - p = &(*p)->rb_right; - else - return ref; - } - if (!new_ref) - return NULL; - - /* might release the proc->outer_lock */ - if (get_ref_desc_olocked(proc, node, &desc) == -EAGAIN) - goto retry; - - binder_stats_created(BINDER_STAT_REF); - new_ref->data.debug_id = atomic_inc_return(&binder_last_id); - new_ref->proc = proc; - new_ref->node = node; - rb_link_node(&new_ref->rb_node_node, parent, p); - rb_insert_color(&new_ref->rb_node_node, &proc->refs_by_node); - - new_ref->data.desc = desc; - p = &proc->refs_by_desc.rb_node; - while (*p) { - parent = *p; - ref = rb_entry(parent, struct binder_ref, rb_node_desc); - - if (new_ref->data.desc < ref->data.desc) - p = &(*p)->rb_left; - else if (new_ref->data.desc > ref->data.desc) - p = &(*p)->rb_right; - else - BUG(); - } - rb_link_node(&new_ref->rb_node_desc, parent, p); - rb_insert_color(&new_ref->rb_node_desc, &proc->refs_by_desc); - - binder_node_lock(node); - hlist_add_head(&new_ref->node_entry, &node->refs); - - binder_debug(BINDER_DEBUG_INTERNAL_REFS, - "%d new ref %d desc %d for node %d\n", - proc->pid, new_ref->data.debug_id, new_ref->data.desc, - node->debug_id); - binder_node_unlock(node); - return new_ref; -} - -static void binder_cleanup_ref_olocked(struct binder_ref *ref) -{ - struct dbitmap *dmap = &ref->proc->dmap; - bool delete_node = false; - - binder_debug(BINDER_DEBUG_INTERNAL_REFS, - "%d delete ref %d desc %d for node %d\n", - ref->proc->pid, ref->data.debug_id, ref->data.desc, - ref->node->debug_id); - - if (dbitmap_enabled(dmap)) - dbitmap_clear_bit(dmap, ref->data.desc); - rb_erase(&ref->rb_node_desc, &ref->proc->refs_by_desc); - rb_erase(&ref->rb_node_node, &ref->proc->refs_by_node); - - binder_node_inner_lock(ref->node); - if (ref->data.strong) - binder_dec_node_nilocked(ref->node, 1, 1); - - hlist_del(&ref->node_entry); - delete_node = binder_dec_node_nilocked(ref->node, 0, 1); - binder_node_inner_unlock(ref->node); - /* - * Clear ref->node unless we want the caller to free the node - */ - if (!delete_node) { - /* - * The caller uses ref->node to determine - * whether the node needs to be freed. Clear - * it since the node is still alive. - */ - ref->node = NULL; - } - - if (ref->death) { - binder_debug(BINDER_DEBUG_DEAD_BINDER, - "%d delete ref %d desc %d has death notification\n", - ref->proc->pid, ref->data.debug_id, - ref->data.desc); - binder_dequeue_work(ref->proc, &ref->death->work); - binder_stats_deleted(BINDER_STAT_DEATH); - } - - if (ref->freeze) { - binder_dequeue_work(ref->proc, &ref->freeze->work); - binder_stats_deleted(BINDER_STAT_FREEZE); - } - - binder_stats_deleted(BINDER_STAT_REF); -} - -/** - * binder_inc_ref_olocked() - increment the ref for given handle - * @ref: ref to be incremented - * @strong: if true, strong increment, else weak - * @target_list: list to queue node work on - * - * Increment the ref. @ref->proc->outer_lock must be held on entry - * - * Return: 0, if successful, else errno - */ -static int binder_inc_ref_olocked(struct binder_ref *ref, int strong, - struct list_head *target_list) -{ - int ret; - - if (strong) { - if (ref->data.strong == 0) { - ret = binder_inc_node(ref->node, 1, 1, target_list); - if (ret) - return ret; - } - ref->data.strong++; - } else { - if (ref->data.weak == 0) { - ret = binder_inc_node(ref->node, 0, 1, target_list); - if (ret) - return ret; - } - ref->data.weak++; - } - return 0; -} - -/** - * binder_dec_ref_olocked() - dec the ref for given handle - * @ref: ref to be decremented - * @strong: if true, strong decrement, else weak - * - * Decrement the ref. - * - * Return: %true if ref is cleaned up and ready to be freed. - */ -static bool binder_dec_ref_olocked(struct binder_ref *ref, int strong) -{ - if (strong) { - if (ref->data.strong == 0) { - binder_user_error("%d invalid dec strong, ref %d desc %d s %d w %d\n", - ref->proc->pid, ref->data.debug_id, - ref->data.desc, ref->data.strong, - ref->data.weak); - return false; - } - ref->data.strong--; - if (ref->data.strong == 0) - binder_dec_node(ref->node, strong, 1); - } else { - if (ref->data.weak == 0) { - binder_user_error("%d invalid dec weak, ref %d desc %d s %d w %d\n", - ref->proc->pid, ref->data.debug_id, - ref->data.desc, ref->data.strong, - ref->data.weak); - return false; - } - ref->data.weak--; - } - if (ref->data.strong == 0 && ref->data.weak == 0) { - binder_cleanup_ref_olocked(ref); - return true; - } - return false; -} - -/** - * binder_get_node_from_ref() - get the node from the given proc/desc - * @proc: proc containing the ref - * @desc: the handle associated with the ref - * @need_strong_ref: if true, only return node if ref is strong - * @rdata: the id/refcount data for the ref - * - * Given a proc and ref handle, return the associated binder_node - * - * Return: a binder_node or NULL if not found or not strong when strong required - */ -static struct binder_node *binder_get_node_from_ref( - struct binder_proc *proc, - u32 desc, bool need_strong_ref, - struct binder_ref_data *rdata) -{ - struct binder_node *node; - struct binder_ref *ref; - - binder_proc_lock(proc); - ref = binder_get_ref_olocked(proc, desc, need_strong_ref); - if (!ref) - goto err_no_ref; - node = ref->node; - /* - * Take an implicit reference on the node to ensure - * it stays alive until the call to binder_put_node() - */ - binder_inc_node_tmpref(node); - if (rdata) - *rdata = ref->data; - binder_proc_unlock(proc); - - return node; - -err_no_ref: - binder_proc_unlock(proc); - return NULL; -} - -/** - * binder_free_ref() - free the binder_ref - * @ref: ref to free - * - * Free the binder_ref. Free the binder_node indicated by ref->node - * (if non-NULL) and the binder_ref_death indicated by ref->death. - */ -static void binder_free_ref(struct binder_ref *ref) -{ - if (ref->node) - binder_free_node(ref->node); - kfree(ref->death); - kfree(ref->freeze); - kfree(ref); -} - -/* shrink descriptor bitmap if needed */ -static void try_shrink_dmap(struct binder_proc *proc) -{ - unsigned long *new; - int nbits; - - binder_proc_lock(proc); - nbits = dbitmap_shrink_nbits(&proc->dmap); - binder_proc_unlock(proc); - - if (!nbits) - return; - - new = bitmap_zalloc(nbits, GFP_KERNEL); - binder_proc_lock(proc); - dbitmap_shrink(&proc->dmap, new, nbits); - binder_proc_unlock(proc); -} - -/** - * binder_update_ref_for_handle() - inc/dec the ref for given handle - * @proc: proc containing the ref - * @desc: the handle associated with the ref - * @increment: true=inc reference, false=dec reference - * @strong: true=strong reference, false=weak reference - * @rdata: the id/refcount data for the ref - * - * Given a proc and ref handle, increment or decrement the ref - * according to "increment" arg. - * - * Return: 0 if successful, else errno - */ -static int binder_update_ref_for_handle(struct binder_proc *proc, - uint32_t desc, bool increment, bool strong, - struct binder_ref_data *rdata) -{ - int ret = 0; - struct binder_ref *ref; - bool delete_ref = false; - - binder_proc_lock(proc); - ref = binder_get_ref_olocked(proc, desc, strong); - if (!ref) { - ret = -EINVAL; - goto err_no_ref; - } - if (increment) - ret = binder_inc_ref_olocked(ref, strong, NULL); - else - delete_ref = binder_dec_ref_olocked(ref, strong); - - if (rdata) - *rdata = ref->data; - binder_proc_unlock(proc); - - if (delete_ref) { - binder_free_ref(ref); - try_shrink_dmap(proc); - } - return ret; - -err_no_ref: - binder_proc_unlock(proc); - return ret; -} - -/** - * binder_dec_ref_for_handle() - dec the ref for given handle - * @proc: proc containing the ref - * @desc: the handle associated with the ref - * @strong: true=strong reference, false=weak reference - * @rdata: the id/refcount data for the ref - * - * Just calls binder_update_ref_for_handle() to decrement the ref. - * - * Return: 0 if successful, else errno - */ -static int binder_dec_ref_for_handle(struct binder_proc *proc, - uint32_t desc, bool strong, struct binder_ref_data *rdata) -{ - return binder_update_ref_for_handle(proc, desc, false, strong, rdata); -} - - -/** - * binder_inc_ref_for_node() - increment the ref for given proc/node - * @proc: proc containing the ref - * @node: target node - * @strong: true=strong reference, false=weak reference - * @target_list: worklist to use if node is incremented - * @rdata: the id/refcount data for the ref - * - * Given a proc and node, increment the ref. Create the ref if it - * doesn't already exist - * - * Return: 0 if successful, else errno - */ -static int binder_inc_ref_for_node(struct binder_proc *proc, - struct binder_node *node, - bool strong, - struct list_head *target_list, - struct binder_ref_data *rdata) -{ - struct binder_ref *ref; - struct binder_ref *new_ref = NULL; - int ret = 0; - - binder_proc_lock(proc); - ref = binder_get_ref_for_node_olocked(proc, node, NULL); - if (!ref) { - binder_proc_unlock(proc); - new_ref = kzalloc_obj(*ref); - if (!new_ref) - return -ENOMEM; - binder_proc_lock(proc); - ref = binder_get_ref_for_node_olocked(proc, node, new_ref); - } - ret = binder_inc_ref_olocked(ref, strong, target_list); - *rdata = ref->data; - if (ret && ref == new_ref) { - /* - * Cleanup the failed reference here as the target - * could now be dead and have already released its - * references by now. Calling on the new reference - * with strong=0 and a tmp_refs will not decrement - * the node. The new_ref gets kfree'd below. - */ - binder_cleanup_ref_olocked(new_ref); - ref = NULL; - } - - binder_proc_unlock(proc); - if (new_ref && ref != new_ref) - /* - * Another thread created the ref first so - * free the one we allocated - */ - kfree(new_ref); - return ret; -} - -static void binder_pop_transaction_ilocked(struct binder_thread *target_thread, - struct binder_transaction *t) -{ - BUG_ON(!target_thread); - assert_spin_locked(&target_thread->proc->inner_lock); - BUG_ON(target_thread->transaction_stack != t); - BUG_ON(target_thread->transaction_stack->from != target_thread); - target_thread->transaction_stack = - target_thread->transaction_stack->from_parent; - t->from = NULL; -} - -/** - * binder_thread_dec_tmpref() - decrement thread->tmp_ref - * @thread: thread to decrement - * - * A thread needs to be kept alive while being used to create or - * handle a transaction. binder_get_txn_from() is used to safely - * extract t->from from a binder_transaction and keep the thread - * indicated by t->from from being freed. When done with that - * binder_thread, this function is called to decrement the - * tmp_ref and free if appropriate (thread has been released - * and no transaction being processed by the driver) - */ -static void binder_thread_dec_tmpref(struct binder_thread *thread) -{ - /* - * atomic is used to protect the counter value while - * it cannot reach zero or thread->is_dead is false - */ - binder_inner_proc_lock(thread->proc); - atomic_dec(&thread->tmp_ref); - if (thread->is_dead && !atomic_read(&thread->tmp_ref)) { - binder_inner_proc_unlock(thread->proc); - binder_free_thread(thread); - return; - } - binder_inner_proc_unlock(thread->proc); -} - -/** - * binder_proc_dec_tmpref() - decrement proc->tmp_ref - * @proc: proc to decrement - * - * A binder_proc needs to be kept alive while being used to create or - * handle a transaction. proc->tmp_ref is incremented when - * creating a new transaction or the binder_proc is currently in-use - * by threads that are being released. When done with the binder_proc, - * this function is called to decrement the counter and free the - * proc if appropriate (proc has been released, all threads have - * been released and not currently in-use to process a transaction). - */ -static void binder_proc_dec_tmpref(struct binder_proc *proc) -{ - binder_inner_proc_lock(proc); - proc->tmp_ref--; - if (proc->is_dead && RB_EMPTY_ROOT(&proc->threads) && - !proc->tmp_ref) { - binder_inner_proc_unlock(proc); - binder_free_proc(proc); - return; - } - binder_inner_proc_unlock(proc); -} - -/** - * binder_get_txn_from() - safely extract the "from" thread in transaction - * @t: binder transaction for t->from - * - * Atomically return the "from" thread and increment the tmp_ref - * count for the thread to ensure it stays alive until - * binder_thread_dec_tmpref() is called. - * - * Return: the value of t->from - */ -static struct binder_thread *binder_get_txn_from( - struct binder_transaction *t) -{ - struct binder_thread *from; - - guard(spinlock)(&t->lock); - from = t->from; - if (from) - atomic_inc(&from->tmp_ref); - return from; -} - -/** - * binder_get_txn_from_and_acq_inner() - get t->from and acquire inner lock - * @t: binder transaction for t->from - * - * Same as binder_get_txn_from() except it also acquires the proc->inner_lock - * to guarantee that the thread cannot be released while operating on it. - * The caller must call binder_inner_proc_unlock() to release the inner lock - * as well as call binder_dec_thread_txn() to release the reference. - * - * Return: the value of t->from - */ -static struct binder_thread *binder_get_txn_from_and_acq_inner( - struct binder_transaction *t) - __acquires(&t->from->proc->inner_lock) -{ - struct binder_thread *from; - - from = binder_get_txn_from(t); - if (!from) { - __acquire(&from->proc->inner_lock); - return NULL; - } - binder_inner_proc_lock(from->proc); - if (t->from) { - BUG_ON(from != t->from); - return from; - } - binder_inner_proc_unlock(from->proc); - __acquire(&from->proc->inner_lock); - binder_thread_dec_tmpref(from); - return NULL; -} - -/** - * binder_free_txn_fixups() - free unprocessed fd fixups - * @t: binder transaction for t->from - * - * If the transaction is being torn down prior to being - * processed by the target process, free all of the - * fd fixups and fput the file structs. It is safe to - * call this function after the fixups have been - * processed -- in that case, the list will be empty. - */ -static void binder_free_txn_fixups(struct binder_transaction *t) -{ - struct binder_txn_fd_fixup *fixup, *tmp; - - list_for_each_entry_safe(fixup, tmp, &t->fd_fixups, fixup_entry) { - fput(fixup->file); - if (fixup->target_fd >= 0) - put_unused_fd(fixup->target_fd); - list_del(&fixup->fixup_entry); - kfree(fixup); - } -} - -static void binder_txn_latency_free(struct binder_transaction *t) -{ - int from_proc, from_thread, to_proc, to_thread; - - spin_lock(&t->lock); - from_proc = t->from ? t->from->proc->pid : 0; - from_thread = t->from ? t->from->pid : 0; - to_proc = t->to_proc ? t->to_proc->pid : 0; - to_thread = t->to_thread ? t->to_thread->pid : 0; - spin_unlock(&t->lock); - - trace_binder_txn_latency_free(t, from_proc, from_thread, to_proc, to_thread); -} - -static void binder_free_transaction(struct binder_transaction *t) -{ - struct binder_thread *target_thread; - struct binder_proc *target_proc; - - spin_lock(&t->lock); - target_proc = t->to_proc; - target_thread = t->to_thread; - /* - * Pin target_thread to keep target_proc alive. Undelivered - * transactions with !target_thread are safe, as target_proc - * can only be the current context there. - */ - if (target_thread) - atomic_inc(&target_thread->tmp_ref); - spin_unlock(&t->lock); - - if (target_proc) { - binder_inner_proc_lock(target_proc); - target_proc->outstanding_txns--; - if (target_proc->outstanding_txns < 0) - pr_warn("%s: Unexpected outstanding_txns %d\n", - __func__, target_proc->outstanding_txns); - if (!target_proc->outstanding_txns && target_proc->is_frozen) - wake_up_interruptible_all(&target_proc->freeze_wait); - if (t->buffer) - t->buffer->transaction = NULL; - binder_inner_proc_unlock(target_proc); - } - - if (target_thread) - binder_thread_dec_tmpref(target_thread); - - if (trace_binder_txn_latency_free_enabled()) - binder_txn_latency_free(t); - /* - * If the transaction has no target_proc, then - * t->buffer->transaction has already been cleared. - */ - binder_free_txn_fixups(t); - kfree(t); - binder_stats_deleted(BINDER_STAT_TRANSACTION); -} - -static void binder_send_failed_reply(struct binder_transaction *t, - uint32_t error_code) -{ - struct binder_thread *target_thread; - struct binder_transaction *next; - - BUG_ON(t->flags & TF_ONE_WAY); - while (1) { - target_thread = binder_get_txn_from_and_acq_inner(t); - if (target_thread) { - binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, - "send failed reply for transaction %d to %d:%d\n", - t->debug_id, - target_thread->proc->pid, - target_thread->pid); - - binder_pop_transaction_ilocked(target_thread, t); - if (target_thread->reply_error.cmd == BR_OK) { - target_thread->reply_error.cmd = error_code; - binder_enqueue_thread_work_ilocked( - target_thread, - &target_thread->reply_error.work); - wake_up_interruptible(&target_thread->wait); - } else { - /* - * Cannot get here for normal operation, but - * we can if multiple synchronous transactions - * are sent without blocking for responses. - * Just ignore the 2nd error in this case. - */ - pr_warn("Unexpected reply error: %u\n", - target_thread->reply_error.cmd); - } - binder_inner_proc_unlock(target_thread->proc); - binder_thread_dec_tmpref(target_thread); - binder_free_transaction(t); - return; - } - __release(&target_thread->proc->inner_lock); - next = t->from_parent; - - binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, - "send failed reply for transaction %d, target dead\n", - t->debug_id); - - binder_free_transaction(t); - if (next == NULL) { - binder_debug(BINDER_DEBUG_DEAD_BINDER, - "reply failed, no target thread at root\n"); - return; - } - t = next; - binder_debug(BINDER_DEBUG_DEAD_BINDER, - "reply failed, no target thread -- retry %d\n", - t->debug_id); - } -} - -/** - * binder_cleanup_transaction() - cleans up undelivered transaction - * @t: transaction that needs to be cleaned up - * @reason: reason the transaction wasn't delivered - * @error_code: error to return to caller (if synchronous call) - */ -static void binder_cleanup_transaction(struct binder_transaction *t, - const char *reason, - uint32_t error_code) -{ - if (t->buffer->target_node && !(t->flags & TF_ONE_WAY)) { - binder_send_failed_reply(t, error_code); - } else { - binder_debug(BINDER_DEBUG_DEAD_TRANSACTION, - "undelivered transaction %d, %s\n", - t->debug_id, reason); - binder_free_transaction(t); - } -} - -/** - * binder_get_object() - gets object and checks for valid metadata - * @proc: binder_proc owning the buffer - * @u: sender's user pointer to base of buffer - * @buffer: binder_buffer that we're parsing. - * @offset: offset in the @buffer at which to validate an object. - * @object: struct binder_object to read into - * - * Copy the binder object at the given offset into @object. If @u is - * provided then the copy is from the sender's buffer. If not, then - * it is copied from the target's @buffer. - * - * Return: If there's a valid metadata object at @offset, the - * size of that object. Otherwise, it returns zero. The object - * is read into the struct binder_object pointed to by @object. - */ -static size_t binder_get_object(struct binder_proc *proc, - const void __user *u, - struct binder_buffer *buffer, - unsigned long offset, - struct binder_object *object) -{ - size_t read_size; - struct binder_object_header *hdr; - size_t object_size = 0; - - read_size = min_t(size_t, sizeof(*object), buffer->data_size - offset); - if (offset > buffer->data_size || read_size < sizeof(*hdr) || - !IS_ALIGNED(offset, sizeof(u32))) - return 0; - - if (u) { - if (copy_from_user(object, u + offset, read_size)) - return 0; - } else { - if (binder_alloc_copy_from_buffer(&proc->alloc, object, buffer, - offset, read_size)) - return 0; - } - - /* Ok, now see if we read a complete object. */ - hdr = &object->hdr; - switch (hdr->type) { - case BINDER_TYPE_BINDER: - case BINDER_TYPE_WEAK_BINDER: - case BINDER_TYPE_HANDLE: - case BINDER_TYPE_WEAK_HANDLE: - object_size = sizeof(struct flat_binder_object); - break; - case BINDER_TYPE_FD: - object_size = sizeof(struct binder_fd_object); - break; - case BINDER_TYPE_PTR: - object_size = sizeof(struct binder_buffer_object); - break; - case BINDER_TYPE_FDA: - object_size = sizeof(struct binder_fd_array_object); - break; - default: - return 0; - } - if (offset <= buffer->data_size - object_size && - buffer->data_size >= object_size) - return object_size; - else - return 0; -} - -/** - * binder_validate_ptr() - validates binder_buffer_object in a binder_buffer. - * @proc: binder_proc owning the buffer - * @b: binder_buffer containing the object - * @object: struct binder_object to read into - * @index: index in offset array at which the binder_buffer_object is - * located - * @start_offset: points to the start of the offset array - * @object_offsetp: offset of @object read from @b - * @num_valid: the number of valid offsets in the offset array - * - * Return: If @index is within the valid range of the offset array - * described by @start and @num_valid, and if there's a valid - * binder_buffer_object at the offset found in index @index - * of the offset array, that object is returned. Otherwise, - * %NULL is returned. - * Note that the offset found in index @index itself is not - * verified; this function assumes that @num_valid elements - * from @start were previously verified to have valid offsets. - * If @object_offsetp is non-NULL, then the offset within - * @b is written to it. - */ -static struct binder_buffer_object *binder_validate_ptr( - struct binder_proc *proc, - struct binder_buffer *b, - struct binder_object *object, - binder_size_t index, - binder_size_t start_offset, - binder_size_t *object_offsetp, - binder_size_t num_valid) -{ - size_t object_size; - binder_size_t object_offset; - unsigned long buffer_offset; - - if (index >= num_valid) - return NULL; - - buffer_offset = start_offset + sizeof(binder_size_t) * index; - if (binder_alloc_copy_from_buffer(&proc->alloc, &object_offset, - b, buffer_offset, - sizeof(object_offset))) - return NULL; - object_size = binder_get_object(proc, NULL, b, object_offset, object); - if (!object_size || object->hdr.type != BINDER_TYPE_PTR) - return NULL; - if (object_offsetp) - *object_offsetp = object_offset; - - return &object->bbo; -} - -/** - * binder_validate_fixup() - validates pointer/fd fixups happen in order. - * @proc: binder_proc owning the buffer - * @b: transaction buffer - * @objects_start_offset: offset to start of objects buffer - * @buffer_obj_offset: offset to binder_buffer_object in which to fix up - * @fixup_offset: start offset in @buffer to fix up - * @last_obj_offset: offset to last binder_buffer_object that we fixed - * @last_min_offset: minimum fixup offset in object at @last_obj_offset - * - * Return: %true if a fixup in buffer @buffer at offset @offset is - * allowed. - * - * For safety reasons, we only allow fixups inside a buffer to happen - * at increasing offsets; additionally, we only allow fixup on the last - * buffer object that was verified, or one of its parents. - * - * Example of what is allowed: - * - * A - * B (parent = A, offset = 0) - * C (parent = A, offset = 16) - * D (parent = C, offset = 0) - * E (parent = A, offset = 32) // min_offset is 16 (C.parent_offset) - * - * Examples of what is not allowed: - * - * Decreasing offsets within the same parent: - * A - * C (parent = A, offset = 16) - * B (parent = A, offset = 0) // decreasing offset within A - * - * Referring to a parent that wasn't the last object or any of its parents: - * A - * B (parent = A, offset = 0) - * C (parent = A, offset = 0) - * C (parent = A, offset = 16) - * D (parent = B, offset = 0) // B is not A or any of A's parents - */ -static bool binder_validate_fixup(struct binder_proc *proc, - struct binder_buffer *b, - binder_size_t objects_start_offset, - binder_size_t buffer_obj_offset, - binder_size_t fixup_offset, - binder_size_t last_obj_offset, - binder_size_t last_min_offset) -{ - if (!last_obj_offset) { - /* Nothing to fix up in */ - return false; - } - - while (last_obj_offset != buffer_obj_offset) { - unsigned long buffer_offset; - struct binder_object last_object; - struct binder_buffer_object *last_bbo; - size_t object_size = binder_get_object(proc, NULL, b, - last_obj_offset, - &last_object); - if (object_size != sizeof(*last_bbo)) - return false; - - last_bbo = &last_object.bbo; - /* - * Safe to retrieve the parent of last_obj, since it - * was already previously verified by the driver. - */ - if ((last_bbo->flags & BINDER_BUFFER_FLAG_HAS_PARENT) == 0) - return false; - last_min_offset = last_bbo->parent_offset + sizeof(uintptr_t); - buffer_offset = objects_start_offset + - sizeof(binder_size_t) * last_bbo->parent; - if (binder_alloc_copy_from_buffer(&proc->alloc, - &last_obj_offset, - b, buffer_offset, - sizeof(last_obj_offset))) - return false; - } - return (fixup_offset >= last_min_offset); -} - -/** - * struct binder_task_work_cb - for deferred close - * - * @twork: callback_head for task work - * @file: file to close - * - * Structure to pass task work to be handled after - * returning from binder_ioctl() via task_work_add(). - */ -struct binder_task_work_cb { - struct callback_head twork; - struct file *file; -}; - -/** - * binder_do_fd_close() - close list of file descriptors - * @twork: callback head for task work - * - * It is not safe to call ksys_close() during the binder_ioctl() - * function if there is a chance that binder's own file descriptor - * might be closed. This is to meet the requirements for using - * fdget() (see comments for __fget_light()). Therefore use - * task_work_add() to schedule the close operation once we have - * returned from binder_ioctl(). This function is a callback - * for that mechanism and does the actual ksys_close() on the - * given file descriptor. - */ -static void binder_do_fd_close(struct callback_head *twork) -{ - struct binder_task_work_cb *twcb = container_of(twork, - struct binder_task_work_cb, twork); - - fput(twcb->file); - kfree(twcb); -} - -/** - * binder_deferred_fd_close() - schedule a close for the given file-descriptor - * @fd: file-descriptor to close - * - * See comments in binder_do_fd_close(). This function is used to schedule - * a file-descriptor to be closed after returning from binder_ioctl(). - */ -static void binder_deferred_fd_close(int fd) -{ - struct binder_task_work_cb *twcb; - - twcb = kzalloc_obj(*twcb); - if (!twcb) - return; - init_task_work(&twcb->twork, binder_do_fd_close); - twcb->file = file_close_fd(fd); - if (twcb->file) { - // pin it until binder_do_fd_close(); see comments there - get_file(twcb->file); - filp_close(twcb->file, current->files); - task_work_add(current, &twcb->twork, TWA_RESUME); - } else { - kfree(twcb); - } -} - -static void binder_transaction_buffer_release(struct binder_proc *proc, - struct binder_thread *thread, - struct binder_buffer *buffer, - binder_size_t off_end_offset, - bool is_failure) -{ - int debug_id = buffer->debug_id; - binder_size_t off_start_offset, buffer_offset; - - binder_debug(BINDER_DEBUG_TRANSACTION, - "%d buffer release %d, size %zd-%zd, failed at %llx\n", - proc->pid, buffer->debug_id, - buffer->data_size, buffer->offsets_size, - (unsigned long long)off_end_offset); - - if (buffer->target_node) - binder_dec_node(buffer->target_node, 1, 0); - - off_start_offset = ALIGN(buffer->data_size, sizeof(void *)); - - for (buffer_offset = off_start_offset; buffer_offset < off_end_offset; - buffer_offset += sizeof(binder_size_t)) { - struct binder_object_header *hdr; - size_t object_size = 0; - struct binder_object object; - binder_size_t object_offset; - - if (!binder_alloc_copy_from_buffer(&proc->alloc, &object_offset, - buffer, buffer_offset, - sizeof(object_offset))) - object_size = binder_get_object(proc, NULL, buffer, - object_offset, &object); - if (object_size == 0) { - pr_err("transaction release %d bad object at offset %lld, size %zd\n", - debug_id, (u64)object_offset, buffer->data_size); - continue; - } - hdr = &object.hdr; - switch (hdr->type) { - case BINDER_TYPE_BINDER: - case BINDER_TYPE_WEAK_BINDER: { - struct flat_binder_object *fp; - struct binder_node *node; - - fp = to_flat_binder_object(hdr); - node = binder_get_node(proc, fp->binder); - if (node == NULL) { - pr_err("transaction release %d bad node %016llx\n", - debug_id, (u64)fp->binder); - break; - } - binder_debug(BINDER_DEBUG_TRANSACTION, - " node %d u%016llx\n", - node->debug_id, (u64)node->ptr); - binder_dec_node(node, hdr->type == BINDER_TYPE_BINDER, - 0); - binder_put_node(node); - } break; - case BINDER_TYPE_HANDLE: - case BINDER_TYPE_WEAK_HANDLE: { - struct flat_binder_object *fp; - struct binder_ref_data rdata; - int ret; - - fp = to_flat_binder_object(hdr); - ret = binder_dec_ref_for_handle(proc, fp->handle, - hdr->type == BINDER_TYPE_HANDLE, &rdata); - - if (ret) { - pr_err("transaction release %d bad handle %d, ret = %d\n", - debug_id, fp->handle, ret); - break; - } - binder_debug(BINDER_DEBUG_TRANSACTION, - " ref %d desc %d\n", - rdata.debug_id, rdata.desc); - } break; - - case BINDER_TYPE_FD: { - /* - * No need to close the file here since user-space - * closes it for successfully delivered - * transactions. For transactions that weren't - * delivered, the new fd was never allocated so - * there is no need to close and the fput on the - * file is done when the transaction is torn - * down. - */ - } break; - case BINDER_TYPE_PTR: - /* - * Nothing to do here, this will get cleaned up when the - * transaction buffer gets freed - */ - break; - case BINDER_TYPE_FDA: { - struct binder_fd_array_object *fda; - struct binder_buffer_object *parent; - struct binder_object ptr_object; - binder_size_t fda_offset; - size_t fd_index; - binder_size_t fd_buf_size; - binder_size_t num_valid; - - if (is_failure) { - /* - * The fd fixups have not been applied so no - * fds need to be closed. - */ - continue; - } - - num_valid = (buffer_offset - off_start_offset) / - sizeof(binder_size_t); - fda = to_binder_fd_array_object(hdr); - parent = binder_validate_ptr(proc, buffer, &ptr_object, - fda->parent, - off_start_offset, - NULL, - num_valid); - if (!parent) { - pr_err("transaction release %d bad parent offset\n", - debug_id); - continue; - } - fd_buf_size = sizeof(u32) * fda->num_fds; - if (fda->num_fds >= SIZE_MAX / sizeof(u32)) { - pr_err("transaction release %d invalid number of fds (%lld)\n", - debug_id, (u64)fda->num_fds); - continue; - } - if (fd_buf_size > parent->length || - fda->parent_offset > parent->length - fd_buf_size) { - /* No space for all file descriptors here. */ - pr_err("transaction release %d not enough space for %lld fds in buffer\n", - debug_id, (u64)fda->num_fds); - continue; - } - /* - * the source data for binder_buffer_object is visible - * to user-space and the @buffer element is the user - * pointer to the buffer_object containing the fd_array. - * Convert the address to an offset relative to - * the base of the transaction buffer. - */ - fda_offset = parent->buffer - buffer->user_data + - fda->parent_offset; - for (fd_index = 0; fd_index < fda->num_fds; - fd_index++) { - u32 fd; - int err; - binder_size_t offset = fda_offset + - fd_index * sizeof(fd); - - err = binder_alloc_copy_from_buffer( - &proc->alloc, &fd, buffer, - offset, sizeof(fd)); - WARN_ON(err); - if (!err) { - binder_deferred_fd_close(fd); - /* - * Need to make sure the thread goes - * back to userspace to complete the - * deferred close - */ - if (thread) - thread->looper_need_return = true; - } - } - } break; - default: - pr_err("transaction release %d bad object type %x\n", - debug_id, hdr->type); - break; - } - } -} - -/* Clean up all the objects in the buffer */ -static inline void binder_release_entire_buffer(struct binder_proc *proc, - struct binder_thread *thread, - struct binder_buffer *buffer, - bool is_failure) -{ - binder_size_t off_end_offset; - - off_end_offset = ALIGN(buffer->data_size, sizeof(void *)); - off_end_offset += buffer->offsets_size; - - binder_transaction_buffer_release(proc, thread, buffer, - off_end_offset, is_failure); -} - -static int binder_translate_binder(struct flat_binder_object *fp, - struct binder_transaction *t, - struct binder_thread *thread) -{ - struct binder_node *node; - struct binder_proc *proc = thread->proc; - struct binder_proc *target_proc = t->to_proc; - struct binder_ref_data rdata; - int ret = 0; - - node = binder_get_node(proc, fp->binder); - if (!node) { - node = binder_new_node(proc, fp); - if (!node) - return -ENOMEM; - } - if (fp->cookie != node->cookie) { - binder_user_error("%d:%d sending u%016llx node %d, cookie mismatch %016llx != %016llx\n", - proc->pid, thread->pid, (u64)fp->binder, - node->debug_id, (u64)fp->cookie, - (u64)node->cookie); - ret = -EINVAL; - goto done; - } - if (security_binder_transfer_binder(proc->cred, target_proc->cred)) { - ret = -EPERM; - goto done; - } - - ret = binder_inc_ref_for_node(target_proc, node, - fp->hdr.type == BINDER_TYPE_BINDER, - &thread->todo, &rdata); - if (ret) - goto done; - - if (fp->hdr.type == BINDER_TYPE_BINDER) - fp->hdr.type = BINDER_TYPE_HANDLE; - else - fp->hdr.type = BINDER_TYPE_WEAK_HANDLE; - fp->binder = 0; - fp->handle = rdata.desc; - fp->cookie = 0; - - trace_binder_transaction_node_to_ref(t, node, &rdata); - binder_debug(BINDER_DEBUG_TRANSACTION, - " node %d u%016llx -> ref %d desc %d\n", - node->debug_id, (u64)node->ptr, - rdata.debug_id, rdata.desc); -done: - binder_put_node(node); - return ret; -} - -static int binder_translate_handle(struct flat_binder_object *fp, - struct binder_transaction *t, - struct binder_thread *thread) -{ - struct binder_proc *proc = thread->proc; - struct binder_proc *target_proc = t->to_proc; - struct binder_node *node; - struct binder_ref_data src_rdata; - int ret = 0; - - node = binder_get_node_from_ref(proc, fp->handle, - fp->hdr.type == BINDER_TYPE_HANDLE, &src_rdata); - if (!node) { - binder_user_error("%d:%d got transaction with invalid handle, %d\n", - proc->pid, thread->pid, fp->handle); - return -EINVAL; - } - if (security_binder_transfer_binder(proc->cred, target_proc->cred)) { - ret = -EPERM; - goto done; - } - - binder_node_lock(node); - if (node->proc == target_proc) { - if (fp->hdr.type == BINDER_TYPE_HANDLE) - fp->hdr.type = BINDER_TYPE_BINDER; - else - fp->hdr.type = BINDER_TYPE_WEAK_BINDER; - fp->binder = node->ptr; - fp->cookie = node->cookie; - if (node->proc) - binder_inner_proc_lock(node->proc); - else - __acquire(&node->proc->inner_lock); - binder_inc_node_nilocked(node, - fp->hdr.type == BINDER_TYPE_BINDER, - 0, NULL); - if (node->proc) - binder_inner_proc_unlock(node->proc); - else - __release(&node->proc->inner_lock); - trace_binder_transaction_ref_to_node(t, node, &src_rdata); - binder_debug(BINDER_DEBUG_TRANSACTION, - " ref %d desc %d -> node %d u%016llx\n", - src_rdata.debug_id, src_rdata.desc, node->debug_id, - (u64)node->ptr); - binder_node_unlock(node); - } else { - struct binder_ref_data dest_rdata; - - binder_node_unlock(node); - ret = binder_inc_ref_for_node(target_proc, node, - fp->hdr.type == BINDER_TYPE_HANDLE, - NULL, &dest_rdata); - if (ret) - goto done; - - fp->binder = 0; - fp->handle = dest_rdata.desc; - fp->cookie = 0; - trace_binder_transaction_ref_to_ref(t, node, &src_rdata, - &dest_rdata); - binder_debug(BINDER_DEBUG_TRANSACTION, - " ref %d desc %d -> ref %d desc %d (node %d)\n", - src_rdata.debug_id, src_rdata.desc, - dest_rdata.debug_id, dest_rdata.desc, - node->debug_id); - } -done: - binder_put_node(node); - return ret; -} - -static int binder_translate_fd(u32 fd, binder_size_t fd_offset, - struct binder_transaction *t, - struct binder_thread *thread, - struct binder_transaction *in_reply_to) -{ - struct binder_proc *proc = thread->proc; - struct binder_proc *target_proc = t->to_proc; - struct binder_txn_fd_fixup *fixup; - struct file *file; - int ret = 0; - bool target_allows_fd; - - if (in_reply_to) - target_allows_fd = !!(in_reply_to->flags & TF_ACCEPT_FDS); - else - target_allows_fd = t->buffer->target_node->accept_fds; - if (!target_allows_fd) { - binder_user_error("%d:%d got %s with fd, %d, but target does not allow fds\n", - proc->pid, thread->pid, - in_reply_to ? "reply" : "transaction", - fd); - ret = -EPERM; - goto err_fd_not_accepted; - } - - file = fget(fd); - if (!file) { - binder_user_error("%d:%d got transaction with invalid fd, %d\n", - proc->pid, thread->pid, fd); - ret = -EBADF; - goto err_fget; - } - ret = security_binder_transfer_file(proc->cred, target_proc->cred, file); - if (ret < 0) { - ret = -EPERM; - goto err_security; - } - - /* - * Add fixup record for this transaction. The allocation - * of the fd in the target needs to be done from a - * target thread. - */ - fixup = kzalloc_obj(*fixup); - if (!fixup) { - ret = -ENOMEM; - goto err_alloc; - } - fixup->file = file; - fixup->offset = fd_offset; - fixup->target_fd = -1; - trace_binder_transaction_fd_send(t, fd, fixup->offset); - list_add_tail(&fixup->fixup_entry, &t->fd_fixups); - - return ret; - -err_alloc: -err_security: - fput(file); -err_fget: -err_fd_not_accepted: - return ret; -} - -/** - * struct binder_ptr_fixup - data to be fixed-up in target buffer - * @offset: offset in target buffer to fixup - * @skip_size: bytes to skip in copy (fixup will be written later) - * @fixup_data: data to write at fixup offset - * @node: list node - * - * This is used for the pointer fixup list (pf) which is created and consumed - * during binder_transaction() and is only accessed locally. No - * locking is necessary. - * - * The list is ordered by @offset. - */ -struct binder_ptr_fixup { - binder_size_t offset; - size_t skip_size; - binder_uintptr_t fixup_data; - struct list_head node; -}; - -/** - * struct binder_sg_copy - scatter-gather data to be copied - * @offset: offset in target buffer - * @sender_uaddr: user address in source buffer - * @length: bytes to copy - * @node: list node - * - * This is used for the sg copy list (sgc) which is created and consumed - * during binder_transaction() and is only accessed locally. No - * locking is necessary. - * - * The list is ordered by @offset. - */ -struct binder_sg_copy { - binder_size_t offset; - const void __user *sender_uaddr; - size_t length; - struct list_head node; -}; - -/** - * binder_do_deferred_txn_copies() - copy and fixup scatter-gather data - * @alloc: binder_alloc associated with @buffer - * @buffer: binder buffer in target process - * @sgc_head: list_head of scatter-gather copy list - * @pf_head: list_head of pointer fixup list - * - * Processes all elements of @sgc_head, applying fixups from @pf_head - * and copying the scatter-gather data from the source process' user - * buffer to the target's buffer. It is expected that the list creation - * and processing all occurs during binder_transaction() so these lists - * are only accessed in local context. - * - * Return: 0=success, else -errno - */ -static int binder_do_deferred_txn_copies(struct binder_alloc *alloc, - struct binder_buffer *buffer, - struct list_head *sgc_head, - struct list_head *pf_head) -{ - int ret = 0; - struct binder_sg_copy *sgc, *tmpsgc; - struct binder_ptr_fixup *tmppf; - struct binder_ptr_fixup *pf = - list_first_entry_or_null(pf_head, struct binder_ptr_fixup, - node); - - list_for_each_entry_safe(sgc, tmpsgc, sgc_head, node) { - size_t bytes_copied = 0; - - while (bytes_copied < sgc->length) { - size_t copy_size; - size_t bytes_left = sgc->length - bytes_copied; - size_t offset = sgc->offset + bytes_copied; - - /* - * We copy up to the fixup (pointed to by pf) - */ - copy_size = pf ? min(bytes_left, (size_t)pf->offset - offset) - : bytes_left; - if (!ret && copy_size) - ret = binder_alloc_copy_user_to_buffer( - alloc, buffer, - offset, - sgc->sender_uaddr + bytes_copied, - copy_size); - bytes_copied += copy_size; - if (copy_size != bytes_left) { - BUG_ON(!pf); - /* we stopped at a fixup offset */ - if (pf->skip_size) { - /* - * we are just skipping. This is for - * BINDER_TYPE_FDA where the translated - * fds will be fixed up when we get - * to target context. - */ - bytes_copied += pf->skip_size; - } else { - /* apply the fixup indicated by pf */ - if (!ret) - ret = binder_alloc_copy_to_buffer( - alloc, buffer, - pf->offset, - &pf->fixup_data, - sizeof(pf->fixup_data)); - bytes_copied += sizeof(pf->fixup_data); - } - list_del(&pf->node); - kfree(pf); - pf = list_first_entry_or_null(pf_head, - struct binder_ptr_fixup, node); - } - } - list_del(&sgc->node); - kfree(sgc); - } - list_for_each_entry_safe(pf, tmppf, pf_head, node) { - BUG_ON(pf->skip_size == 0); - list_del(&pf->node); - kfree(pf); - } - BUG_ON(!list_empty(sgc_head)); - - return ret > 0 ? -EINVAL : ret; -} - -/** - * binder_cleanup_deferred_txn_lists() - free specified lists - * @sgc_head: list_head of scatter-gather copy list - * @pf_head: list_head of pointer fixup list - * - * Called to clean up @sgc_head and @pf_head if there is an - * error. - */ -static void binder_cleanup_deferred_txn_lists(struct list_head *sgc_head, - struct list_head *pf_head) -{ - struct binder_sg_copy *sgc, *tmpsgc; - struct binder_ptr_fixup *pf, *tmppf; - - list_for_each_entry_safe(sgc, tmpsgc, sgc_head, node) { - list_del(&sgc->node); - kfree(sgc); - } - list_for_each_entry_safe(pf, tmppf, pf_head, node) { - list_del(&pf->node); - kfree(pf); - } -} - -/** - * binder_defer_copy() - queue a scatter-gather buffer for copy - * @sgc_head: list_head of scatter-gather copy list - * @offset: binder buffer offset in target process - * @sender_uaddr: user address in source process - * @length: bytes to copy - * - * Specify a scatter-gather block to be copied. The actual copy must - * be deferred until all the needed fixups are identified and queued. - * Then the copy and fixups are done together so un-translated values - * from the source are never visible in the target buffer. - * - * We are guaranteed that repeated calls to this function will have - * monotonically increasing @offset values so the list will naturally - * be ordered. - * - * Return: 0=success, else -errno - */ -static int binder_defer_copy(struct list_head *sgc_head, binder_size_t offset, - const void __user *sender_uaddr, size_t length) -{ - struct binder_sg_copy *bc = kzalloc_obj(*bc); - - if (!bc) - return -ENOMEM; - - bc->offset = offset; - bc->sender_uaddr = sender_uaddr; - bc->length = length; - INIT_LIST_HEAD(&bc->node); - - /* - * We are guaranteed that the deferred copies are in-order - * so just add to the tail. - */ - list_add_tail(&bc->node, sgc_head); - - return 0; -} - -/** - * binder_add_fixup() - queue a fixup to be applied to sg copy - * @pf_head: list_head of binder ptr fixup list - * @offset: binder buffer offset in target process - * @fixup: bytes to be copied for fixup - * @skip_size: bytes to skip when copying (fixup will be applied later) - * - * Add the specified fixup to a list ordered by @offset. When copying - * the scatter-gather buffers, the fixup will be copied instead of - * data from the source buffer. For BINDER_TYPE_FDA fixups, the fixup - * will be applied later (in target process context), so we just skip - * the bytes specified by @skip_size. If @skip_size is 0, we copy the - * value in @fixup. - * - * This function is called *mostly* in @offset order, but there are - * exceptions. Since out-of-order inserts are relatively uncommon, - * we insert the new element by searching backward from the tail of - * the list. - * - * Return: 0=success, else -errno - */ -static int binder_add_fixup(struct list_head *pf_head, binder_size_t offset, - binder_uintptr_t fixup, size_t skip_size) -{ - struct binder_ptr_fixup *pf = kzalloc_obj(*pf); - struct binder_ptr_fixup *tmppf; - - if (!pf) - return -ENOMEM; - - pf->offset = offset; - pf->fixup_data = fixup; - pf->skip_size = skip_size; - INIT_LIST_HEAD(&pf->node); - - /* Fixups are *mostly* added in-order, but there are some - * exceptions. Look backwards through list for insertion point. - */ - list_for_each_entry_reverse(tmppf, pf_head, node) { - if (tmppf->offset < pf->offset) { - list_add(&pf->node, &tmppf->node); - return 0; - } - } - /* - * if we get here, then the new offset is the lowest so - * insert at the head - */ - list_add(&pf->node, pf_head); - return 0; -} - -static int binder_translate_fd_array(struct list_head *pf_head, - struct binder_fd_array_object *fda, - const void __user *sender_ubuffer, - struct binder_buffer_object *parent, - struct binder_buffer_object *sender_uparent, - struct binder_transaction *t, - struct binder_thread *thread, - struct binder_transaction *in_reply_to) -{ - binder_size_t fdi, fd_buf_size; - binder_size_t fda_offset; - const void __user *sender_ufda_base; - struct binder_proc *proc = thread->proc; - int ret; - - if (fda->num_fds == 0) - return 0; - - fd_buf_size = sizeof(u32) * fda->num_fds; - if (fda->num_fds >= SIZE_MAX / sizeof(u32)) { - binder_user_error("%d:%d got transaction with invalid number of fds (%lld)\n", - proc->pid, thread->pid, (u64)fda->num_fds); - return -EINVAL; - } - if (fd_buf_size > parent->length || - fda->parent_offset > parent->length - fd_buf_size) { - /* No space for all file descriptors here. */ - binder_user_error("%d:%d not enough space to store %lld fds in buffer\n", - proc->pid, thread->pid, (u64)fda->num_fds); - return -EINVAL; - } - /* - * the source data for binder_buffer_object is visible - * to user-space and the @buffer element is the user - * pointer to the buffer_object containing the fd_array. - * Convert the address to an offset relative to - * the base of the transaction buffer. - */ - fda_offset = parent->buffer - t->buffer->user_data + - fda->parent_offset; - sender_ufda_base = (void __user *)(uintptr_t)sender_uparent->buffer + - fda->parent_offset; - - if (!IS_ALIGNED((unsigned long)fda_offset, sizeof(u32)) || - !IS_ALIGNED((unsigned long)sender_ufda_base, sizeof(u32))) { - binder_user_error("%d:%d parent offset not aligned correctly.\n", - proc->pid, thread->pid); - return -EINVAL; - } - ret = binder_add_fixup(pf_head, fda_offset, 0, fda->num_fds * sizeof(u32)); - if (ret) - return ret; - - for (fdi = 0; fdi < fda->num_fds; fdi++) { - u32 fd; - binder_size_t offset = fda_offset + fdi * sizeof(fd); - binder_size_t sender_uoffset = fdi * sizeof(fd); - - ret = copy_from_user(&fd, sender_ufda_base + sender_uoffset, sizeof(fd)); - if (!ret) - ret = binder_translate_fd(fd, offset, t, thread, - in_reply_to); - if (ret) - return ret > 0 ? -EINVAL : ret; - } - return 0; -} - -static int binder_fixup_parent(struct list_head *pf_head, - struct binder_transaction *t, - struct binder_thread *thread, - struct binder_buffer_object *bp, - binder_size_t off_start_offset, - binder_size_t num_valid, - binder_size_t last_fixup_obj_off, - binder_size_t last_fixup_min_off) -{ - struct binder_buffer_object *parent; - struct binder_buffer *b = t->buffer; - struct binder_proc *proc = thread->proc; - struct binder_proc *target_proc = t->to_proc; - struct binder_object object; - binder_size_t buffer_offset; - binder_size_t parent_offset; - - if (!(bp->flags & BINDER_BUFFER_FLAG_HAS_PARENT)) - return 0; - - parent = binder_validate_ptr(target_proc, b, &object, bp->parent, - off_start_offset, &parent_offset, - num_valid); - if (!parent) { - binder_user_error("%d:%d got transaction with invalid parent offset or type\n", - proc->pid, thread->pid); - return -EINVAL; - } - - if (!binder_validate_fixup(target_proc, b, off_start_offset, - parent_offset, bp->parent_offset, - last_fixup_obj_off, - last_fixup_min_off)) { - binder_user_error("%d:%d got transaction with out-of-order buffer fixup\n", - proc->pid, thread->pid); - return -EINVAL; - } - - if (parent->length < sizeof(binder_uintptr_t) || - bp->parent_offset > parent->length - sizeof(binder_uintptr_t)) { - /* No space for a pointer here! */ - binder_user_error("%d:%d got transaction with invalid parent offset\n", - proc->pid, thread->pid); - return -EINVAL; - } - - buffer_offset = bp->parent_offset + parent->buffer - b->user_data; - - return binder_add_fixup(pf_head, buffer_offset, bp->buffer, 0); -} - -/** - * binder_can_update_transaction() - Can a txn be superseded by an updated one? - * @t1: the pending async txn in the frozen process - * @t2: the new async txn to supersede the outdated pending one - * - * Return: true if t2 can supersede t1 - * false if t2 can not supersede t1 - */ -static bool binder_can_update_transaction(struct binder_transaction *t1, - struct binder_transaction *t2) -{ - if ((t1->flags & t2->flags & (TF_ONE_WAY | TF_UPDATE_TXN)) != - (TF_ONE_WAY | TF_UPDATE_TXN) || !t1->to_proc || !t2->to_proc) - return false; - if (t1->to_proc->tsk == t2->to_proc->tsk && t1->code == t2->code && - t1->flags == t2->flags && t1->buffer->pid == t2->buffer->pid && - t1->buffer->target_node->ptr == t2->buffer->target_node->ptr && - t1->buffer->target_node->cookie == t2->buffer->target_node->cookie) - return true; - return false; -} - -/** - * binder_find_outdated_transaction_ilocked() - Find the outdated transaction - * @t: new async transaction - * @target_list: list to find outdated transaction - * - * Return: the outdated transaction if found - * NULL if no outdated transacton can be found - * - * Requires the proc->inner_lock to be held. - */ -static struct binder_transaction * -binder_find_outdated_transaction_ilocked(struct binder_transaction *t, - struct list_head *target_list) -{ - struct binder_work *w; - - list_for_each_entry(w, target_list, entry) { - struct binder_transaction *t_queued; - - if (w->type != BINDER_WORK_TRANSACTION) - continue; - t_queued = container_of(w, struct binder_transaction, work); - if (binder_can_update_transaction(t_queued, t)) - return t_queued; - } - return NULL; -} - -/** - * binder_proc_transaction() - sends a transaction to a process and wakes it up - * @t: transaction to send - * @proc: process to send the transaction to - * @thread: thread in @proc to send the transaction to (may be NULL) - * - * This function queues a transaction to the specified process. It will try - * to find a thread in the target process to handle the transaction and - * wake it up. If no thread is found, the work is queued to the proc - * waitqueue. - * - * If the @thread parameter is not NULL, the transaction is always queued - * to the waitlist of that specific thread. - * - * Return: 0 if the transaction was successfully queued - * BR_DEAD_REPLY if the target process or thread is dead - * BR_FROZEN_REPLY if the target process or thread is frozen and - * the sync transaction was rejected - * BR_TRANSACTION_PENDING_FROZEN if the target process is frozen - * and the async transaction was successfully queued - */ -static int binder_proc_transaction(struct binder_transaction *t, - struct binder_proc *proc, - struct binder_thread *thread) -{ - struct binder_node *node = t->buffer->target_node; - bool oneway = !!(t->flags & TF_ONE_WAY); - bool pending_async = false; - struct binder_transaction *t_outdated = NULL; - bool frozen = false; - - BUG_ON(!node); - binder_node_lock(node); - if (oneway) { - BUG_ON(thread); - if (node->has_async_transaction) - pending_async = true; - else - node->has_async_transaction = true; - } - - binder_inner_proc_lock(proc); - if (proc->is_frozen) { - frozen = true; - proc->sync_recv |= !oneway; - proc->async_recv |= oneway; - } - - if ((frozen && !oneway) || proc->is_dead || - (thread && thread->is_dead)) { - binder_inner_proc_unlock(proc); - binder_node_unlock(node); - return frozen ? BR_FROZEN_REPLY : BR_DEAD_REPLY; - } - - if (!thread && !pending_async) - thread = binder_select_thread_ilocked(proc); - - if (thread) { - binder_enqueue_thread_work_ilocked(thread, &t->work); - } else if (!pending_async) { - binder_enqueue_work_ilocked(&t->work, &proc->todo); - } else { - if ((t->flags & TF_UPDATE_TXN) && frozen) { - t_outdated = binder_find_outdated_transaction_ilocked(t, - &node->async_todo); - if (t_outdated) { - binder_debug(BINDER_DEBUG_TRANSACTION, - "txn %d supersedes %d\n", - t->debug_id, t_outdated->debug_id); - list_del_init(&t_outdated->work.entry); - proc->outstanding_txns--; - } - } - binder_enqueue_work_ilocked(&t->work, &node->async_todo); - } - - if (!pending_async) - binder_wakeup_thread_ilocked(proc, thread, !oneway /* sync */); - - proc->outstanding_txns++; - binder_inner_proc_unlock(proc); - binder_node_unlock(node); - - /* - * To reduce potential contention, free the outdated transaction and - * buffer after releasing the locks. - */ - if (t_outdated) { - struct binder_buffer *buffer = t_outdated->buffer; - - t_outdated->buffer = NULL; - buffer->transaction = NULL; - trace_binder_transaction_update_buffer_release(buffer); - binder_release_entire_buffer(proc, NULL, buffer, true); - binder_alloc_free_buf(&proc->alloc, buffer); - binder_free_txn_fixups(t_outdated); - kfree(t_outdated); - binder_stats_deleted(BINDER_STAT_TRANSACTION); - } - - if (oneway && frozen) - return BR_TRANSACTION_PENDING_FROZEN; - - return 0; -} - -/** - * binder_get_node_refs_for_txn() - Get required refs on node for txn - * @node: struct binder_node for which to get refs - * @procp: returns @node->proc if valid - * @error: if no @procp then returns BR_DEAD_REPLY - * - * User-space normally keeps the node alive when creating a transaction - * since it has a reference to the target. The local strong ref keeps it - * alive if the sending process dies before the target process processes - * the transaction. If the source process is malicious or has a reference - * counting bug, relying on the local strong ref can fail. - * - * Since user-space can cause the local strong ref to go away, we also take - * a tmpref on the node to ensure it survives while we are constructing - * the transaction. We also need a tmpref on the proc while we are - * constructing the transaction, so we take that here as well. - * - * Return: The target_node with refs taken or NULL if no @node->proc is NULL. - * Also sets @procp if valid. If the @node->proc is NULL indicating that the - * target proc has died, @error is set to BR_DEAD_REPLY. - */ -static struct binder_node *binder_get_node_refs_for_txn( - struct binder_node *node, - struct binder_proc **procp, - uint32_t *error) -{ - struct binder_node *target_node = NULL; - - binder_node_inner_lock(node); - if (node->proc) { - target_node = node; - binder_inc_node_nilocked(node, 1, 0, NULL); - binder_inc_node_tmpref_ilocked(node); - node->proc->tmp_ref++; - *procp = node->proc; - } else - *error = BR_DEAD_REPLY; - binder_node_inner_unlock(node); - - return target_node; -} - -static void binder_set_txn_from_error(struct binder_transaction *t, int id, - uint32_t command, int32_t param) -{ - struct binder_thread *from = binder_get_txn_from_and_acq_inner(t); - - if (!from) { - /* annotation for sparse */ - __release(&from->proc->inner_lock); - return; - } - - /* don't override existing errors */ - if (from->ee.command == BR_OK) - binder_set_extended_error(&from->ee, id, command, param); - binder_inner_proc_unlock(from->proc); - binder_thread_dec_tmpref(from); -} - -/** - * binder_netlink_report() - report a transaction failure via netlink - * @proc: the binder proc sending the transaction - * @t: the binder transaction that failed - * @data_size: the user provided data size for the transaction - * @error: enum binder_driver_return_protocol returned to sender - * - * Note that t->buffer is not safe to access here, as it may have been - * released (or not yet allocated). Callers should guarantee all the - * transaction items used here are safe to access. - */ -static void binder_netlink_report(struct binder_proc *proc, - struct binder_transaction *t, - u32 data_size, - u32 error) -{ - const char *context = proc->context->name; - struct sk_buff *skb; - void *hdr; - - if (!genl_has_listeners(&binder_nl_family, &init_net, - BINDER_NLGRP_REPORT)) - return; - - trace_binder_netlink_report(context, t, data_size, error); - - skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); - if (!skb) - return; - - hdr = genlmsg_put(skb, 0, 0, &binder_nl_family, 0, BINDER_CMD_REPORT); - if (!hdr) - goto free_skb; - - if (nla_put_u32(skb, BINDER_A_REPORT_ERROR, error) || - nla_put_string(skb, BINDER_A_REPORT_CONTEXT, context) || - nla_put_u32(skb, BINDER_A_REPORT_FROM_PID, t->from_pid) || - nla_put_u32(skb, BINDER_A_REPORT_FROM_TID, t->from_tid)) - goto cancel_skb; - - if (t->to_proc && - nla_put_u32(skb, BINDER_A_REPORT_TO_PID, t->to_proc->pid)) - goto cancel_skb; - - if (t->to_thread && - nla_put_u32(skb, BINDER_A_REPORT_TO_TID, t->to_thread->pid)) - goto cancel_skb; - - if (t->is_reply && nla_put_flag(skb, BINDER_A_REPORT_IS_REPLY)) - goto cancel_skb; - - if (nla_put_u32(skb, BINDER_A_REPORT_FLAGS, t->flags) || - nla_put_u32(skb, BINDER_A_REPORT_CODE, t->code) || - nla_put_u32(skb, BINDER_A_REPORT_DATA_SIZE, data_size)) - goto cancel_skb; - - genlmsg_end(skb, hdr); - genlmsg_multicast(&binder_nl_family, skb, 0, BINDER_NLGRP_REPORT, - GFP_KERNEL); - return; - -cancel_skb: - genlmsg_cancel(skb, hdr); -free_skb: - nlmsg_free(skb); -} - -static void binder_transaction(struct binder_proc *proc, - struct binder_thread *thread, - struct binder_transaction_data *tr, int reply, - binder_size_t extra_buffers_size) -{ - int ret; - struct binder_transaction *t; - struct binder_work *w; - struct binder_work *tcomplete; - binder_size_t buffer_offset = 0; - binder_size_t off_start_offset, off_end_offset; - binder_size_t off_min; - binder_size_t sg_buf_offset, sg_buf_end_offset; - binder_size_t user_offset = 0; - struct binder_proc *target_proc = NULL; - struct binder_thread *target_thread = NULL; - struct binder_node *target_node = NULL; - struct binder_transaction *in_reply_to = NULL; - struct binder_transaction_log_entry *e; - uint32_t return_error = 0; - uint32_t return_error_param = 0; - uint32_t return_error_line = 0; - binder_size_t last_fixup_obj_off = 0; - binder_size_t last_fixup_min_off = 0; - struct binder_context *context = proc->context; - int t_debug_id = atomic_inc_return(&binder_last_id); - ktime_t t_start_time = ktime_get(); - struct lsm_context lsmctx = { }; - size_t lsmctx_aligned_size = 0; - LIST_HEAD(sgc_head); - LIST_HEAD(pf_head); - const void __user *user_buffer = (const void __user *) - (uintptr_t)tr->data.ptr.buffer; - - e = binder_transaction_log_add(&binder_transaction_log); - e->debug_id = t_debug_id; - e->call_type = reply ? 2 : !!(tr->flags & TF_ONE_WAY); - e->from_proc = proc->pid; - e->from_thread = thread->pid; - e->target_handle = tr->target.handle; - e->data_size = tr->data_size; - e->offsets_size = tr->offsets_size; - strscpy(e->context_name, proc->context->name, BINDERFS_MAX_NAME); - - binder_inner_proc_lock(proc); - binder_set_extended_error(&thread->ee, t_debug_id, BR_OK, 0); - binder_inner_proc_unlock(proc); - - t = kzalloc_obj(*t); - if (!t) { - binder_txn_error("%d:%d cannot allocate transaction\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -ENOMEM; - return_error_line = __LINE__; - goto err_alloc_t_failed; - } - INIT_LIST_HEAD(&t->fd_fixups); - binder_stats_created(BINDER_STAT_TRANSACTION); - spin_lock_init(&t->lock); - t->debug_id = t_debug_id; - t->start_time = t_start_time; - t->from_pid = proc->pid; - t->from_tid = thread->pid; - t->sender_euid = current_euid(); - t->code = tr->code; - t->flags = tr->flags; - t->priority = task_nice(current); - t->work.type = BINDER_WORK_TRANSACTION; - t->is_async = !reply && (tr->flags & TF_ONE_WAY); - t->is_reply = reply; - if (!reply && !(tr->flags & TF_ONE_WAY)) - t->from = thread; - - if (reply) { - binder_inner_proc_lock(proc); - in_reply_to = thread->transaction_stack; - if (in_reply_to == NULL) { - binder_inner_proc_unlock(proc); - binder_user_error("%d:%d got reply transaction with no transaction stack\n", - proc->pid, thread->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -EPROTO; - return_error_line = __LINE__; - goto err_empty_call_stack; - } - if (in_reply_to->to_thread != thread) { - spin_lock(&in_reply_to->lock); - binder_user_error("%d:%d got reply transaction with bad transaction stack, transaction %d has target %d:%d\n", - proc->pid, thread->pid, in_reply_to->debug_id, - in_reply_to->to_proc ? - in_reply_to->to_proc->pid : 0, - in_reply_to->to_thread ? - in_reply_to->to_thread->pid : 0); - spin_unlock(&in_reply_to->lock); - binder_inner_proc_unlock(proc); - return_error = BR_FAILED_REPLY; - return_error_param = -EPROTO; - return_error_line = __LINE__; - in_reply_to = NULL; - goto err_bad_call_stack; - } - thread->transaction_stack = in_reply_to->to_parent; - binder_inner_proc_unlock(proc); - binder_set_nice(in_reply_to->saved_priority); - target_thread = binder_get_txn_from_and_acq_inner(in_reply_to); - if (target_thread == NULL) { - /* annotation for sparse */ - __release(&target_thread->proc->inner_lock); - binder_txn_error("%d:%d reply target not found\n", - thread->pid, proc->pid); - return_error = BR_DEAD_REPLY; - return_error_line = __LINE__; - goto err_dead_binder; - } - if (target_thread->transaction_stack != in_reply_to) { - binder_user_error("%d:%d got reply transaction with bad target transaction stack %d, expected %d\n", - proc->pid, thread->pid, - target_thread->transaction_stack ? - target_thread->transaction_stack->debug_id : 0, - in_reply_to->debug_id); - binder_inner_proc_unlock(target_thread->proc); - return_error = BR_FAILED_REPLY; - return_error_param = -EPROTO; - return_error_line = __LINE__; - in_reply_to = NULL; - target_thread = NULL; - goto err_dead_binder; - } - target_proc = target_thread->proc; - target_proc->tmp_ref++; - binder_inner_proc_unlock(target_thread->proc); - } else { - if (tr->target.handle) { - struct binder_ref *ref; - - /* - * There must already be a strong ref - * on this node. If so, do a strong - * increment on the node to ensure it - * stays alive until the transaction is - * done. - */ - binder_proc_lock(proc); - ref = binder_get_ref_olocked(proc, tr->target.handle, - true); - if (ref) { - target_node = binder_get_node_refs_for_txn( - ref->node, &target_proc, - &return_error); - } else { - binder_user_error("%d:%d got transaction to invalid handle, %u\n", - proc->pid, thread->pid, tr->target.handle); - return_error = BR_FAILED_REPLY; - } - binder_proc_unlock(proc); - } else { - mutex_lock(&context->context_mgr_node_lock); - target_node = context->binder_context_mgr_node; - if (target_node) - target_node = binder_get_node_refs_for_txn( - target_node, &target_proc, - &return_error); - else - return_error = BR_DEAD_REPLY; - mutex_unlock(&context->context_mgr_node_lock); - if (target_node && target_proc->pid == proc->pid) { - binder_user_error("%d:%d got transaction to context manager from process owning it\n", - proc->pid, thread->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_invalid_target_handle; - } - } - if (!target_node) { - binder_txn_error("%d:%d cannot find target node\n", - proc->pid, thread->pid); - /* return_error is set above */ - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_dead_binder; - } - e->to_node = target_node->debug_id; - if (WARN_ON(proc == target_proc)) { - binder_txn_error("%d:%d self transactions not allowed\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_invalid_target_handle; - } - if (security_binder_transaction(proc->cred, - target_proc->cred) < 0) { - binder_txn_error("%d:%d transaction credentials failed\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -EPERM; - return_error_line = __LINE__; - goto err_invalid_target_handle; - } - binder_inner_proc_lock(proc); - - w = list_first_entry_or_null(&thread->todo, - struct binder_work, entry); - if (!(tr->flags & TF_ONE_WAY) && w && - w->type == BINDER_WORK_TRANSACTION) { - /* - * Do not allow new outgoing transaction from a - * thread that has a transaction at the head of - * its todo list. Only need to check the head - * because binder_select_thread_ilocked picks a - * thread from proc->waiting_threads to enqueue - * the transaction, and nothing is queued to the - * todo list while the thread is on waiting_threads. - */ - binder_user_error("%d:%d new transaction not allowed when there is a transaction on thread todo\n", - proc->pid, thread->pid); - binder_inner_proc_unlock(proc); - return_error = BR_FAILED_REPLY; - return_error_param = -EPROTO; - return_error_line = __LINE__; - goto err_bad_todo_list; - } - - if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) { - struct binder_transaction *tmp; - - tmp = thread->transaction_stack; - if (tmp->to_thread != thread) { - spin_lock(&tmp->lock); - binder_user_error("%d:%d got new transaction with bad transaction stack, transaction %d has target %d:%d\n", - proc->pid, thread->pid, tmp->debug_id, - tmp->to_proc ? tmp->to_proc->pid : 0, - tmp->to_thread ? - tmp->to_thread->pid : 0); - spin_unlock(&tmp->lock); - binder_inner_proc_unlock(proc); - return_error = BR_FAILED_REPLY; - return_error_param = -EPROTO; - return_error_line = __LINE__; - goto err_bad_call_stack; - } - while (tmp) { - struct binder_thread *from; - - spin_lock(&tmp->lock); - from = tmp->from; - if (from && from->proc == target_proc) { - atomic_inc(&from->tmp_ref); - target_thread = from; - spin_unlock(&tmp->lock); - break; - } - spin_unlock(&tmp->lock); - tmp = tmp->from_parent; - } - } - binder_inner_proc_unlock(proc); - } - - t->to_proc = target_proc; - t->to_thread = target_thread; - if (target_thread) - e->to_thread = target_thread->pid; - e->to_proc = target_proc->pid; - - tcomplete = kzalloc_obj(*tcomplete); - if (tcomplete == NULL) { - binder_txn_error("%d:%d cannot allocate work for transaction\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -ENOMEM; - return_error_line = __LINE__; - goto err_alloc_tcomplete_failed; - } - binder_stats_created(BINDER_STAT_TRANSACTION_COMPLETE); - - if (reply) - binder_debug(BINDER_DEBUG_TRANSACTION, - "%d:%d BC_REPLY %d -> %d:%d, data size %lld-%lld-%lld\n", - proc->pid, thread->pid, t->debug_id, - target_proc->pid, target_thread->pid, - (u64)tr->data_size, (u64)tr->offsets_size, - (u64)extra_buffers_size); - else - binder_debug(BINDER_DEBUG_TRANSACTION, - "%d:%d BC_TRANSACTION %d -> %d - node %d, data size %lld-%lld-%lld\n", - proc->pid, thread->pid, t->debug_id, - target_proc->pid, target_node->debug_id, - (u64)tr->data_size, (u64)tr->offsets_size, - (u64)extra_buffers_size); - - if (target_node && target_node->txn_security_ctx) { - u32 secid; - - security_cred_getsecid(proc->cred, &secid); - ret = security_secid_to_secctx(secid, &lsmctx); - if (ret < 0) { - binder_txn_error("%d:%d failed to get security context\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = ret; - return_error_line = __LINE__; - goto err_get_secctx_failed; - } - lsmctx_aligned_size = ALIGN(lsmctx.len, sizeof(u64)); - extra_buffers_size += lsmctx_aligned_size; - if (extra_buffers_size < lsmctx_aligned_size) { - binder_txn_error("%d:%d integer overflow of extra_buffers_size\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_bad_extra_size; - } - } - - trace_binder_transaction(reply, t, target_node); - - t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size, - tr->offsets_size, extra_buffers_size, - !reply && (t->flags & TF_ONE_WAY)); - if (IS_ERR(t->buffer)) { - char *s; - - ret = PTR_ERR(t->buffer); - s = (ret == -ESRCH) ? ": vma cleared, target dead or dying" - : (ret == -ENOSPC) ? ": no space left" - : (ret == -ENOMEM) ? ": memory allocation failed" - : ""; - binder_txn_error("cannot allocate buffer%s", s); - - return_error_param = PTR_ERR(t->buffer); - return_error = return_error_param == -ESRCH ? - BR_DEAD_REPLY : BR_FAILED_REPLY; - return_error_line = __LINE__; - t->buffer = NULL; - goto err_binder_alloc_buf_failed; - } - if (lsmctx.context) { - int err; - size_t buf_offset = ALIGN(tr->data_size, sizeof(void *)) + - ALIGN(tr->offsets_size, sizeof(void *)) + - ALIGN(extra_buffers_size, sizeof(void *)) - - lsmctx_aligned_size; - - t->security_ctx = t->buffer->user_data + buf_offset; - err = binder_alloc_copy_to_buffer(&target_proc->alloc, - t->buffer, buf_offset, - lsmctx.context, lsmctx.len); - if (err) { - t->security_ctx = 0; - WARN_ON(1); - } - security_release_secctx(&lsmctx); - lsmctx.context = NULL; - } - t->buffer->debug_id = t->debug_id; - t->buffer->transaction = t; - t->buffer->target_node = target_node; - t->buffer->clear_on_free = !!(t->flags & TF_CLEAR_BUF); - trace_binder_transaction_alloc_buf(t->buffer); - - if (binder_alloc_copy_user_to_buffer( - &target_proc->alloc, - t->buffer, - ALIGN(tr->data_size, sizeof(void *)), - (const void __user *) - (uintptr_t)tr->data.ptr.offsets, - tr->offsets_size)) { - binder_user_error("%d:%d got transaction with invalid offsets ptr\n", - proc->pid, thread->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -EFAULT; - return_error_line = __LINE__; - goto err_copy_data_failed; - } - if (!IS_ALIGNED(tr->offsets_size, sizeof(binder_size_t))) { - binder_user_error("%d:%d got transaction with invalid offsets size, %lld\n", - proc->pid, thread->pid, (u64)tr->offsets_size); - return_error = BR_FAILED_REPLY; - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_bad_offset; - } - if (!IS_ALIGNED(extra_buffers_size, sizeof(u64))) { - binder_user_error("%d:%d got transaction with unaligned buffers size, %lld\n", - proc->pid, thread->pid, - (u64)extra_buffers_size); - return_error = BR_FAILED_REPLY; - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_bad_offset; - } - off_start_offset = ALIGN(tr->data_size, sizeof(void *)); - buffer_offset = off_start_offset; - off_end_offset = off_start_offset + tr->offsets_size; - sg_buf_offset = ALIGN(off_end_offset, sizeof(void *)); - sg_buf_end_offset = sg_buf_offset + extra_buffers_size - - lsmctx_aligned_size; - off_min = 0; - for (buffer_offset = off_start_offset; buffer_offset < off_end_offset; - buffer_offset += sizeof(binder_size_t)) { - struct binder_object_header *hdr; - size_t object_size; - struct binder_object object; - binder_size_t object_offset; - binder_size_t copy_size; - - if (binder_alloc_copy_from_buffer(&target_proc->alloc, - &object_offset, - t->buffer, - buffer_offset, - sizeof(object_offset))) { - binder_txn_error("%d:%d copy offset from buffer failed\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_bad_offset; - } - - /* - * Copy the source user buffer up to the next object - * that will be processed. - */ - copy_size = object_offset - user_offset; - if (copy_size && (user_offset > object_offset || - object_offset > tr->data_size || - binder_alloc_copy_user_to_buffer( - &target_proc->alloc, - t->buffer, user_offset, - user_buffer + user_offset, - copy_size))) { - binder_user_error("%d:%d got transaction with invalid data ptr\n", - proc->pid, thread->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -EFAULT; - return_error_line = __LINE__; - goto err_copy_data_failed; - } - object_size = binder_get_object(target_proc, user_buffer, - t->buffer, object_offset, &object); - if (object_size == 0 || object_offset < off_min) { - binder_user_error("%d:%d got transaction with invalid offset (%lld, min %lld max %lld) or object.\n", - proc->pid, thread->pid, - (u64)object_offset, - (u64)off_min, - (u64)t->buffer->data_size); - return_error = BR_FAILED_REPLY; - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_bad_offset; - } - /* - * Set offset to the next buffer fragment to be - * copied - */ - user_offset = object_offset + object_size; - - hdr = &object.hdr; - off_min = object_offset + object_size; - switch (hdr->type) { - case BINDER_TYPE_BINDER: - case BINDER_TYPE_WEAK_BINDER: { - struct flat_binder_object *fp; - - fp = to_flat_binder_object(hdr); - ret = binder_translate_binder(fp, t, thread); - - if (ret < 0 || - binder_alloc_copy_to_buffer(&target_proc->alloc, - t->buffer, - object_offset, - fp, sizeof(*fp))) { - binder_txn_error("%d:%d translate binder failed\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = ret; - return_error_line = __LINE__; - goto err_translate_failed; - } - } break; - case BINDER_TYPE_HANDLE: - case BINDER_TYPE_WEAK_HANDLE: { - struct flat_binder_object *fp; - - fp = to_flat_binder_object(hdr); - ret = binder_translate_handle(fp, t, thread); - if (ret < 0 || - binder_alloc_copy_to_buffer(&target_proc->alloc, - t->buffer, - object_offset, - fp, sizeof(*fp))) { - binder_txn_error("%d:%d translate handle failed\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = ret; - return_error_line = __LINE__; - goto err_translate_failed; - } - } break; - - case BINDER_TYPE_FD: { - struct binder_fd_object *fp = to_binder_fd_object(hdr); - binder_size_t fd_offset = object_offset + - (uintptr_t)&fp->fd - (uintptr_t)fp; - int ret = binder_translate_fd(fp->fd, fd_offset, t, - thread, in_reply_to); - - fp->pad_binder = 0; - if (ret < 0 || - binder_alloc_copy_to_buffer(&target_proc->alloc, - t->buffer, - object_offset, - fp, sizeof(*fp))) { - binder_txn_error("%d:%d translate fd failed\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = ret; - return_error_line = __LINE__; - goto err_translate_failed; - } - } break; - case BINDER_TYPE_FDA: { - struct binder_object ptr_object; - binder_size_t parent_offset; - struct binder_object user_object; - size_t user_parent_size; - struct binder_fd_array_object *fda = - to_binder_fd_array_object(hdr); - size_t num_valid = (buffer_offset - off_start_offset) / - sizeof(binder_size_t); - struct binder_buffer_object *parent = - binder_validate_ptr(target_proc, t->buffer, - &ptr_object, fda->parent, - off_start_offset, - &parent_offset, - num_valid); - if (!parent) { - binder_user_error("%d:%d got transaction with invalid parent offset or type\n", - proc->pid, thread->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_bad_parent; - } - if (!binder_validate_fixup(target_proc, t->buffer, - off_start_offset, - parent_offset, - fda->parent_offset, - last_fixup_obj_off, - last_fixup_min_off)) { - binder_user_error("%d:%d got transaction with out-of-order buffer fixup\n", - proc->pid, thread->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_bad_parent; - } - /* - * We need to read the user version of the parent - * object to get the original user offset - */ - user_parent_size = - binder_get_object(proc, user_buffer, t->buffer, - parent_offset, &user_object); - if (user_parent_size != sizeof(user_object.bbo)) { - binder_user_error("%d:%d invalid ptr object size: %zd vs %zd\n", - proc->pid, thread->pid, - user_parent_size, - sizeof(user_object.bbo)); - return_error = BR_FAILED_REPLY; - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_bad_parent; - } - ret = binder_translate_fd_array(&pf_head, fda, - user_buffer, parent, - &user_object.bbo, t, - thread, in_reply_to); - if (!ret) - ret = binder_alloc_copy_to_buffer(&target_proc->alloc, - t->buffer, - object_offset, - fda, sizeof(*fda)); - if (ret) { - binder_txn_error("%d:%d translate fd array failed\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = ret > 0 ? -EINVAL : ret; - return_error_line = __LINE__; - goto err_translate_failed; - } - last_fixup_obj_off = parent_offset; - last_fixup_min_off = - fda->parent_offset + sizeof(u32) * fda->num_fds; - } break; - case BINDER_TYPE_PTR: { - struct binder_buffer_object *bp = - to_binder_buffer_object(hdr); - size_t buf_left = sg_buf_end_offset - sg_buf_offset; - size_t num_valid; - - if (bp->length > buf_left) { - binder_user_error("%d:%d got transaction with too large buffer\n", - proc->pid, thread->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_bad_offset; - } - ret = binder_defer_copy(&sgc_head, sg_buf_offset, - (const void __user *)(uintptr_t)bp->buffer, - bp->length); - if (ret) { - binder_txn_error("%d:%d deferred copy failed\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = ret; - return_error_line = __LINE__; - goto err_translate_failed; - } - /* Fixup buffer pointer to target proc address space */ - bp->buffer = t->buffer->user_data + sg_buf_offset; - sg_buf_offset += ALIGN(bp->length, sizeof(u64)); - - num_valid = (buffer_offset - off_start_offset) / - sizeof(binder_size_t); - ret = binder_fixup_parent(&pf_head, t, - thread, bp, - off_start_offset, - num_valid, - last_fixup_obj_off, - last_fixup_min_off); - if (ret < 0 || - binder_alloc_copy_to_buffer(&target_proc->alloc, - t->buffer, - object_offset, - bp, sizeof(*bp))) { - binder_txn_error("%d:%d failed to fixup parent\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = ret; - return_error_line = __LINE__; - goto err_translate_failed; - } - last_fixup_obj_off = object_offset; - last_fixup_min_off = 0; - } break; - default: - binder_user_error("%d:%d got transaction with invalid object type, %x\n", - proc->pid, thread->pid, hdr->type); - return_error = BR_FAILED_REPLY; - return_error_param = -EINVAL; - return_error_line = __LINE__; - goto err_bad_object_type; - } - } - /* Done processing objects, copy the rest of the buffer */ - if (binder_alloc_copy_user_to_buffer( - &target_proc->alloc, - t->buffer, user_offset, - user_buffer + user_offset, - tr->data_size - user_offset)) { - binder_user_error("%d:%d got transaction with invalid data ptr\n", - proc->pid, thread->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -EFAULT; - return_error_line = __LINE__; - goto err_copy_data_failed; - } - - ret = binder_do_deferred_txn_copies(&target_proc->alloc, t->buffer, - &sgc_head, &pf_head); - if (ret) { - binder_user_error("%d:%d got transaction with invalid offsets ptr\n", - proc->pid, thread->pid); - return_error = BR_FAILED_REPLY; - return_error_param = ret; - return_error_line = __LINE__; - goto err_copy_data_failed; - } - if (t->buffer->oneway_spam_suspect) { - tcomplete->type = BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT; - binder_netlink_report(proc, t, tr->data_size, - BR_ONEWAY_SPAM_SUSPECT); - } else { - tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; - } - - if (reply) { - binder_enqueue_thread_work(thread, tcomplete); - binder_inner_proc_lock(target_proc); - if (target_thread->is_dead) { - return_error = BR_DEAD_REPLY; - binder_inner_proc_unlock(target_proc); - goto err_dead_proc_or_thread; - } - BUG_ON(t->buffer->async_transaction != 0); - binder_pop_transaction_ilocked(target_thread, in_reply_to); - binder_enqueue_thread_work_ilocked(target_thread, &t->work); - target_proc->outstanding_txns++; - binder_inner_proc_unlock(target_proc); - wake_up_interruptible_sync(&target_thread->wait); - binder_free_transaction(in_reply_to); - } else if (!(t->flags & TF_ONE_WAY)) { - BUG_ON(t->buffer->async_transaction != 0); - binder_inner_proc_lock(proc); - /* - * Defer the TRANSACTION_COMPLETE, so we don't return to - * userspace immediately; this allows the target process to - * immediately start processing this transaction, reducing - * latency. We will then return the TRANSACTION_COMPLETE when - * the target replies (or there is an error). - */ - binder_enqueue_deferred_thread_work_ilocked(thread, tcomplete); - t->from_parent = thread->transaction_stack; - thread->transaction_stack = t; - binder_inner_proc_unlock(proc); - return_error = binder_proc_transaction(t, - target_proc, target_thread); - if (return_error) { - binder_inner_proc_lock(proc); - binder_pop_transaction_ilocked(thread, t); - binder_inner_proc_unlock(proc); - goto err_dead_proc_or_thread; - } - } else { - /* - * Make a transaction copy. It is not safe to access 't' after - * binder_proc_transaction() reported a pending frozen. The - * target could thaw and consume the transaction at any point. - * Instead, use a safe 't_copy' for binder_netlink_report(). - */ - struct binder_transaction t_copy = *t; - - BUG_ON(target_node == NULL); - BUG_ON(t->buffer->async_transaction != 1); - return_error = binder_proc_transaction(t, target_proc, NULL); - /* - * Let the caller know when async transaction reaches a frozen - * process and is put in a pending queue, waiting for the target - * process to be unfrozen. - */ - if (return_error == BR_TRANSACTION_PENDING_FROZEN) { - tcomplete->type = BINDER_WORK_TRANSACTION_PENDING; - binder_netlink_report(proc, &t_copy, tr->data_size, - return_error); - } - binder_enqueue_thread_work(thread, tcomplete); - if (return_error && - return_error != BR_TRANSACTION_PENDING_FROZEN) - goto err_dead_proc_or_thread; - } - if (target_thread) - binder_thread_dec_tmpref(target_thread); - binder_proc_dec_tmpref(target_proc); - if (target_node) - binder_dec_node_tmpref(target_node); - /* - * write barrier to synchronize with initialization - * of log entry - */ - smp_wmb(); - WRITE_ONCE(e->debug_id_done, t_debug_id); - return; - -err_dead_proc_or_thread: - binder_txn_error("%d:%d %s process or thread\n", - proc->pid, thread->pid, - return_error == BR_FROZEN_REPLY ? "frozen" : "dead"); - return_error_line = __LINE__; - binder_dequeue_work(proc, tcomplete); -err_translate_failed: -err_bad_object_type: -err_bad_offset: -err_bad_parent: -err_copy_data_failed: - binder_cleanup_deferred_txn_lists(&sgc_head, &pf_head); - binder_free_txn_fixups(t); - trace_binder_transaction_failed_buffer_release(t->buffer); - binder_transaction_buffer_release(target_proc, NULL, t->buffer, - buffer_offset, true); - if (target_node) - binder_dec_node_tmpref(target_node); - target_node = NULL; - t->buffer->transaction = NULL; - binder_alloc_free_buf(&target_proc->alloc, t->buffer); -err_binder_alloc_buf_failed: -err_bad_extra_size: - if (lsmctx.context) - security_release_secctx(&lsmctx); -err_get_secctx_failed: - kfree(tcomplete); - binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE); -err_alloc_tcomplete_failed: - if (trace_binder_txn_latency_free_enabled()) - binder_txn_latency_free(t); -err_bad_todo_list: -err_bad_call_stack: -err_empty_call_stack: -err_dead_binder: -err_invalid_target_handle: - if (target_node) { - binder_dec_node(target_node, 1, 0); - binder_dec_node_tmpref(target_node); - } - - binder_netlink_report(proc, t, tr->data_size, return_error); - kfree(t); - binder_stats_deleted(BINDER_STAT_TRANSACTION); -err_alloc_t_failed: - - binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, - "%d:%d transaction %s to %d:%d failed %d/%d/%d, code %u size %lld-%lld line %d\n", - proc->pid, thread->pid, reply ? "reply" : - (tr->flags & TF_ONE_WAY ? "async" : "call"), - target_proc ? target_proc->pid : 0, - target_thread ? target_thread->pid : 0, - t_debug_id, return_error, return_error_param, - tr->code, (u64)tr->data_size, (u64)tr->offsets_size, - return_error_line); - - if (target_thread) - binder_thread_dec_tmpref(target_thread); - if (target_proc) - binder_proc_dec_tmpref(target_proc); - - { - struct binder_transaction_log_entry *fe; - - e->return_error = return_error; - e->return_error_param = return_error_param; - e->return_error_line = return_error_line; - fe = binder_transaction_log_add(&binder_transaction_log_failed); - *fe = *e; - /* - * write barrier to synchronize with initialization - * of log entry - */ - smp_wmb(); - WRITE_ONCE(e->debug_id_done, t_debug_id); - WRITE_ONCE(fe->debug_id_done, t_debug_id); - } - - BUG_ON(thread->return_error.cmd != BR_OK); - if (in_reply_to) { - binder_set_txn_from_error(in_reply_to, t_debug_id, - return_error, return_error_param); - thread->return_error.cmd = BR_TRANSACTION_COMPLETE; - binder_enqueue_thread_work(thread, &thread->return_error.work); - binder_send_failed_reply(in_reply_to, return_error); - } else { - binder_inner_proc_lock(proc); - binder_set_extended_error(&thread->ee, t_debug_id, - return_error, return_error_param); - binder_inner_proc_unlock(proc); - thread->return_error.cmd = return_error; - binder_enqueue_thread_work(thread, &thread->return_error.work); - } -} - -static int -binder_request_freeze_notification(struct binder_proc *proc, - struct binder_thread *thread, - struct binder_handle_cookie *handle_cookie) -{ - struct binder_ref_freeze *freeze; - struct binder_ref *ref; - - freeze = kzalloc_obj(*freeze); - if (!freeze) - return -ENOMEM; - binder_proc_lock(proc); - ref = binder_get_ref_olocked(proc, handle_cookie->handle, false); - if (!ref) { - binder_user_error("%d:%d BC_REQUEST_FREEZE_NOTIFICATION invalid ref %d\n", - proc->pid, thread->pid, handle_cookie->handle); - binder_proc_unlock(proc); - kfree(freeze); - return -EINVAL; - } - - binder_node_lock(ref->node); - if (ref->freeze) { - binder_user_error("%d:%d BC_REQUEST_FREEZE_NOTIFICATION already set\n", - proc->pid, thread->pid); - binder_node_unlock(ref->node); - binder_proc_unlock(proc); - kfree(freeze); - return -EINVAL; - } - - binder_stats_created(BINDER_STAT_FREEZE); - INIT_LIST_HEAD(&freeze->work.entry); - freeze->cookie = handle_cookie->cookie; - freeze->work.type = BINDER_WORK_FROZEN_BINDER; - ref->freeze = freeze; - - if (ref->node->proc) { - binder_inner_proc_lock(ref->node->proc); - freeze->is_frozen = ref->node->proc->is_frozen; - binder_inner_proc_unlock(ref->node->proc); - - binder_inner_proc_lock(proc); - binder_enqueue_work_ilocked(&freeze->work, &proc->todo); - binder_wakeup_proc_ilocked(proc); - binder_inner_proc_unlock(proc); - } - - binder_node_unlock(ref->node); - binder_proc_unlock(proc); - return 0; -} - -static int -binder_clear_freeze_notification(struct binder_proc *proc, - struct binder_thread *thread, - struct binder_handle_cookie *handle_cookie) -{ - struct binder_ref_freeze *freeze; - struct binder_ref *ref; - - binder_proc_lock(proc); - ref = binder_get_ref_olocked(proc, handle_cookie->handle, false); - if (!ref) { - binder_user_error("%d:%d BC_CLEAR_FREEZE_NOTIFICATION invalid ref %d\n", - proc->pid, thread->pid, handle_cookie->handle); - binder_proc_unlock(proc); - return -EINVAL; - } - - binder_node_lock(ref->node); - - if (!ref->freeze) { - binder_user_error("%d:%d BC_CLEAR_FREEZE_NOTIFICATION freeze notification not active\n", - proc->pid, thread->pid); - binder_node_unlock(ref->node); - binder_proc_unlock(proc); - return -EINVAL; - } - freeze = ref->freeze; - binder_inner_proc_lock(proc); - if (freeze->cookie != handle_cookie->cookie) { - binder_user_error("%d:%d BC_CLEAR_FREEZE_NOTIFICATION freeze notification cookie mismatch %016llx != %016llx\n", - proc->pid, thread->pid, (u64)freeze->cookie, - (u64)handle_cookie->cookie); - binder_inner_proc_unlock(proc); - binder_node_unlock(ref->node); - binder_proc_unlock(proc); - return -EINVAL; - } - ref->freeze = NULL; - /* - * Take the existing freeze object and overwrite its work type. There are three cases here: - * 1. No pending notification. In this case just add the work to the queue. - * 2. A notification was sent and is pending an ack from userspace. Once an ack arrives, we - * should resend with the new work type. - * 3. A notification is pending to be sent. Since the work is already in the queue, nothing - * needs to be done here. - */ - freeze->work.type = BINDER_WORK_CLEAR_FREEZE_NOTIFICATION; - if (list_empty(&freeze->work.entry)) { - binder_enqueue_work_ilocked(&freeze->work, &proc->todo); - binder_wakeup_proc_ilocked(proc); - } else if (freeze->sent) { - freeze->resend = true; - } - binder_inner_proc_unlock(proc); - binder_node_unlock(ref->node); - binder_proc_unlock(proc); - return 0; -} - -static int -binder_freeze_notification_done(struct binder_proc *proc, - struct binder_thread *thread, - binder_uintptr_t cookie) -{ - struct binder_ref_freeze *freeze = NULL; - struct binder_work *w; - - binder_inner_proc_lock(proc); - list_for_each_entry(w, &proc->delivered_freeze, entry) { - struct binder_ref_freeze *tmp_freeze = - container_of(w, struct binder_ref_freeze, work); - - if (tmp_freeze->cookie == cookie) { - freeze = tmp_freeze; - break; - } - } - if (!freeze) { - binder_user_error("%d:%d BC_FREEZE_NOTIFICATION_DONE %016llx not found\n", - proc->pid, thread->pid, (u64)cookie); - binder_inner_proc_unlock(proc); - return -EINVAL; - } - binder_dequeue_work_ilocked(&freeze->work); - freeze->sent = false; - if (freeze->resend) { - freeze->resend = false; - binder_enqueue_work_ilocked(&freeze->work, &proc->todo); - binder_wakeup_proc_ilocked(proc); - } - binder_inner_proc_unlock(proc); - return 0; -} - -/** - * binder_free_buf() - free the specified buffer - * @proc: binder proc that owns buffer - * @thread: binder thread performing the buffer release - * @buffer: buffer to be freed - * @is_failure: failed to send transaction - * - * If the buffer is for an async transaction, enqueue the next async - * transaction from the node. - * - * Cleanup the buffer and free it. - */ -static void -binder_free_buf(struct binder_proc *proc, - struct binder_thread *thread, - struct binder_buffer *buffer, bool is_failure) -{ - binder_inner_proc_lock(proc); - if (buffer->transaction) { - buffer->transaction->buffer = NULL; - buffer->transaction = NULL; - } - binder_inner_proc_unlock(proc); - if (buffer->async_transaction && buffer->target_node) { - struct binder_node *buf_node; - struct binder_work *w; - - buf_node = buffer->target_node; - binder_node_inner_lock(buf_node); - BUG_ON(!buf_node->has_async_transaction); - BUG_ON(buf_node->proc != proc); - w = binder_dequeue_work_head_ilocked( - &buf_node->async_todo); - if (!w) { - buf_node->has_async_transaction = false; - } else { - binder_enqueue_work_ilocked( - w, &proc->todo); - binder_wakeup_proc_ilocked(proc); - } - binder_node_inner_unlock(buf_node); - } - trace_binder_transaction_buffer_release(buffer); - binder_release_entire_buffer(proc, thread, buffer, is_failure); - binder_alloc_free_buf(&proc->alloc, buffer); -} - -static int binder_thread_write(struct binder_proc *proc, - struct binder_thread *thread, - binder_uintptr_t binder_buffer, size_t size, - binder_size_t *consumed) -{ - uint32_t cmd; - struct binder_context *context = proc->context; - void __user *buffer = (void __user *)(uintptr_t)binder_buffer; - void __user *ptr = buffer + *consumed; - void __user *end = buffer + size; - - while (ptr < end && thread->return_error.cmd == BR_OK) { - int ret; - - if (get_user(cmd, (uint32_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(uint32_t); - trace_binder_command(cmd); - if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.bc)) { - atomic_inc(&binder_stats.bc[_IOC_NR(cmd)]); - atomic_inc(&proc->stats.bc[_IOC_NR(cmd)]); - atomic_inc(&thread->stats.bc[_IOC_NR(cmd)]); - } - switch (cmd) { - case BC_INCREFS: - case BC_ACQUIRE: - case BC_RELEASE: - case BC_DECREFS: { - uint32_t target; - const char *debug_string; - bool strong = cmd == BC_ACQUIRE || cmd == BC_RELEASE; - bool increment = cmd == BC_INCREFS || cmd == BC_ACQUIRE; - struct binder_ref_data rdata; - - if (get_user(target, (uint32_t __user *)ptr)) - return -EFAULT; - - ptr += sizeof(uint32_t); - ret = -1; - if (increment && !target) { - struct binder_node *ctx_mgr_node; - - mutex_lock(&context->context_mgr_node_lock); - ctx_mgr_node = context->binder_context_mgr_node; - if (ctx_mgr_node) { - if (ctx_mgr_node->proc == proc) { - binder_user_error("%d:%d context manager tried to acquire desc 0\n", - proc->pid, thread->pid); - mutex_unlock(&context->context_mgr_node_lock); - return -EINVAL; - } - ret = binder_inc_ref_for_node( - proc, ctx_mgr_node, - strong, NULL, &rdata); - } - mutex_unlock(&context->context_mgr_node_lock); - } - if (ret) - ret = binder_update_ref_for_handle( - proc, target, increment, strong, - &rdata); - if (!ret && rdata.desc != target) { - binder_user_error("%d:%d tried to acquire reference to desc %d, got %d instead\n", - proc->pid, thread->pid, - target, rdata.desc); - } - switch (cmd) { - case BC_INCREFS: - debug_string = "IncRefs"; - break; - case BC_ACQUIRE: - debug_string = "Acquire"; - break; - case BC_RELEASE: - debug_string = "Release"; - break; - case BC_DECREFS: - default: - debug_string = "DecRefs"; - break; - } - if (ret) { - binder_user_error("%d:%d %s %d refcount change on invalid ref %d ret %d\n", - proc->pid, thread->pid, debug_string, - strong, target, ret); - break; - } - binder_debug(BINDER_DEBUG_USER_REFS, - "%d:%d %s ref %d desc %d s %d w %d\n", - proc->pid, thread->pid, debug_string, - rdata.debug_id, rdata.desc, rdata.strong, - rdata.weak); - break; - } - case BC_INCREFS_DONE: - case BC_ACQUIRE_DONE: { - binder_uintptr_t node_ptr; - binder_uintptr_t cookie; - struct binder_node *node; - bool free_node; - - if (get_user(node_ptr, (binder_uintptr_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(binder_uintptr_t); - if (get_user(cookie, (binder_uintptr_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(binder_uintptr_t); - node = binder_get_node(proc, node_ptr); - if (node == NULL) { - binder_user_error("%d:%d %s u%016llx no match\n", - proc->pid, thread->pid, - cmd == BC_INCREFS_DONE ? - "BC_INCREFS_DONE" : - "BC_ACQUIRE_DONE", - (u64)node_ptr); - break; - } - if (cookie != node->cookie) { - binder_user_error("%d:%d %s u%016llx node %d cookie mismatch %016llx != %016llx\n", - proc->pid, thread->pid, - cmd == BC_INCREFS_DONE ? - "BC_INCREFS_DONE" : "BC_ACQUIRE_DONE", - (u64)node_ptr, node->debug_id, - (u64)cookie, (u64)node->cookie); - binder_put_node(node); - break; - } - binder_node_inner_lock(node); - if (cmd == BC_ACQUIRE_DONE) { - if (node->pending_strong_ref == 0) { - binder_user_error("%d:%d BC_ACQUIRE_DONE node %d has no pending acquire request\n", - proc->pid, thread->pid, - node->debug_id); - binder_node_inner_unlock(node); - binder_put_node(node); - break; - } - node->pending_strong_ref = 0; - } else { - if (node->pending_weak_ref == 0) { - binder_user_error("%d:%d BC_INCREFS_DONE node %d has no pending increfs request\n", - proc->pid, thread->pid, - node->debug_id); - binder_node_inner_unlock(node); - binder_put_node(node); - break; - } - node->pending_weak_ref = 0; - } - free_node = binder_dec_node_nilocked(node, - cmd == BC_ACQUIRE_DONE, 0); - WARN_ON(free_node); - binder_debug(BINDER_DEBUG_USER_REFS, - "%d:%d %s node %d ls %d lw %d tr %d\n", - proc->pid, thread->pid, - cmd == BC_INCREFS_DONE ? "BC_INCREFS_DONE" : "BC_ACQUIRE_DONE", - node->debug_id, node->local_strong_refs, - node->local_weak_refs, node->tmp_refs); - binder_node_inner_unlock(node); - binder_put_node(node); - break; - } - case BC_ATTEMPT_ACQUIRE: - pr_err("BC_ATTEMPT_ACQUIRE not supported\n"); - return -EINVAL; - case BC_ACQUIRE_RESULT: - pr_err("BC_ACQUIRE_RESULT not supported\n"); - return -EINVAL; - - case BC_FREE_BUFFER: { - binder_uintptr_t data_ptr; - struct binder_buffer *buffer; - - if (get_user(data_ptr, (binder_uintptr_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(binder_uintptr_t); - - buffer = binder_alloc_prepare_to_free(&proc->alloc, - data_ptr); - if (IS_ERR_OR_NULL(buffer)) { - if (PTR_ERR(buffer) == -EPERM) { - binder_user_error( - "%d:%d BC_FREE_BUFFER matched unreturned or currently freeing buffer at offset %lx\n", - proc->pid, thread->pid, - (unsigned long)data_ptr - proc->alloc.vm_start); - } else { - binder_user_error( - "%d:%d BC_FREE_BUFFER no match for buffer at offset %lx\n", - proc->pid, thread->pid, - (unsigned long)data_ptr - proc->alloc.vm_start); - } - break; - } - binder_debug(BINDER_DEBUG_FREE_BUFFER, - "%d:%d BC_FREE_BUFFER at offset %lx found buffer %d for %s transaction\n", - proc->pid, thread->pid, - (unsigned long)data_ptr - proc->alloc.vm_start, - buffer->debug_id, - buffer->transaction ? "active" : "finished"); - binder_free_buf(proc, thread, buffer, false); - break; - } - - case BC_TRANSACTION_SG: - case BC_REPLY_SG: { - struct binder_transaction_data_sg tr; - - if (copy_from_user(&tr, ptr, sizeof(tr))) - return -EFAULT; - ptr += sizeof(tr); - binder_transaction(proc, thread, &tr.transaction_data, - cmd == BC_REPLY_SG, tr.buffers_size); - break; - } - case BC_TRANSACTION: - case BC_REPLY: { - struct binder_transaction_data tr; - - if (copy_from_user(&tr, ptr, sizeof(tr))) - return -EFAULT; - ptr += sizeof(tr); - binder_transaction(proc, thread, &tr, - cmd == BC_REPLY, 0); - break; - } - - case BC_REGISTER_LOOPER: - binder_debug(BINDER_DEBUG_THREADS, - "%d:%d BC_REGISTER_LOOPER\n", - proc->pid, thread->pid); - binder_inner_proc_lock(proc); - if (thread->looper & BINDER_LOOPER_STATE_ENTERED) { - thread->looper |= BINDER_LOOPER_STATE_INVALID; - binder_user_error("%d:%d ERROR: BC_REGISTER_LOOPER called after BC_ENTER_LOOPER\n", - proc->pid, thread->pid); - } else if (proc->requested_threads == 0) { - thread->looper |= BINDER_LOOPER_STATE_INVALID; - binder_user_error("%d:%d ERROR: BC_REGISTER_LOOPER called without request\n", - proc->pid, thread->pid); - } else { - proc->requested_threads--; - proc->requested_threads_started++; - } - thread->looper |= BINDER_LOOPER_STATE_REGISTERED; - binder_inner_proc_unlock(proc); - break; - case BC_ENTER_LOOPER: - binder_debug(BINDER_DEBUG_THREADS, - "%d:%d BC_ENTER_LOOPER\n", - proc->pid, thread->pid); - if (thread->looper & BINDER_LOOPER_STATE_REGISTERED) { - thread->looper |= BINDER_LOOPER_STATE_INVALID; - binder_user_error("%d:%d ERROR: BC_ENTER_LOOPER called after BC_REGISTER_LOOPER\n", - proc->pid, thread->pid); - } - thread->looper |= BINDER_LOOPER_STATE_ENTERED; - break; - case BC_EXIT_LOOPER: - binder_debug(BINDER_DEBUG_THREADS, - "%d:%d BC_EXIT_LOOPER\n", - proc->pid, thread->pid); - thread->looper |= BINDER_LOOPER_STATE_EXITED; - break; - - case BC_REQUEST_DEATH_NOTIFICATION: - case BC_CLEAR_DEATH_NOTIFICATION: { - uint32_t target; - binder_uintptr_t cookie; - struct binder_ref *ref; - struct binder_ref_death *death = NULL; - - if (get_user(target, (uint32_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(uint32_t); - if (get_user(cookie, (binder_uintptr_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(binder_uintptr_t); - if (cmd == BC_REQUEST_DEATH_NOTIFICATION) { - /* - * Allocate memory for death notification - * before taking lock - */ - death = kzalloc_obj(*death); - if (death == NULL) { - WARN_ON(thread->return_error.cmd != - BR_OK); - thread->return_error.cmd = BR_ERROR; - binder_enqueue_thread_work( - thread, - &thread->return_error.work); - binder_debug( - BINDER_DEBUG_FAILED_TRANSACTION, - "%d:%d BC_REQUEST_DEATH_NOTIFICATION failed\n", - proc->pid, thread->pid); - break; - } - } - binder_proc_lock(proc); - ref = binder_get_ref_olocked(proc, target, false); - if (ref == NULL) { - binder_user_error("%d:%d %s invalid ref %d\n", - proc->pid, thread->pid, - cmd == BC_REQUEST_DEATH_NOTIFICATION ? - "BC_REQUEST_DEATH_NOTIFICATION" : - "BC_CLEAR_DEATH_NOTIFICATION", - target); - binder_proc_unlock(proc); - kfree(death); - break; - } - - binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION, - "%d:%d %s %016llx ref %d desc %d s %d w %d for node %d\n", - proc->pid, thread->pid, - cmd == BC_REQUEST_DEATH_NOTIFICATION ? - "BC_REQUEST_DEATH_NOTIFICATION" : - "BC_CLEAR_DEATH_NOTIFICATION", - (u64)cookie, ref->data.debug_id, - ref->data.desc, ref->data.strong, - ref->data.weak, ref->node->debug_id); - - binder_node_lock(ref->node); - if (cmd == BC_REQUEST_DEATH_NOTIFICATION) { - if (ref->death) { - binder_user_error("%d:%d BC_REQUEST_DEATH_NOTIFICATION death notification already set\n", - proc->pid, thread->pid); - binder_node_unlock(ref->node); - binder_proc_unlock(proc); - kfree(death); - break; - } - binder_stats_created(BINDER_STAT_DEATH); - INIT_LIST_HEAD(&death->work.entry); - death->cookie = cookie; - ref->death = death; - if (ref->node->proc == NULL) { - ref->death->work.type = BINDER_WORK_DEAD_BINDER; - - binder_inner_proc_lock(proc); - binder_enqueue_work_ilocked( - &ref->death->work, &proc->todo); - binder_wakeup_proc_ilocked(proc); - binder_inner_proc_unlock(proc); - } - } else { - if (ref->death == NULL) { - binder_user_error("%d:%d BC_CLEAR_DEATH_NOTIFICATION death notification not active\n", - proc->pid, thread->pid); - binder_node_unlock(ref->node); - binder_proc_unlock(proc); - break; - } - death = ref->death; - if (death->cookie != cookie) { - binder_user_error("%d:%d BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch %016llx != %016llx\n", - proc->pid, thread->pid, - (u64)death->cookie, - (u64)cookie); - binder_node_unlock(ref->node); - binder_proc_unlock(proc); - break; - } - ref->death = NULL; - binder_inner_proc_lock(proc); - if (list_empty(&death->work.entry)) { - death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION; - if (thread->looper & - (BINDER_LOOPER_STATE_REGISTERED | - BINDER_LOOPER_STATE_ENTERED)) - binder_enqueue_thread_work_ilocked( - thread, - &death->work); - else { - binder_enqueue_work_ilocked( - &death->work, - &proc->todo); - binder_wakeup_proc_ilocked( - proc); - } - } else { - BUG_ON(death->work.type != BINDER_WORK_DEAD_BINDER); - death->work.type = BINDER_WORK_DEAD_BINDER_AND_CLEAR; - } - binder_inner_proc_unlock(proc); - } - binder_node_unlock(ref->node); - binder_proc_unlock(proc); - } break; - case BC_DEAD_BINDER_DONE: { - struct binder_work *w; - binder_uintptr_t cookie; - struct binder_ref_death *death = NULL; - - if (get_user(cookie, (binder_uintptr_t __user *)ptr)) - return -EFAULT; - - ptr += sizeof(cookie); - binder_inner_proc_lock(proc); - list_for_each_entry(w, &proc->delivered_death, - entry) { - struct binder_ref_death *tmp_death = - container_of(w, - struct binder_ref_death, - work); - - if (tmp_death->cookie == cookie) { - death = tmp_death; - break; - } - } - binder_debug(BINDER_DEBUG_DEAD_BINDER, - "%d:%d BC_DEAD_BINDER_DONE %016llx found %p\n", - proc->pid, thread->pid, (u64)cookie, - death); - if (death == NULL) { - binder_user_error("%d:%d BC_DEAD_BINDER_DONE %016llx not found\n", - proc->pid, thread->pid, (u64)cookie); - binder_inner_proc_unlock(proc); - break; - } - binder_dequeue_work_ilocked(&death->work); - if (death->work.type == BINDER_WORK_DEAD_BINDER_AND_CLEAR) { - death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION; - if (thread->looper & - (BINDER_LOOPER_STATE_REGISTERED | - BINDER_LOOPER_STATE_ENTERED)) - binder_enqueue_thread_work_ilocked( - thread, &death->work); - else { - binder_enqueue_work_ilocked( - &death->work, - &proc->todo); - binder_wakeup_proc_ilocked(proc); - } - } - binder_inner_proc_unlock(proc); - } break; - - case BC_REQUEST_FREEZE_NOTIFICATION: { - struct binder_handle_cookie handle_cookie; - int error; - - if (copy_from_user(&handle_cookie, ptr, sizeof(handle_cookie))) - return -EFAULT; - ptr += sizeof(handle_cookie); - error = binder_request_freeze_notification(proc, thread, - &handle_cookie); - if (error) - return error; - } break; - - case BC_CLEAR_FREEZE_NOTIFICATION: { - struct binder_handle_cookie handle_cookie; - int error; - - if (copy_from_user(&handle_cookie, ptr, sizeof(handle_cookie))) - return -EFAULT; - ptr += sizeof(handle_cookie); - error = binder_clear_freeze_notification(proc, thread, &handle_cookie); - if (error) - return error; - } break; - - case BC_FREEZE_NOTIFICATION_DONE: { - binder_uintptr_t cookie; - int error; - - if (get_user(cookie, (binder_uintptr_t __user *)ptr)) - return -EFAULT; - - ptr += sizeof(cookie); - error = binder_freeze_notification_done(proc, thread, cookie); - if (error) - return error; - } break; - - default: - pr_err("%d:%d unknown command %u\n", - proc->pid, thread->pid, cmd); - return -EINVAL; - } - *consumed = ptr - buffer; - } - return 0; -} - -static void binder_stat_br(struct binder_proc *proc, - struct binder_thread *thread, uint32_t cmd) -{ - trace_binder_return(cmd); - if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.br)) { - atomic_inc(&binder_stats.br[_IOC_NR(cmd)]); - atomic_inc(&proc->stats.br[_IOC_NR(cmd)]); - atomic_inc(&thread->stats.br[_IOC_NR(cmd)]); - } -} - -static int binder_put_node_cmd(struct binder_proc *proc, - struct binder_thread *thread, - void __user **ptrp, - binder_uintptr_t node_ptr, - binder_uintptr_t node_cookie, - int node_debug_id, - uint32_t cmd, const char *cmd_name) -{ - void __user *ptr = *ptrp; - - if (put_user(cmd, (uint32_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(uint32_t); - - if (put_user(node_ptr, (binder_uintptr_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(binder_uintptr_t); - - if (put_user(node_cookie, (binder_uintptr_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(binder_uintptr_t); - - binder_stat_br(proc, thread, cmd); - binder_debug(BINDER_DEBUG_USER_REFS, "%d:%d %s %d u%016llx c%016llx\n", - proc->pid, thread->pid, cmd_name, node_debug_id, - (u64)node_ptr, (u64)node_cookie); - - *ptrp = ptr; - return 0; -} - -static int binder_wait_for_work(struct binder_thread *thread, - bool do_proc_work) -{ - DEFINE_WAIT(wait); - struct binder_proc *proc = thread->proc; - int ret = 0; - - binder_inner_proc_lock(proc); - for (;;) { - prepare_to_wait(&thread->wait, &wait, TASK_INTERRUPTIBLE|TASK_FREEZABLE); - if (binder_has_work_ilocked(thread, do_proc_work)) - break; - if (do_proc_work) - list_add(&thread->waiting_thread_node, - &proc->waiting_threads); - binder_inner_proc_unlock(proc); - schedule(); - binder_inner_proc_lock(proc); - list_del_init(&thread->waiting_thread_node); - if (signal_pending(current)) { - ret = -EINTR; - break; - } - } - finish_wait(&thread->wait, &wait); - binder_inner_proc_unlock(proc); - - return ret; -} - -/** - * binder_apply_fd_fixups() - finish fd translation - * @proc: binder_proc associated @t->buffer - * @t: binder transaction with list of fd fixups - * - * Now that we are in the context of the transaction target - * process, we can allocate and install fds. Process the - * list of fds to translate and fixup the buffer with the - * new fds first and only then install the files. - * - * If we fail to allocate an fd, skip the install and release - * any fds that have already been allocated. - * - * Return: 0 on success, a negative errno code on failure. - */ -static int binder_apply_fd_fixups(struct binder_proc *proc, - struct binder_transaction *t) -{ - struct binder_txn_fd_fixup *fixup, *tmp; - int ret = 0; - - list_for_each_entry(fixup, &t->fd_fixups, fixup_entry) { - int fd = get_unused_fd_flags(O_CLOEXEC); - - if (fd < 0) { - binder_debug(BINDER_DEBUG_TRANSACTION, - "failed fd fixup txn %d fd %d\n", - t->debug_id, fd); - ret = -ENOMEM; - goto err; - } - binder_debug(BINDER_DEBUG_TRANSACTION, - "fd fixup txn %d fd %d\n", - t->debug_id, fd); - trace_binder_transaction_fd_recv(t, fd, fixup->offset); - fixup->target_fd = fd; - if (binder_alloc_copy_to_buffer(&proc->alloc, t->buffer, - fixup->offset, &fd, - sizeof(u32))) { - ret = -EINVAL; - goto err; - } - } - list_for_each_entry_safe(fixup, tmp, &t->fd_fixups, fixup_entry) { - fd_install(fixup->target_fd, fixup->file); - list_del(&fixup->fixup_entry); - kfree(fixup); - } - - return ret; - -err: - binder_free_txn_fixups(t); - return ret; -} - -static int binder_thread_read(struct binder_proc *proc, - struct binder_thread *thread, - binder_uintptr_t binder_buffer, size_t size, - binder_size_t *consumed, int non_block) -{ - void __user *buffer = (void __user *)(uintptr_t)binder_buffer; - void __user *ptr = buffer + *consumed; - void __user *end = buffer + size; - - int ret = 0; - int wait_for_proc_work; - - if (*consumed == 0) { - if (put_user(BR_NOOP, (uint32_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(uint32_t); - } - -retry: - binder_inner_proc_lock(proc); - wait_for_proc_work = binder_available_for_proc_work_ilocked(thread); - binder_inner_proc_unlock(proc); - - thread->looper |= BINDER_LOOPER_STATE_WAITING; - - trace_binder_wait_for_work(wait_for_proc_work, - !!thread->transaction_stack, - !binder_worklist_empty(proc, &thread->todo)); - if (wait_for_proc_work) { - if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED | - BINDER_LOOPER_STATE_ENTERED))) { - binder_user_error("%d:%d ERROR: Thread waiting for process work before calling BC_REGISTER_LOOPER or BC_ENTER_LOOPER (state %x)\n", - proc->pid, thread->pid, thread->looper); - wait_event_interruptible(binder_user_error_wait, - binder_stop_on_user_error < 2); - } - binder_set_nice(proc->default_priority); - } - - if (non_block) { - if (!binder_has_work(thread, wait_for_proc_work)) - ret = -EAGAIN; - } else { - ret = binder_wait_for_work(thread, wait_for_proc_work); - } - - thread->looper &= ~BINDER_LOOPER_STATE_WAITING; - - if (ret) - return ret; - - while (1) { - uint32_t cmd; - struct binder_transaction_data_secctx tr; - struct binder_transaction_data *trd = &tr.transaction_data; - struct binder_work *w = NULL; - struct list_head *list = NULL; - struct binder_transaction *t = NULL; - struct binder_thread *t_from; - size_t trsize = sizeof(*trd); - - binder_inner_proc_lock(proc); - if (!binder_worklist_empty_ilocked(&thread->todo)) - list = &thread->todo; - else if (!binder_worklist_empty_ilocked(&proc->todo) && - wait_for_proc_work) - list = &proc->todo; - else { - binder_inner_proc_unlock(proc); - - /* no data added */ - if (ptr - buffer == 4 && !thread->looper_need_return) - goto retry; - break; - } - - if (end - ptr < sizeof(tr) + 4) { - binder_inner_proc_unlock(proc); - break; - } - w = binder_dequeue_work_head_ilocked(list); - if (binder_worklist_empty_ilocked(&thread->todo)) - thread->process_todo = false; - - switch (w->type) { - case BINDER_WORK_TRANSACTION: { - binder_inner_proc_unlock(proc); - t = container_of(w, struct binder_transaction, work); - } break; - case BINDER_WORK_RETURN_ERROR: { - struct binder_error *e = container_of( - w, struct binder_error, work); - - WARN_ON(e->cmd == BR_OK); - binder_inner_proc_unlock(proc); - if (put_user(e->cmd, (uint32_t __user *)ptr)) - return -EFAULT; - cmd = e->cmd; - e->cmd = BR_OK; - ptr += sizeof(uint32_t); - - binder_stat_br(proc, thread, cmd); - } break; - case BINDER_WORK_TRANSACTION_COMPLETE: - case BINDER_WORK_TRANSACTION_PENDING: - case BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT: { - if (proc->oneway_spam_detection_enabled && - w->type == BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT) - cmd = BR_ONEWAY_SPAM_SUSPECT; - else if (w->type == BINDER_WORK_TRANSACTION_PENDING) - cmd = BR_TRANSACTION_PENDING_FROZEN; - else - cmd = BR_TRANSACTION_COMPLETE; - binder_inner_proc_unlock(proc); - kfree(w); - binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE); - if (put_user(cmd, (uint32_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(uint32_t); - - binder_stat_br(proc, thread, cmd); - binder_debug(BINDER_DEBUG_TRANSACTION_COMPLETE, - "%d:%d BR_TRANSACTION_COMPLETE\n", - proc->pid, thread->pid); - } break; - case BINDER_WORK_NODE: { - struct binder_node *node = container_of(w, struct binder_node, work); - int strong, weak; - binder_uintptr_t node_ptr = node->ptr; - binder_uintptr_t node_cookie = node->cookie; - int node_debug_id = node->debug_id; - int has_weak_ref; - int has_strong_ref; - void __user *orig_ptr = ptr; - - BUG_ON(proc != node->proc); - strong = node->internal_strong_refs || - node->local_strong_refs; - weak = !hlist_empty(&node->refs) || - node->local_weak_refs || - node->tmp_refs || strong; - has_strong_ref = node->has_strong_ref; - has_weak_ref = node->has_weak_ref; - - if (weak && !has_weak_ref) { - node->has_weak_ref = 1; - node->pending_weak_ref = 1; - node->local_weak_refs++; - } - if (strong && !has_strong_ref) { - node->has_strong_ref = 1; - node->pending_strong_ref = 1; - node->local_strong_refs++; - } - if (!strong && has_strong_ref) - node->has_strong_ref = 0; - if (!weak && has_weak_ref) - node->has_weak_ref = 0; - if (!weak && !strong) { - binder_debug(BINDER_DEBUG_INTERNAL_REFS, - "%d:%d node %d u%016llx c%016llx deleted\n", - proc->pid, thread->pid, - node_debug_id, - (u64)node_ptr, - (u64)node_cookie); - rb_erase(&node->rb_node, &proc->nodes); - binder_inner_proc_unlock(proc); - binder_node_lock(node); - /* - * Acquire the node lock before freeing the - * node to serialize with other threads that - * may have been holding the node lock while - * decrementing this node (avoids race where - * this thread frees while the other thread - * is unlocking the node after the final - * decrement) - */ - binder_node_unlock(node); - binder_free_node(node); - } else - binder_inner_proc_unlock(proc); - - if (weak && !has_weak_ref) - ret = binder_put_node_cmd( - proc, thread, &ptr, node_ptr, - node_cookie, node_debug_id, - BR_INCREFS, "BR_INCREFS"); - if (!ret && strong && !has_strong_ref) - ret = binder_put_node_cmd( - proc, thread, &ptr, node_ptr, - node_cookie, node_debug_id, - BR_ACQUIRE, "BR_ACQUIRE"); - if (!ret && !strong && has_strong_ref) - ret = binder_put_node_cmd( - proc, thread, &ptr, node_ptr, - node_cookie, node_debug_id, - BR_RELEASE, "BR_RELEASE"); - if (!ret && !weak && has_weak_ref) - ret = binder_put_node_cmd( - proc, thread, &ptr, node_ptr, - node_cookie, node_debug_id, - BR_DECREFS, "BR_DECREFS"); - if (orig_ptr == ptr) - binder_debug(BINDER_DEBUG_INTERNAL_REFS, - "%d:%d node %d u%016llx c%016llx state unchanged\n", - proc->pid, thread->pid, - node_debug_id, - (u64)node_ptr, - (u64)node_cookie); - if (ret) - return ret; - } break; - case BINDER_WORK_DEAD_BINDER: - case BINDER_WORK_DEAD_BINDER_AND_CLEAR: - case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: { - struct binder_ref_death *death; - uint32_t cmd; - binder_uintptr_t cookie; - - death = container_of(w, struct binder_ref_death, work); - if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION) - cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE; - else - cmd = BR_DEAD_BINDER; - cookie = death->cookie; - - binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION, - "%d:%d %s %016llx\n", - proc->pid, thread->pid, - cmd == BR_DEAD_BINDER ? - "BR_DEAD_BINDER" : - "BR_CLEAR_DEATH_NOTIFICATION_DONE", - (u64)cookie); - if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION) { - binder_inner_proc_unlock(proc); - kfree(death); - binder_stats_deleted(BINDER_STAT_DEATH); - } else { - binder_enqueue_work_ilocked( - w, &proc->delivered_death); - binder_inner_proc_unlock(proc); - } - if (put_user(cmd, (uint32_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(uint32_t); - if (put_user(cookie, - (binder_uintptr_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(binder_uintptr_t); - binder_stat_br(proc, thread, cmd); - if (cmd == BR_DEAD_BINDER) - goto done; /* DEAD_BINDER notifications can cause transactions */ - } break; - - case BINDER_WORK_FROZEN_BINDER: { - struct binder_ref_freeze *freeze; - struct binder_frozen_state_info info; - - memset(&info, 0, sizeof(info)); - freeze = container_of(w, struct binder_ref_freeze, work); - info.is_frozen = freeze->is_frozen; - info.cookie = freeze->cookie; - freeze->sent = true; - binder_enqueue_work_ilocked(w, &proc->delivered_freeze); - binder_inner_proc_unlock(proc); - - if (put_user(BR_FROZEN_BINDER, (uint32_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(uint32_t); - if (copy_to_user(ptr, &info, sizeof(info))) - return -EFAULT; - ptr += sizeof(info); - binder_stat_br(proc, thread, BR_FROZEN_BINDER); - goto done; /* BR_FROZEN_BINDER notifications can cause transactions */ - } break; - - case BINDER_WORK_CLEAR_FREEZE_NOTIFICATION: { - struct binder_ref_freeze *freeze = - container_of(w, struct binder_ref_freeze, work); - binder_uintptr_t cookie = freeze->cookie; - - binder_inner_proc_unlock(proc); - kfree(freeze); - binder_stats_deleted(BINDER_STAT_FREEZE); - if (put_user(BR_CLEAR_FREEZE_NOTIFICATION_DONE, (uint32_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(uint32_t); - if (put_user(cookie, (binder_uintptr_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(binder_uintptr_t); - binder_stat_br(proc, thread, BR_CLEAR_FREEZE_NOTIFICATION_DONE); - } break; - - default: - binder_inner_proc_unlock(proc); - pr_err("%d:%d: bad work type %d\n", - proc->pid, thread->pid, w->type); - break; - } - - if (!t) - continue; - - BUG_ON(t->buffer == NULL); - if (t->buffer->target_node) { - struct binder_node *target_node = t->buffer->target_node; - - trd->target.ptr = target_node->ptr; - trd->cookie = target_node->cookie; - t->saved_priority = task_nice(current); - if (t->priority < target_node->min_priority && - !(t->flags & TF_ONE_WAY)) - binder_set_nice(t->priority); - else if (!(t->flags & TF_ONE_WAY) || - t->saved_priority > target_node->min_priority) - binder_set_nice(target_node->min_priority); - cmd = BR_TRANSACTION; - } else { - trd->target.ptr = 0; - trd->cookie = 0; - cmd = BR_REPLY; - } - trd->code = t->code; - trd->flags = t->flags; - trd->sender_euid = from_kuid(current_user_ns(), t->sender_euid); - - t_from = binder_get_txn_from(t); - if (t_from) { - struct task_struct *sender = t_from->proc->tsk; - - trd->sender_pid = - task_tgid_nr_ns(sender, - task_active_pid_ns(current)); - } else { - trd->sender_pid = 0; - } - - ret = binder_apply_fd_fixups(proc, t); - if (ret) { - struct binder_buffer *buffer = t->buffer; - bool oneway = !!(t->flags & TF_ONE_WAY); - int tid = t->debug_id; - - if (t_from) - binder_thread_dec_tmpref(t_from); - buffer->transaction = NULL; - binder_cleanup_transaction(t, "fd fixups failed", - BR_FAILED_REPLY); - binder_free_buf(proc, thread, buffer, true); - binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, - "%d:%d %stransaction %d fd fixups failed %d/%d, line %d\n", - proc->pid, thread->pid, - oneway ? "async " : - (cmd == BR_REPLY ? "reply " : ""), - tid, BR_FAILED_REPLY, ret, __LINE__); - if (cmd == BR_REPLY) { - cmd = BR_FAILED_REPLY; - if (put_user(cmd, (uint32_t __user *)ptr)) - return -EFAULT; - ptr += sizeof(uint32_t); - binder_stat_br(proc, thread, cmd); - break; - } - continue; - } - trd->data_size = t->buffer->data_size; - trd->offsets_size = t->buffer->offsets_size; - trd->data.ptr.buffer = t->buffer->user_data; - trd->data.ptr.offsets = trd->data.ptr.buffer + - ALIGN(t->buffer->data_size, - sizeof(void *)); - - tr.secctx = t->security_ctx; - if (t->security_ctx) { - cmd = BR_TRANSACTION_SEC_CTX; - trsize = sizeof(tr); - } - if (put_user(cmd, (uint32_t __user *)ptr)) { - if (t_from) - binder_thread_dec_tmpref(t_from); - - binder_cleanup_transaction(t, "put_user failed", - BR_FAILED_REPLY); - - return -EFAULT; - } - ptr += sizeof(uint32_t); - if (copy_to_user(ptr, &tr, trsize)) { - if (t_from) - binder_thread_dec_tmpref(t_from); - - binder_cleanup_transaction(t, "copy_to_user failed", - BR_FAILED_REPLY); - - return -EFAULT; - } - ptr += trsize; - - trace_binder_transaction_received(t); - binder_stat_br(proc, thread, cmd); - binder_debug(BINDER_DEBUG_TRANSACTION, - "%d:%d %s %d %d:%d, cmd %u size %zd-%zd\n", - proc->pid, thread->pid, - (cmd == BR_TRANSACTION) ? "BR_TRANSACTION" : - (cmd == BR_TRANSACTION_SEC_CTX) ? - "BR_TRANSACTION_SEC_CTX" : "BR_REPLY", - t->debug_id, t_from ? t_from->proc->pid : 0, - t_from ? t_from->pid : 0, cmd, - t->buffer->data_size, t->buffer->offsets_size); - - if (t_from) - binder_thread_dec_tmpref(t_from); - t->buffer->allow_user_free = 1; - if (cmd != BR_REPLY && !(t->flags & TF_ONE_WAY)) { - binder_inner_proc_lock(thread->proc); - t->to_parent = thread->transaction_stack; - t->to_thread = thread; - thread->transaction_stack = t; - binder_inner_proc_unlock(thread->proc); - } else { - binder_free_transaction(t); - } - break; - } - -done: - - *consumed = ptr - buffer; - binder_inner_proc_lock(proc); - if (proc->requested_threads == 0 && - list_empty(&thread->proc->waiting_threads) && - proc->requested_threads_started < proc->max_threads && - (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | - BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */ - /*spawn a new thread if we leave this out */) { - proc->requested_threads++; - binder_inner_proc_unlock(proc); - binder_debug(BINDER_DEBUG_THREADS, - "%d:%d BR_SPAWN_LOOPER\n", - proc->pid, thread->pid); - if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer)) - return -EFAULT; - binder_stat_br(proc, thread, BR_SPAWN_LOOPER); - } else - binder_inner_proc_unlock(proc); - return 0; -} - -static void binder_release_work(struct binder_proc *proc, - struct list_head *list) -{ - struct binder_work *w; - enum binder_work_type wtype; - - while (1) { - binder_inner_proc_lock(proc); - w = binder_dequeue_work_head_ilocked(list); - wtype = w ? w->type : 0; - binder_inner_proc_unlock(proc); - if (!w) - return; - - switch (wtype) { - case BINDER_WORK_TRANSACTION: { - struct binder_transaction *t; - - t = container_of(w, struct binder_transaction, work); - - binder_cleanup_transaction(t, "process died.", - BR_DEAD_REPLY); - } break; - case BINDER_WORK_RETURN_ERROR: { - struct binder_error *e = container_of( - w, struct binder_error, work); - - binder_debug(BINDER_DEBUG_DEAD_TRANSACTION, - "undelivered TRANSACTION_ERROR: %u\n", - e->cmd); - } break; - case BINDER_WORK_TRANSACTION_PENDING: - case BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT: - case BINDER_WORK_TRANSACTION_COMPLETE: { - binder_debug(BINDER_DEBUG_DEAD_TRANSACTION, - "undelivered TRANSACTION_COMPLETE\n"); - kfree(w); - binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE); - } break; - case BINDER_WORK_DEAD_BINDER_AND_CLEAR: - case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: { - struct binder_ref_death *death; - - death = container_of(w, struct binder_ref_death, work); - binder_debug(BINDER_DEBUG_DEAD_TRANSACTION, - "undelivered death notification, %016llx\n", - (u64)death->cookie); - kfree(death); - binder_stats_deleted(BINDER_STAT_DEATH); - } break; - case BINDER_WORK_NODE: - break; - case BINDER_WORK_CLEAR_FREEZE_NOTIFICATION: { - struct binder_ref_freeze *freeze; - - freeze = container_of(w, struct binder_ref_freeze, work); - binder_debug(BINDER_DEBUG_DEAD_TRANSACTION, - "undelivered freeze notification, %016llx\n", - (u64)freeze->cookie); - kfree(freeze); - binder_stats_deleted(BINDER_STAT_FREEZE); - } break; - default: - pr_err("unexpected work type, %d, not freed\n", - wtype); - break; - } - } - -} - -static struct binder_thread *binder_get_thread_ilocked( - struct binder_proc *proc, struct binder_thread *new_thread) -{ - struct binder_thread *thread = NULL; - struct rb_node *parent = NULL; - struct rb_node **p = &proc->threads.rb_node; - - while (*p) { - parent = *p; - thread = rb_entry(parent, struct binder_thread, rb_node); - - if (current->pid < thread->pid) - p = &(*p)->rb_left; - else if (current->pid > thread->pid) - p = &(*p)->rb_right; - else - return thread; - } - if (!new_thread) - return NULL; - thread = new_thread; - binder_stats_created(BINDER_STAT_THREAD); - thread->proc = proc; - thread->pid = current->pid; - atomic_set(&thread->tmp_ref, 0); - init_waitqueue_head(&thread->wait); - INIT_LIST_HEAD(&thread->todo); - rb_link_node(&thread->rb_node, parent, p); - rb_insert_color(&thread->rb_node, &proc->threads); - thread->looper_need_return = true; - thread->return_error.work.type = BINDER_WORK_RETURN_ERROR; - thread->return_error.cmd = BR_OK; - thread->reply_error.work.type = BINDER_WORK_RETURN_ERROR; - thread->reply_error.cmd = BR_OK; - thread->ee.command = BR_OK; - INIT_LIST_HEAD(&new_thread->waiting_thread_node); - return thread; -} - -static struct binder_thread *binder_get_thread(struct binder_proc *proc) -{ - struct binder_thread *thread; - struct binder_thread *new_thread; - - binder_inner_proc_lock(proc); - thread = binder_get_thread_ilocked(proc, NULL); - binder_inner_proc_unlock(proc); - if (!thread) { - new_thread = kzalloc_obj(*thread); - if (new_thread == NULL) - return NULL; - binder_inner_proc_lock(proc); - thread = binder_get_thread_ilocked(proc, new_thread); - binder_inner_proc_unlock(proc); - if (thread != new_thread) - kfree(new_thread); - } - return thread; -} - -static void binder_free_proc(struct binder_proc *proc) -{ - struct binder_device *device; - - BUG_ON(!list_empty(&proc->todo)); - BUG_ON(!list_empty(&proc->delivered_death)); - if (proc->outstanding_txns) - pr_warn("%s: Unexpected outstanding_txns %d\n", - __func__, proc->outstanding_txns); - device = container_of(proc->context, struct binder_device, context); - if (refcount_dec_and_test(&device->ref)) { - binder_remove_device(device); - kfree(proc->context->name); - kfree(device); - } - binder_alloc_deferred_release(&proc->alloc); - put_task_struct(proc->tsk); - put_cred(proc->cred); - binder_stats_deleted(BINDER_STAT_PROC); - dbitmap_free(&proc->dmap); - kfree(proc); -} - -static void binder_free_thread(struct binder_thread *thread) -{ - BUG_ON(!list_empty(&thread->todo)); - binder_stats_deleted(BINDER_STAT_THREAD); - binder_proc_dec_tmpref(thread->proc); - kfree(thread); -} - -static int binder_thread_release(struct binder_proc *proc, - struct binder_thread *thread) -{ - struct binder_transaction *t; - struct binder_transaction *send_reply = NULL; - int active_transactions = 0; - struct binder_transaction *last_t = NULL; - - binder_inner_proc_lock(thread->proc); - /* - * take a ref on the proc so it survives - * after we remove this thread from proc->threads. - * The corresponding dec is when we actually - * free the thread in binder_free_thread() - */ - proc->tmp_ref++; - /* - * take a ref on this thread to ensure it - * survives while we are releasing it - */ - atomic_inc(&thread->tmp_ref); - rb_erase(&thread->rb_node, &proc->threads); - t = thread->transaction_stack; - if (t) { - spin_lock(&t->lock); - if (t->to_thread == thread) - send_reply = t; - } else { - __acquire(&t->lock); - } - thread->is_dead = true; - - while (t) { - last_t = t; - active_transactions++; - binder_debug(BINDER_DEBUG_DEAD_TRANSACTION, - "release %d:%d transaction %d %s, still active\n", - proc->pid, thread->pid, - t->debug_id, - (t->to_thread == thread) ? "in" : "out"); - - if (t->to_thread == thread) { - thread->proc->outstanding_txns--; - t->to_proc = NULL; - t->to_thread = NULL; - if (t->buffer) { - t->buffer->transaction = NULL; - t->buffer = NULL; - } - t = t->to_parent; - } else if (t->from == thread) { - t->from = NULL; - t = t->from_parent; - } else - BUG(); - spin_unlock(&last_t->lock); - if (t) - spin_lock(&t->lock); - else - __acquire(&t->lock); - } - /* annotation for sparse, lock not acquired in last iteration above */ - __release(&t->lock); - - /* - * If this thread used poll, make sure we remove the waitqueue from any - * poll data structures holding it. - */ - if (thread->looper & BINDER_LOOPER_STATE_POLL) - wake_up_pollfree(&thread->wait); - - binder_inner_proc_unlock(thread->proc); - - /* - * This is needed to avoid races between wake_up_pollfree() above and - * someone else removing the last entry from the queue for other reasons - * (e.g. ep_remove_wait_queue() being called due to an epoll file - * descriptor being closed). Such other users hold an RCU read lock, so - * we can be sure they're done after we call synchronize_rcu(). - */ - if (thread->looper & BINDER_LOOPER_STATE_POLL) - synchronize_rcu(); - - if (send_reply) - binder_send_failed_reply(send_reply, BR_DEAD_REPLY); - binder_release_work(proc, &thread->todo); - binder_thread_dec_tmpref(thread); - return active_transactions; -} - -static __poll_t binder_poll(struct file *filp, - struct poll_table_struct *wait) -{ - struct binder_proc *proc = filp->private_data; - struct binder_thread *thread = NULL; - bool wait_for_proc_work; - - thread = binder_get_thread(proc); - if (!thread) - return EPOLLERR; - - binder_inner_proc_lock(thread->proc); - thread->looper |= BINDER_LOOPER_STATE_POLL; - wait_for_proc_work = binder_available_for_proc_work_ilocked(thread); - - binder_inner_proc_unlock(thread->proc); - - poll_wait(filp, &thread->wait, wait); - - if (binder_has_work(thread, wait_for_proc_work)) - return EPOLLIN; - - return 0; -} - -static int binder_ioctl_write_read(struct file *filp, unsigned long arg, - struct binder_thread *thread) -{ - int ret = 0; - struct binder_proc *proc = filp->private_data; - void __user *ubuf = (void __user *)arg; - struct binder_write_read bwr; - - if (copy_from_user(&bwr, ubuf, sizeof(bwr))) - return -EFAULT; - - binder_debug(BINDER_DEBUG_READ_WRITE, - "%d:%d write %lld at %016llx, read %lld at %016llx\n", - proc->pid, thread->pid, - (u64)bwr.write_size, (u64)bwr.write_buffer, - (u64)bwr.read_size, (u64)bwr.read_buffer); - - if (bwr.write_size > 0) { - ret = binder_thread_write(proc, thread, - bwr.write_buffer, - bwr.write_size, - &bwr.write_consumed); - trace_binder_write_done(ret); - if (ret < 0) { - bwr.read_consumed = 0; - goto out; - } - } - if (bwr.read_size > 0) { - ret = binder_thread_read(proc, thread, bwr.read_buffer, - bwr.read_size, - &bwr.read_consumed, - filp->f_flags & O_NONBLOCK); - trace_binder_read_done(ret); - binder_inner_proc_lock(proc); - if (!binder_worklist_empty_ilocked(&proc->todo)) - binder_wakeup_proc_ilocked(proc); - binder_inner_proc_unlock(proc); - if (ret < 0) - goto out; - } - binder_debug(BINDER_DEBUG_READ_WRITE, - "%d:%d wrote %lld of %lld, read return %lld of %lld\n", - proc->pid, thread->pid, - (u64)bwr.write_consumed, (u64)bwr.write_size, - (u64)bwr.read_consumed, (u64)bwr.read_size); -out: - if (copy_to_user(ubuf, &bwr, sizeof(bwr))) - ret = -EFAULT; - return ret; -} - -static int binder_ioctl_set_ctx_mgr(struct file *filp, - struct flat_binder_object *fbo) -{ - int ret = 0; - struct binder_proc *proc = filp->private_data; - struct binder_context *context = proc->context; - struct binder_node *new_node; - kuid_t curr_euid = current_euid(); - - guard(mutex)(&context->context_mgr_node_lock); - if (context->binder_context_mgr_node) { - pr_err("BINDER_SET_CONTEXT_MGR already set\n"); - return -EBUSY; - } - ret = security_binder_set_context_mgr(proc->cred); - if (ret < 0) - return ret; - if (uid_valid(context->binder_context_mgr_uid)) { - if (!uid_eq(context->binder_context_mgr_uid, curr_euid)) { - pr_err("BINDER_SET_CONTEXT_MGR bad uid %d != %d\n", - from_kuid(&init_user_ns, curr_euid), - from_kuid(&init_user_ns, - context->binder_context_mgr_uid)); - return -EPERM; - } - } else { - context->binder_context_mgr_uid = curr_euid; - } - new_node = binder_new_node(proc, fbo); - if (!new_node) - return -ENOMEM; - binder_node_lock(new_node); - new_node->local_weak_refs++; - new_node->local_strong_refs++; - new_node->has_strong_ref = 1; - new_node->has_weak_ref = 1; - context->binder_context_mgr_node = new_node; - binder_node_unlock(new_node); - binder_put_node(new_node); - return ret; -} - -static int binder_ioctl_get_node_info_for_ref(struct binder_proc *proc, - struct binder_node_info_for_ref *info) -{ - struct binder_node *node; - struct binder_context *context = proc->context; - __u32 handle = info->handle; - - if (info->strong_count || info->weak_count || info->reserved1 || - info->reserved2 || info->reserved3) { - binder_user_error("%d BINDER_GET_NODE_INFO_FOR_REF: only handle may be non-zero.", - proc->pid); - return -EINVAL; - } - - /* This ioctl may only be used by the context manager */ - mutex_lock(&context->context_mgr_node_lock); - if (!context->binder_context_mgr_node || - context->binder_context_mgr_node->proc != proc) { - mutex_unlock(&context->context_mgr_node_lock); - return -EPERM; - } - mutex_unlock(&context->context_mgr_node_lock); - - node = binder_get_node_from_ref(proc, handle, true, NULL); - if (!node) - return -EINVAL; - - info->strong_count = node->local_strong_refs + - node->internal_strong_refs; - info->weak_count = node->local_weak_refs; - - binder_put_node(node); - - return 0; -} - -static int binder_ioctl_get_node_debug_info(struct binder_proc *proc, - struct binder_node_debug_info *info) -{ - struct rb_node *n; - binder_uintptr_t ptr = info->ptr; - - memset(info, 0, sizeof(*info)); - - binder_inner_proc_lock(proc); - for (n = rb_first(&proc->nodes); n != NULL; n = rb_next(n)) { - struct binder_node *node = rb_entry(n, struct binder_node, - rb_node); - if (node->ptr > ptr) { - info->ptr = node->ptr; - info->cookie = node->cookie; - info->has_strong_ref = node->has_strong_ref; - info->has_weak_ref = node->has_weak_ref; - break; - } - } - binder_inner_proc_unlock(proc); - - return 0; -} - -static bool binder_txns_pending_ilocked(struct binder_proc *proc) -{ - struct rb_node *n; - struct binder_thread *thread; - - if (proc->outstanding_txns > 0) - return true; - - for (n = rb_first(&proc->threads); n; n = rb_next(n)) { - thread = rb_entry(n, struct binder_thread, rb_node); - if (thread->transaction_stack) - return true; - } - return false; -} - -static void binder_add_freeze_work(struct binder_proc *proc, bool is_frozen) -{ - struct binder_node *prev = NULL; - struct rb_node *n; - struct binder_ref *ref; - - binder_inner_proc_lock(proc); - for (n = rb_first(&proc->nodes); n; n = rb_next(n)) { - struct binder_node *node; - - node = rb_entry(n, struct binder_node, rb_node); - binder_inc_node_tmpref_ilocked(node); - binder_inner_proc_unlock(proc); - if (prev) - binder_put_node(prev); - binder_node_lock(node); - hlist_for_each_entry(ref, &node->refs, node_entry) { - /* - * Need the node lock to synchronize - * with new notification requests and the - * inner lock to synchronize with queued - * freeze notifications. - */ - binder_inner_proc_lock(ref->proc); - if (!ref->freeze) { - binder_inner_proc_unlock(ref->proc); - continue; - } - ref->freeze->work.type = BINDER_WORK_FROZEN_BINDER; - if (list_empty(&ref->freeze->work.entry)) { - ref->freeze->is_frozen = is_frozen; - binder_enqueue_work_ilocked(&ref->freeze->work, &ref->proc->todo); - binder_wakeup_proc_ilocked(ref->proc); - } else { - if (ref->freeze->sent && ref->freeze->is_frozen != is_frozen) - ref->freeze->resend = true; - ref->freeze->is_frozen = is_frozen; - } - binder_inner_proc_unlock(ref->proc); - } - prev = node; - binder_node_unlock(node); - binder_inner_proc_lock(proc); - if (proc->is_dead) - break; - } - binder_inner_proc_unlock(proc); - if (prev) - binder_put_node(prev); -} - -static int binder_ioctl_freeze(struct binder_freeze_info *info, - struct binder_proc *target_proc) -{ - int ret = 0; - - if (!info->enable) { - binder_inner_proc_lock(target_proc); - target_proc->sync_recv = false; - target_proc->async_recv = false; - target_proc->is_frozen = false; - binder_inner_proc_unlock(target_proc); - binder_add_freeze_work(target_proc, false); - return 0; - } - - /* - * Freezing the target. Prevent new transactions by - * setting frozen state. If timeout specified, wait - * for transactions to drain. - */ - binder_inner_proc_lock(target_proc); - target_proc->sync_recv = false; - target_proc->async_recv = false; - target_proc->is_frozen = true; - binder_inner_proc_unlock(target_proc); - - if (info->timeout_ms > 0) - ret = wait_event_interruptible_timeout( - target_proc->freeze_wait, - (!target_proc->outstanding_txns), - msecs_to_jiffies(info->timeout_ms)); - - /* Check pending transactions that wait for reply */ - if (ret >= 0) { - binder_inner_proc_lock(target_proc); - if (binder_txns_pending_ilocked(target_proc)) - ret = -EAGAIN; - binder_inner_proc_unlock(target_proc); - } - - if (ret < 0) { - binder_inner_proc_lock(target_proc); - target_proc->is_frozen = false; - binder_inner_proc_unlock(target_proc); - } else { - binder_add_freeze_work(target_proc, true); - } - - return ret; -} - -static int binder_ioctl_get_freezer_info( - struct binder_frozen_status_info *info) -{ - struct binder_proc *target_proc; - bool found = false; - __u32 txns_pending; - - info->sync_recv = 0; - info->async_recv = 0; - - mutex_lock(&binder_procs_lock); - hlist_for_each_entry(target_proc, &binder_procs, proc_node) { - if (target_proc->pid == info->pid) { - found = true; - binder_inner_proc_lock(target_proc); - txns_pending = binder_txns_pending_ilocked(target_proc); - info->sync_recv |= target_proc->sync_recv | - (txns_pending << 1); - info->async_recv |= target_proc->async_recv; - binder_inner_proc_unlock(target_proc); - } - } - mutex_unlock(&binder_procs_lock); - - if (!found) - return -EINVAL; - - return 0; -} - -static int binder_ioctl_get_extended_error(struct binder_thread *thread, - void __user *ubuf) -{ - struct binder_extended_error ee; - - binder_inner_proc_lock(thread->proc); - ee = thread->ee; - binder_set_extended_error(&thread->ee, 0, BR_OK, 0); - binder_inner_proc_unlock(thread->proc); - - if (copy_to_user(ubuf, &ee, sizeof(ee))) - return -EFAULT; - - return 0; -} - -static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) -{ - int ret; - struct binder_proc *proc = filp->private_data; - struct binder_thread *thread; - void __user *ubuf = (void __user *)arg; - - trace_binder_ioctl(cmd, arg); - - ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2); - if (ret) - goto err_unlocked; - - thread = binder_get_thread(proc); - if (thread == NULL) { - ret = -ENOMEM; - goto err; - } - - switch (cmd) { - case BINDER_WRITE_READ: - ret = binder_ioctl_write_read(filp, arg, thread); - if (ret) - goto err; - break; - case BINDER_SET_MAX_THREADS: { - u32 max_threads; - - if (copy_from_user(&max_threads, ubuf, - sizeof(max_threads))) { - ret = -EINVAL; - goto err; - } - binder_inner_proc_lock(proc); - proc->max_threads = max_threads; - binder_inner_proc_unlock(proc); - break; - } - case BINDER_SET_CONTEXT_MGR_EXT: { - struct flat_binder_object fbo; - - if (copy_from_user(&fbo, ubuf, sizeof(fbo))) { - ret = -EINVAL; - goto err; - } - ret = binder_ioctl_set_ctx_mgr(filp, &fbo); - if (ret) - goto err; - break; - } - case BINDER_SET_CONTEXT_MGR: - ret = binder_ioctl_set_ctx_mgr(filp, NULL); - if (ret) - goto err; - break; - case BINDER_THREAD_EXIT: - binder_debug(BINDER_DEBUG_THREADS, "%d:%d exit\n", - proc->pid, thread->pid); - binder_thread_release(proc, thread); - thread = NULL; - break; - case BINDER_VERSION: { - struct binder_version __user *ver = ubuf; - - if (put_user(BINDER_CURRENT_PROTOCOL_VERSION, - &ver->protocol_version)) { - ret = -EINVAL; - goto err; - } - break; - } - case BINDER_GET_NODE_INFO_FOR_REF: { - struct binder_node_info_for_ref info; - - if (copy_from_user(&info, ubuf, sizeof(info))) { - ret = -EFAULT; - goto err; - } - - ret = binder_ioctl_get_node_info_for_ref(proc, &info); - if (ret < 0) - goto err; - - if (copy_to_user(ubuf, &info, sizeof(info))) { - ret = -EFAULT; - goto err; - } - - break; - } - case BINDER_GET_NODE_DEBUG_INFO: { - struct binder_node_debug_info info; - - if (copy_from_user(&info, ubuf, sizeof(info))) { - ret = -EFAULT; - goto err; - } - - ret = binder_ioctl_get_node_debug_info(proc, &info); - if (ret < 0) - goto err; - - if (copy_to_user(ubuf, &info, sizeof(info))) { - ret = -EFAULT; - goto err; - } - break; - } - case BINDER_FREEZE: { - struct binder_freeze_info info; - struct binder_proc **target_procs = NULL, *target_proc; - int target_procs_count = 0, i = 0; - - ret = 0; - - if (copy_from_user(&info, ubuf, sizeof(info))) { - ret = -EFAULT; - goto err; - } - - mutex_lock(&binder_procs_lock); - hlist_for_each_entry(target_proc, &binder_procs, proc_node) { - if (target_proc->pid == info.pid) - target_procs_count++; - } - - if (target_procs_count == 0) { - mutex_unlock(&binder_procs_lock); - ret = -EINVAL; - goto err; - } - - target_procs = kzalloc_objs(struct binder_proc *, - target_procs_count); - - if (!target_procs) { - mutex_unlock(&binder_procs_lock); - ret = -ENOMEM; - goto err; - } - - hlist_for_each_entry(target_proc, &binder_procs, proc_node) { - if (target_proc->pid != info.pid) - continue; - - binder_inner_proc_lock(target_proc); - target_proc->tmp_ref++; - binder_inner_proc_unlock(target_proc); - - target_procs[i++] = target_proc; - } - mutex_unlock(&binder_procs_lock); - - for (i = 0; i < target_procs_count; i++) { - if (ret >= 0) - ret = binder_ioctl_freeze(&info, - target_procs[i]); - - binder_proc_dec_tmpref(target_procs[i]); - } - - kfree(target_procs); - - if (ret < 0) - goto err; - break; - } - case BINDER_GET_FROZEN_INFO: { - struct binder_frozen_status_info info; - - if (copy_from_user(&info, ubuf, sizeof(info))) { - ret = -EFAULT; - goto err; - } - - ret = binder_ioctl_get_freezer_info(&info); - if (ret < 0) - goto err; - - if (copy_to_user(ubuf, &info, sizeof(info))) { - ret = -EFAULT; - goto err; - } - break; - } - case BINDER_ENABLE_ONEWAY_SPAM_DETECTION: { - uint32_t enable; - - if (copy_from_user(&enable, ubuf, sizeof(enable))) { - ret = -EFAULT; - goto err; - } - binder_inner_proc_lock(proc); - proc->oneway_spam_detection_enabled = (bool)enable; - binder_inner_proc_unlock(proc); - break; - } - case BINDER_GET_EXTENDED_ERROR: - ret = binder_ioctl_get_extended_error(thread, ubuf); - if (ret < 0) - goto err; - break; - default: - ret = -EINVAL; - goto err; - } - ret = 0; -err: - if (thread) - thread->looper_need_return = false; - wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2); - if (ret && ret != -EINTR) - pr_info("%d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret); -err_unlocked: - trace_binder_ioctl_done(ret); - return ret; -} - -static void binder_vma_open(struct vm_area_struct *vma) -{ - struct binder_proc *proc = vma->vm_private_data; - - binder_debug(BINDER_DEBUG_OPEN_CLOSE, - "%d open vm area %lx-%lx (%ld K) vma %lx pagep %lx\n", - proc->pid, vma->vm_start, vma->vm_end, - (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags, - (unsigned long)pgprot_val(vma->vm_page_prot)); -} - -static void binder_vma_close(struct vm_area_struct *vma) -{ - struct binder_proc *proc = vma->vm_private_data; - - binder_debug(BINDER_DEBUG_OPEN_CLOSE, - "%d close vm area %lx-%lx (%ld K) vma %lx pagep %lx\n", - proc->pid, vma->vm_start, vma->vm_end, - (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags, - (unsigned long)pgprot_val(vma->vm_page_prot)); - binder_alloc_vma_close(&proc->alloc); -} - -VISIBLE_IF_KUNIT vm_fault_t binder_vm_fault(struct vm_fault *vmf) -{ - return VM_FAULT_SIGBUS; -} -EXPORT_SYMBOL_IF_KUNIT(binder_vm_fault); - -static const struct vm_operations_struct binder_vm_ops = { - .open = binder_vma_open, - .close = binder_vma_close, - .fault = binder_vm_fault, -}; - -static int binder_mmap(struct file *filp, struct vm_area_struct *vma) -{ - struct binder_proc *proc = filp->private_data; - - if (!same_thread_group(proc->tsk, current)) - return -EINVAL; - - binder_debug(BINDER_DEBUG_OPEN_CLOSE, - "%s: %d %lx-%lx (%ld K) vma %lx pagep %lx\n", - __func__, proc->pid, vma->vm_start, vma->vm_end, - (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags, - (unsigned long)pgprot_val(vma->vm_page_prot)); - - if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) { - pr_err("%s: %d %lx-%lx %s failed %d\n", __func__, - proc->pid, vma->vm_start, vma->vm_end, "bad vm_flags", -EPERM); - return -EPERM; - } - vm_flags_mod(vma, VM_DONTCOPY | VM_MIXEDMAP, VM_MAYWRITE); - - vma->vm_ops = &binder_vm_ops; - vma->vm_private_data = proc; - - return binder_alloc_mmap_handler(&proc->alloc, vma); -} - -static int binder_open(struct inode *nodp, struct file *filp) -{ - struct binder_proc *proc, *itr; - struct binder_device *binder_dev; - struct binderfs_info *info; - struct dentry *binder_binderfs_dir_entry_proc = NULL; - bool existing_pid = false; - - binder_debug(BINDER_DEBUG_OPEN_CLOSE, "%s: %d:%d\n", __func__, - current->tgid, current->pid); - - proc = kzalloc_obj(*proc); - if (proc == NULL) - return -ENOMEM; - - dbitmap_init(&proc->dmap); - spin_lock_init(&proc->inner_lock); - spin_lock_init(&proc->outer_lock); - proc->tsk = get_task_struct(current->group_leader); - proc->pid = current->tgid; - proc->cred = get_cred(filp->f_cred); - INIT_LIST_HEAD(&proc->todo); - init_waitqueue_head(&proc->freeze_wait); - proc->default_priority = task_nice(current); - /* binderfs stashes devices in i_private */ - if (is_binderfs_device(nodp)) { - binder_dev = nodp->i_private; - info = nodp->i_sb->s_fs_info; - binder_binderfs_dir_entry_proc = info->proc_log_dir; - } else { - binder_dev = container_of(filp->private_data, - struct binder_device, miscdev); - } - refcount_inc(&binder_dev->ref); - proc->context = &binder_dev->context; - binder_alloc_init(&proc->alloc); - - binder_stats_created(BINDER_STAT_PROC); - INIT_LIST_HEAD(&proc->delivered_death); - INIT_LIST_HEAD(&proc->delivered_freeze); - INIT_LIST_HEAD(&proc->waiting_threads); - filp->private_data = proc; - - mutex_lock(&binder_procs_lock); - hlist_for_each_entry(itr, &binder_procs, proc_node) { - if (itr->pid == proc->pid) { - existing_pid = true; - break; - } - } - hlist_add_head(&proc->proc_node, &binder_procs); - mutex_unlock(&binder_procs_lock); - - if (binder_debugfs_dir_entry_proc && !existing_pid) { - char strbuf[11]; - - snprintf(strbuf, sizeof(strbuf), "%u", proc->pid); - /* - * proc debug entries are shared between contexts. - * Only create for the first PID to avoid debugfs log spamming - * The printing code will anyway print all contexts for a given - * PID so this is not a problem. - */ - proc->debugfs_entry = debugfs_create_file(strbuf, 0444, - binder_debugfs_dir_entry_proc, - (void *)(unsigned long)proc->pid, - &proc_fops); - } - - if (binder_binderfs_dir_entry_proc && !existing_pid) { - char strbuf[11]; - struct dentry *binderfs_entry; - - snprintf(strbuf, sizeof(strbuf), "%u", proc->pid); - /* - * Similar to debugfs, the process specific log file is shared - * between contexts. Only create for the first PID. - * This is ok since same as debugfs, the log file will contain - * information on all contexts of a given PID. - */ - binderfs_entry = binderfs_create_file(binder_binderfs_dir_entry_proc, - strbuf, &proc_fops, (void *)(unsigned long)proc->pid); - if (!IS_ERR(binderfs_entry)) { - proc->binderfs_entry = binderfs_entry; - } else { - int error; - - error = PTR_ERR(binderfs_entry); - pr_warn("Unable to create file %s in binderfs (error %d)\n", - strbuf, error); - } - } - - return 0; -} - -static int binder_flush(struct file *filp, fl_owner_t id) -{ - struct binder_proc *proc = filp->private_data; - - binder_defer_work(proc, BINDER_DEFERRED_FLUSH); - - return 0; -} - -static void binder_deferred_flush(struct binder_proc *proc) -{ - struct rb_node *n; - int wake_count = 0; - - binder_inner_proc_lock(proc); - for (n = rb_first(&proc->threads); n != NULL; n = rb_next(n)) { - struct binder_thread *thread = rb_entry(n, struct binder_thread, rb_node); - - thread->looper_need_return = true; - if (thread->looper & BINDER_LOOPER_STATE_WAITING) { - wake_up_interruptible(&thread->wait); - wake_count++; - } - } - binder_inner_proc_unlock(proc); - - binder_debug(BINDER_DEBUG_OPEN_CLOSE, - "binder_flush: %d woke %d threads\n", proc->pid, - wake_count); -} - -static int binder_release(struct inode *nodp, struct file *filp) -{ - struct binder_proc *proc = filp->private_data; - - debugfs_remove(proc->debugfs_entry); - - if (proc->binderfs_entry) { - simple_recursive_removal(proc->binderfs_entry, NULL); - proc->binderfs_entry = NULL; - } - - binder_defer_work(proc, BINDER_DEFERRED_RELEASE); - - return 0; -} - -static int binder_node_release(struct binder_node *node, int refs) -{ - struct binder_ref *ref; - int death = 0; - struct binder_proc *proc = node->proc; - - binder_release_work(proc, &node->async_todo); - - binder_node_lock(node); - binder_inner_proc_lock(proc); - binder_dequeue_work_ilocked(&node->work); - /* - * The caller must have taken a temporary ref on the node, - */ - BUG_ON(!node->tmp_refs); - if (hlist_empty(&node->refs) && node->tmp_refs == 1) { - binder_inner_proc_unlock(proc); - binder_node_unlock(node); - binder_free_node(node); - - return refs; - } - - node->proc = NULL; - node->local_strong_refs = 0; - node->local_weak_refs = 0; - binder_inner_proc_unlock(proc); - - spin_lock(&binder_dead_nodes_lock); - hlist_add_head(&node->dead_node, &binder_dead_nodes); - spin_unlock(&binder_dead_nodes_lock); - - hlist_for_each_entry(ref, &node->refs, node_entry) { - refs++; - /* - * Need the node lock to synchronize - * with new notification requests and the - * inner lock to synchronize with queued - * death notifications. - */ - binder_inner_proc_lock(ref->proc); - if (!ref->death) { - binder_inner_proc_unlock(ref->proc); - continue; - } - - death++; - - BUG_ON(!list_empty(&ref->death->work.entry)); - ref->death->work.type = BINDER_WORK_DEAD_BINDER; - binder_enqueue_work_ilocked(&ref->death->work, - &ref->proc->todo); - binder_wakeup_proc_ilocked(ref->proc); - binder_inner_proc_unlock(ref->proc); - } - - binder_debug(BINDER_DEBUG_DEAD_BINDER, - "node %d now dead, refs %d, death %d\n", - node->debug_id, refs, death); - binder_node_unlock(node); - binder_put_node(node); - - return refs; -} - -static void binder_deferred_release(struct binder_proc *proc) -{ - struct binder_context *context = proc->context; - struct rb_node *n; - int threads, nodes, incoming_refs, outgoing_refs, active_transactions; - - mutex_lock(&binder_procs_lock); - hlist_del(&proc->proc_node); - mutex_unlock(&binder_procs_lock); - - mutex_lock(&context->context_mgr_node_lock); - if (context->binder_context_mgr_node && - context->binder_context_mgr_node->proc == proc) { - binder_debug(BINDER_DEBUG_DEAD_BINDER, - "%s: %d context_mgr_node gone\n", - __func__, proc->pid); - context->binder_context_mgr_node = NULL; - } - mutex_unlock(&context->context_mgr_node_lock); - binder_inner_proc_lock(proc); - /* - * Make sure proc stays alive after we - * remove all the threads - */ - proc->tmp_ref++; - - proc->is_dead = true; - proc->is_frozen = false; - proc->sync_recv = false; - proc->async_recv = false; - threads = 0; - active_transactions = 0; - while ((n = rb_first(&proc->threads))) { - struct binder_thread *thread; - - thread = rb_entry(n, struct binder_thread, rb_node); - binder_inner_proc_unlock(proc); - threads++; - active_transactions += binder_thread_release(proc, thread); - binder_inner_proc_lock(proc); - } - - nodes = 0; - incoming_refs = 0; - while ((n = rb_first(&proc->nodes))) { - struct binder_node *node; - - node = rb_entry(n, struct binder_node, rb_node); - nodes++; - /* - * take a temporary ref on the node before - * calling binder_node_release() which will either - * kfree() the node or call binder_put_node() - */ - binder_inc_node_tmpref_ilocked(node); - rb_erase(&node->rb_node, &proc->nodes); - binder_inner_proc_unlock(proc); - incoming_refs = binder_node_release(node, incoming_refs); - binder_inner_proc_lock(proc); - } - binder_inner_proc_unlock(proc); - - outgoing_refs = 0; - binder_proc_lock(proc); - while ((n = rb_first(&proc->refs_by_desc))) { - struct binder_ref *ref; - - ref = rb_entry(n, struct binder_ref, rb_node_desc); - outgoing_refs++; - binder_cleanup_ref_olocked(ref); - binder_proc_unlock(proc); - binder_free_ref(ref); - binder_proc_lock(proc); - } - binder_proc_unlock(proc); - - binder_release_work(proc, &proc->todo); - binder_release_work(proc, &proc->delivered_death); - binder_release_work(proc, &proc->delivered_freeze); - - binder_debug(BINDER_DEBUG_OPEN_CLOSE, - "%s: %d threads %d, nodes %d (ref %d), refs %d, active transactions %d\n", - __func__, proc->pid, threads, nodes, incoming_refs, - outgoing_refs, active_transactions); - - binder_proc_dec_tmpref(proc); -} - -static void binder_deferred_func(struct work_struct *work) -{ - struct binder_proc *proc; - - int defer; - - do { - mutex_lock(&binder_deferred_lock); - if (!hlist_empty(&binder_deferred_list)) { - proc = hlist_entry(binder_deferred_list.first, - struct binder_proc, deferred_work_node); - hlist_del_init(&proc->deferred_work_node); - defer = proc->deferred_work; - proc->deferred_work = 0; - } else { - proc = NULL; - defer = 0; - } - mutex_unlock(&binder_deferred_lock); - - if (defer & BINDER_DEFERRED_FLUSH) - binder_deferred_flush(proc); - - if (defer & BINDER_DEFERRED_RELEASE) - binder_deferred_release(proc); /* frees proc */ - } while (proc); -} -static DECLARE_WORK(binder_deferred_work, binder_deferred_func); - -static void -binder_defer_work(struct binder_proc *proc, enum binder_deferred_state defer) -{ - guard(mutex)(&binder_deferred_lock); - proc->deferred_work |= defer; - if (hlist_unhashed(&proc->deferred_work_node)) { - hlist_add_head(&proc->deferred_work_node, - &binder_deferred_list); - schedule_work(&binder_deferred_work); - } -} - -static void print_binder_transaction_ilocked(struct seq_file *m, - struct binder_proc *proc, - const char *prefix, - struct binder_transaction *t) -{ - struct binder_proc *to_proc; - struct binder_buffer *buffer = t->buffer; - ktime_t current_time = ktime_get(); - - spin_lock(&t->lock); - to_proc = t->to_proc; - seq_printf(m, - "%s %d: %pK from %d:%d to %d:%d code %x flags %x pri %ld a%d r%d elapsed %lldms", - prefix, t->debug_id, t, - t->from_pid, - t->from_tid, - to_proc ? to_proc->pid : 0, - t->to_thread ? t->to_thread->pid : 0, - t->code, t->flags, t->priority, t->is_async, t->is_reply, - ktime_ms_delta(current_time, t->start_time)); - spin_unlock(&t->lock); - - if (proc != to_proc) { - /* - * Can only safely deref buffer if we are holding the - * correct proc inner lock for this node - */ - seq_puts(m, "\n"); - return; - } - - if (buffer == NULL) { - seq_puts(m, " buffer free\n"); - return; - } - if (buffer->target_node) - seq_printf(m, " node %d", buffer->target_node->debug_id); - seq_printf(m, " size %zd:%zd offset %lx\n", - buffer->data_size, buffer->offsets_size, - buffer->user_data - proc->alloc.vm_start); -} - -static void print_binder_work_ilocked(struct seq_file *m, - struct binder_proc *proc, - const char *prefix, - const char *transaction_prefix, - struct binder_work *w, bool hash_ptrs) -{ - struct binder_node *node; - struct binder_transaction *t; - - switch (w->type) { - case BINDER_WORK_TRANSACTION: - t = container_of(w, struct binder_transaction, work); - print_binder_transaction_ilocked( - m, proc, transaction_prefix, t); - break; - case BINDER_WORK_RETURN_ERROR: { - struct binder_error *e = container_of( - w, struct binder_error, work); - - seq_printf(m, "%stransaction error: %u\n", - prefix, e->cmd); - } break; - case BINDER_WORK_TRANSACTION_COMPLETE: - seq_printf(m, "%stransaction complete\n", prefix); - break; - case BINDER_WORK_NODE: - node = container_of(w, struct binder_node, work); - if (hash_ptrs) - seq_printf(m, "%snode work %d: u%p c%p\n", - prefix, node->debug_id, - (void *)(long)node->ptr, - (void *)(long)node->cookie); - else - seq_printf(m, "%snode work %d: u%016llx c%016llx\n", - prefix, node->debug_id, - (u64)node->ptr, (u64)node->cookie); - break; - case BINDER_WORK_DEAD_BINDER: - seq_printf(m, "%shas dead binder\n", prefix); - break; - case BINDER_WORK_DEAD_BINDER_AND_CLEAR: - seq_printf(m, "%shas cleared dead binder\n", prefix); - break; - case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: - seq_printf(m, "%shas cleared death notification\n", prefix); - break; - case BINDER_WORK_FROZEN_BINDER: - seq_printf(m, "%shas frozen binder\n", prefix); - break; - case BINDER_WORK_CLEAR_FREEZE_NOTIFICATION: - seq_printf(m, "%shas cleared freeze notification\n", prefix); - break; - default: - seq_printf(m, "%sunknown work: type %d\n", prefix, w->type); - break; - } -} - -static void print_binder_thread_ilocked(struct seq_file *m, - struct binder_thread *thread, - bool print_always, bool hash_ptrs) -{ - struct binder_transaction *t; - struct binder_work *w; - size_t start_pos = m->count; - size_t header_pos; - - seq_printf(m, " thread %d: l %02x need_return %d tr %d\n", - thread->pid, thread->looper, - thread->looper_need_return, - atomic_read(&thread->tmp_ref)); - header_pos = m->count; - t = thread->transaction_stack; - while (t) { - if (t->from == thread) { - print_binder_transaction_ilocked(m, thread->proc, - " outgoing transaction", t); - t = t->from_parent; - } else if (t->to_thread == thread) { - print_binder_transaction_ilocked(m, thread->proc, - " incoming transaction", t); - t = t->to_parent; - } else { - print_binder_transaction_ilocked(m, thread->proc, - " bad transaction", t); - t = NULL; - } - } - list_for_each_entry(w, &thread->todo, entry) { - print_binder_work_ilocked(m, thread->proc, " ", - " pending transaction", - w, hash_ptrs); - } - if (!print_always && m->count == header_pos) - m->count = start_pos; -} - -static void print_binder_node_nilocked(struct seq_file *m, - struct binder_node *node, - bool hash_ptrs) -{ - struct binder_ref *ref; - struct binder_work *w; - int count; - - count = hlist_count_nodes(&node->refs); - - if (hash_ptrs) - seq_printf(m, " node %d: u%p c%p", node->debug_id, - (void *)(long)node->ptr, (void *)(long)node->cookie); - else - seq_printf(m, " node %d: u%016llx c%016llx", node->debug_id, - (u64)node->ptr, (u64)node->cookie); - seq_printf(m, " hs %d hw %d ls %d lw %d is %d iw %d tr %d", - node->has_strong_ref, node->has_weak_ref, - node->local_strong_refs, node->local_weak_refs, - node->internal_strong_refs, count, node->tmp_refs); - if (count) { - seq_puts(m, " proc"); - hlist_for_each_entry(ref, &node->refs, node_entry) - seq_printf(m, " %d", ref->proc->pid); - } - seq_puts(m, "\n"); - if (node->proc) { - list_for_each_entry(w, &node->async_todo, entry) - print_binder_work_ilocked(m, node->proc, " ", - " pending async transaction", - w, hash_ptrs); - } -} - -static void print_binder_ref_olocked(struct seq_file *m, - struct binder_ref *ref) -{ - binder_node_lock(ref->node); - seq_printf(m, " ref %d: desc %d %snode %d s %d w %d d %pK\n", - ref->data.debug_id, ref->data.desc, - ref->node->proc ? "" : "dead ", - ref->node->debug_id, ref->data.strong, - ref->data.weak, ref->death); - binder_node_unlock(ref->node); -} - -/** - * print_next_binder_node_ilocked() - Print binder_node from a locked list - * @m: struct seq_file for output via seq_printf() - * @proc: struct binder_proc we hold the inner_proc_lock to (if any) - * @node: struct binder_node to print fields of - * @prev_node: struct binder_node we hold a temporary reference to (if any) - * @hash_ptrs: whether to hash @node's binder_uintptr_t fields - * - * Helper function to handle synchronization around printing a struct - * binder_node while iterating through @proc->nodes or the dead nodes list. - * Caller must hold either @proc->inner_lock (for live nodes) or - * binder_dead_nodes_lock. This lock will be released during the body of this - * function, but it will be reacquired before returning to the caller. - * - * Return: pointer to the struct binder_node we hold a tmpref on - */ -static struct binder_node * -print_next_binder_node_ilocked(struct seq_file *m, struct binder_proc *proc, - struct binder_node *node, - struct binder_node *prev_node, bool hash_ptrs) -{ - /* - * Take a temporary reference on the node so that isn't freed while - * we print it. - */ - binder_inc_node_tmpref_ilocked(node); - /* - * Live nodes need to drop the inner proc lock and dead nodes need to - * drop the binder_dead_nodes_lock before trying to take the node lock. - */ - if (proc) - binder_inner_proc_unlock(proc); - else - spin_unlock(&binder_dead_nodes_lock); - if (prev_node) - binder_put_node(prev_node); - binder_node_inner_lock(node); - print_binder_node_nilocked(m, node, hash_ptrs); - binder_node_inner_unlock(node); - if (proc) - binder_inner_proc_lock(proc); - else - spin_lock(&binder_dead_nodes_lock); - return node; -} - -static void print_binder_proc(struct seq_file *m, struct binder_proc *proc, - bool print_all, bool hash_ptrs) -{ - struct binder_work *w; - struct rb_node *n; - size_t start_pos = m->count; - size_t header_pos; - struct binder_node *last_node = NULL; - - seq_printf(m, "proc %d\n", proc->pid); - seq_printf(m, "context %s\n", proc->context->name); - header_pos = m->count; - - binder_inner_proc_lock(proc); - for (n = rb_first(&proc->threads); n; n = rb_next(n)) - print_binder_thread_ilocked(m, rb_entry(n, struct binder_thread, - rb_node), print_all, hash_ptrs); - - for (n = rb_first(&proc->nodes); n; n = rb_next(n)) { - struct binder_node *node = rb_entry(n, struct binder_node, - rb_node); - if (!print_all && !node->has_async_transaction) - continue; - - last_node = print_next_binder_node_ilocked(m, proc, node, - last_node, - hash_ptrs); - } - binder_inner_proc_unlock(proc); - if (last_node) - binder_put_node(last_node); - - if (print_all) { - binder_proc_lock(proc); - for (n = rb_first(&proc->refs_by_desc); n; n = rb_next(n)) - print_binder_ref_olocked(m, rb_entry(n, - struct binder_ref, - rb_node_desc)); - binder_proc_unlock(proc); - } - binder_alloc_print_allocated(m, &proc->alloc); - binder_inner_proc_lock(proc); - list_for_each_entry(w, &proc->todo, entry) - print_binder_work_ilocked(m, proc, " ", - " pending transaction", w, - hash_ptrs); - list_for_each_entry(w, &proc->delivered_death, entry) { - seq_puts(m, " has delivered dead binder\n"); - break; - } - list_for_each_entry(w, &proc->delivered_freeze, entry) { - seq_puts(m, " has delivered freeze binder\n"); - break; - } - binder_inner_proc_unlock(proc); - if (!print_all && m->count == header_pos) - m->count = start_pos; -} - -static const char * const binder_return_strings[] = { - "BR_ERROR", - "BR_OK", - "BR_TRANSACTION", - "BR_REPLY", - "BR_ACQUIRE_RESULT", - "BR_DEAD_REPLY", - "BR_TRANSACTION_COMPLETE", - "BR_INCREFS", - "BR_ACQUIRE", - "BR_RELEASE", - "BR_DECREFS", - "BR_ATTEMPT_ACQUIRE", - "BR_NOOP", - "BR_SPAWN_LOOPER", - "BR_FINISHED", - "BR_DEAD_BINDER", - "BR_CLEAR_DEATH_NOTIFICATION_DONE", - "BR_FAILED_REPLY", - "BR_FROZEN_REPLY", - "BR_ONEWAY_SPAM_SUSPECT", - "BR_TRANSACTION_PENDING_FROZEN", - "BR_FROZEN_BINDER", - "BR_CLEAR_FREEZE_NOTIFICATION_DONE", -}; - -static const char * const binder_command_strings[] = { - "BC_TRANSACTION", - "BC_REPLY", - "BC_ACQUIRE_RESULT", - "BC_FREE_BUFFER", - "BC_INCREFS", - "BC_ACQUIRE", - "BC_RELEASE", - "BC_DECREFS", - "BC_INCREFS_DONE", - "BC_ACQUIRE_DONE", - "BC_ATTEMPT_ACQUIRE", - "BC_REGISTER_LOOPER", - "BC_ENTER_LOOPER", - "BC_EXIT_LOOPER", - "BC_REQUEST_DEATH_NOTIFICATION", - "BC_CLEAR_DEATH_NOTIFICATION", - "BC_DEAD_BINDER_DONE", - "BC_TRANSACTION_SG", - "BC_REPLY_SG", - "BC_REQUEST_FREEZE_NOTIFICATION", - "BC_CLEAR_FREEZE_NOTIFICATION", - "BC_FREEZE_NOTIFICATION_DONE", -}; - -static const char * const binder_objstat_strings[] = { - "proc", - "thread", - "node", - "ref", - "death", - "transaction", - "transaction_complete", - "freeze", -}; - -static void print_binder_stats(struct seq_file *m, const char *prefix, - struct binder_stats *stats) -{ - int i; - - BUILD_BUG_ON(ARRAY_SIZE(stats->bc) != - ARRAY_SIZE(binder_command_strings)); - for (i = 0; i < ARRAY_SIZE(stats->bc); i++) { - int temp = atomic_read(&stats->bc[i]); - - if (temp) - seq_printf(m, "%s%s: %d\n", prefix, - binder_command_strings[i], temp); - } - - BUILD_BUG_ON(ARRAY_SIZE(stats->br) != - ARRAY_SIZE(binder_return_strings)); - for (i = 0; i < ARRAY_SIZE(stats->br); i++) { - int temp = atomic_read(&stats->br[i]); - - if (temp) - seq_printf(m, "%s%s: %d\n", prefix, - binder_return_strings[i], temp); - } - - BUILD_BUG_ON(ARRAY_SIZE(stats->obj_created) != - ARRAY_SIZE(binder_objstat_strings)); - BUILD_BUG_ON(ARRAY_SIZE(stats->obj_created) != - ARRAY_SIZE(stats->obj_deleted)); - for (i = 0; i < ARRAY_SIZE(stats->obj_created); i++) { - int created = atomic_read(&stats->obj_created[i]); - int deleted = atomic_read(&stats->obj_deleted[i]); - - if (created || deleted) - seq_printf(m, "%s%s: active %d total %d\n", - prefix, - binder_objstat_strings[i], - created - deleted, - created); - } -} - -static void print_binder_proc_stats(struct seq_file *m, - struct binder_proc *proc) -{ - struct binder_work *w; - struct binder_thread *thread; - struct rb_node *n; - int count, strong, weak, ready_threads; - size_t free_async_space = - binder_alloc_get_free_async_space(&proc->alloc); - - seq_printf(m, "proc %d\n", proc->pid); - seq_printf(m, "context %s\n", proc->context->name); - count = 0; - ready_threads = 0; - binder_inner_proc_lock(proc); - for (n = rb_first(&proc->threads); n; n = rb_next(n)) - count++; - - list_for_each_entry(thread, &proc->waiting_threads, waiting_thread_node) - ready_threads++; - - seq_printf(m, " threads: %d\n", count); - seq_printf(m, " requested threads: %d+%d/%d\n" - " ready threads %d\n" - " free async space %zd\n", proc->requested_threads, - proc->requested_threads_started, proc->max_threads, - ready_threads, - free_async_space); - count = 0; - for (n = rb_first(&proc->nodes); n; n = rb_next(n)) - count++; - binder_inner_proc_unlock(proc); - seq_printf(m, " nodes: %d\n", count); - count = 0; - strong = 0; - weak = 0; - binder_proc_lock(proc); - for (n = rb_first(&proc->refs_by_desc); n; n = rb_next(n)) { - struct binder_ref *ref = rb_entry(n, struct binder_ref, - rb_node_desc); - count++; - strong += ref->data.strong; - weak += ref->data.weak; - } - binder_proc_unlock(proc); - seq_printf(m, " refs: %d s %d w %d\n", count, strong, weak); - - count = binder_alloc_get_allocated_count(&proc->alloc); - seq_printf(m, " buffers: %d\n", count); - - binder_alloc_print_pages(m, &proc->alloc); - - count = 0; - binder_inner_proc_lock(proc); - list_for_each_entry(w, &proc->todo, entry) { - if (w->type == BINDER_WORK_TRANSACTION) - count++; - } - binder_inner_proc_unlock(proc); - seq_printf(m, " pending transactions: %d\n", count); - - print_binder_stats(m, " ", &proc->stats); -} - -static void print_binder_state(struct seq_file *m, bool hash_ptrs) -{ - struct binder_proc *proc; - struct binder_node *node; - struct binder_node *last_node = NULL; - - seq_puts(m, "binder state:\n"); - - spin_lock(&binder_dead_nodes_lock); - if (!hlist_empty(&binder_dead_nodes)) - seq_puts(m, "dead nodes:\n"); - hlist_for_each_entry(node, &binder_dead_nodes, dead_node) - last_node = print_next_binder_node_ilocked(m, NULL, node, - last_node, - hash_ptrs); - spin_unlock(&binder_dead_nodes_lock); - if (last_node) - binder_put_node(last_node); - - mutex_lock(&binder_procs_lock); - hlist_for_each_entry(proc, &binder_procs, proc_node) - print_binder_proc(m, proc, true, hash_ptrs); - mutex_unlock(&binder_procs_lock); -} - -static void print_binder_transactions(struct seq_file *m, bool hash_ptrs) -{ - struct binder_proc *proc; - - seq_puts(m, "binder transactions:\n"); - mutex_lock(&binder_procs_lock); - hlist_for_each_entry(proc, &binder_procs, proc_node) - print_binder_proc(m, proc, false, hash_ptrs); - mutex_unlock(&binder_procs_lock); -} - -static int state_show(struct seq_file *m, void *unused) -{ - print_binder_state(m, false); - return 0; -} - -static int state_hashed_show(struct seq_file *m, void *unused) -{ - print_binder_state(m, true); - return 0; -} - -static int stats_show(struct seq_file *m, void *unused) -{ - struct binder_proc *proc; - - seq_puts(m, "binder stats:\n"); - - print_binder_stats(m, "", &binder_stats); - - mutex_lock(&binder_procs_lock); - hlist_for_each_entry(proc, &binder_procs, proc_node) - print_binder_proc_stats(m, proc); - mutex_unlock(&binder_procs_lock); - - return 0; -} - -static int transactions_show(struct seq_file *m, void *unused) -{ - print_binder_transactions(m, false); - return 0; -} - -static int transactions_hashed_show(struct seq_file *m, void *unused) -{ - print_binder_transactions(m, true); - return 0; -} - -static int proc_show(struct seq_file *m, void *unused) -{ - struct binder_proc *itr; - int pid = (unsigned long)m->private; - - guard(mutex)(&binder_procs_lock); - hlist_for_each_entry(itr, &binder_procs, proc_node) { - if (itr->pid == pid) { - seq_puts(m, "binder proc state:\n"); - print_binder_proc(m, itr, true, false); - } - } - - return 0; -} - -static void print_binder_transaction_log_entry(struct seq_file *m, - struct binder_transaction_log_entry *e) -{ - int debug_id = READ_ONCE(e->debug_id_done); - /* - * read barrier to guarantee debug_id_done read before - * we print the log values - */ - smp_rmb(); - seq_printf(m, - "%d: %s from %d:%d to %d:%d context %s node %d handle %d size %d:%d ret %d/%d l=%d", - e->debug_id, (e->call_type == 2) ? "reply" : - ((e->call_type == 1) ? "async" : "call "), e->from_proc, - e->from_thread, e->to_proc, e->to_thread, e->context_name, - e->to_node, e->target_handle, e->data_size, e->offsets_size, - e->return_error, e->return_error_param, - e->return_error_line); - /* - * read-barrier to guarantee read of debug_id_done after - * done printing the fields of the entry - */ - smp_rmb(); - seq_printf(m, debug_id && debug_id == READ_ONCE(e->debug_id_done) ? - "\n" : " (incomplete)\n"); -} - -static int transaction_log_show(struct seq_file *m, void *unused) -{ - struct binder_transaction_log *log = m->private; - unsigned int log_cur = atomic_read(&log->cur); - unsigned int count; - unsigned int cur; - int i; - - count = log_cur + 1; - cur = count < ARRAY_SIZE(log->entry) && !log->full ? - 0 : count % ARRAY_SIZE(log->entry); - if (count > ARRAY_SIZE(log->entry) || log->full) - count = ARRAY_SIZE(log->entry); - for (i = 0; i < count; i++) { - unsigned int index = cur++ % ARRAY_SIZE(log->entry); - - print_binder_transaction_log_entry(m, &log->entry[index]); - } - return 0; -} - -const struct file_operations binder_fops = { - .owner = THIS_MODULE, - .poll = binder_poll, - .unlocked_ioctl = binder_ioctl, - .compat_ioctl = compat_ptr_ioctl, - .mmap = binder_mmap, - .open = binder_open, - .flush = binder_flush, - .release = binder_release, -}; - -DEFINE_SHOW_ATTRIBUTE(state); -DEFINE_SHOW_ATTRIBUTE(state_hashed); -DEFINE_SHOW_ATTRIBUTE(stats); -DEFINE_SHOW_ATTRIBUTE(transactions); -DEFINE_SHOW_ATTRIBUTE(transactions_hashed); -DEFINE_SHOW_ATTRIBUTE(transaction_log); - -const struct binder_debugfs_entry binder_debugfs_entries[] = { - { - .name = "state", - .mode = 0444, - .fops = &state_fops, - .data = NULL, - }, - { - .name = "state_hashed", - .mode = 0444, - .fops = &state_hashed_fops, - .data = NULL, - }, - { - .name = "stats", - .mode = 0444, - .fops = &stats_fops, - .data = NULL, - }, - { - .name = "transactions", - .mode = 0444, - .fops = &transactions_fops, - .data = NULL, - }, - { - .name = "transactions_hashed", - .mode = 0444, - .fops = &transactions_hashed_fops, - .data = NULL, - }, - { - .name = "transaction_log", - .mode = 0444, - .fops = &transaction_log_fops, - .data = &binder_transaction_log, - }, - { - .name = "failed_transaction_log", - .mode = 0444, - .fops = &transaction_log_fops, - .data = &binder_transaction_log_failed, - }, - {} /* terminator */ -}; - -void binder_add_device(struct binder_device *device) -{ - guard(spinlock)(&binder_devices_lock); - hlist_add_head(&device->hlist, &binder_devices); -} - -void binder_remove_device(struct binder_device *device) -{ - guard(spinlock)(&binder_devices_lock); - hlist_del_init(&device->hlist); -} - -static int __init init_binder_device(const char *name) -{ - int ret; - struct binder_device *binder_device; - - binder_device = kzalloc_obj(*binder_device); - if (!binder_device) - return -ENOMEM; - - binder_device->miscdev.fops = &binder_fops; - binder_device->miscdev.minor = MISC_DYNAMIC_MINOR; - binder_device->miscdev.name = name; - - refcount_set(&binder_device->ref, 1); - binder_device->context.binder_context_mgr_uid = INVALID_UID; - binder_device->context.name = name; - mutex_init(&binder_device->context.context_mgr_node_lock); - - ret = misc_register(&binder_device->miscdev); - if (ret < 0) { - kfree(binder_device); - return ret; - } - - binder_add_device(binder_device); - - return ret; -} - -static int __init binder_init(void) -{ - int ret; - char *device_name, *device_tmp; - struct binder_device *device; - struct hlist_node *tmp; - char *device_names = NULL; - const struct binder_debugfs_entry *db_entry; - - ret = binder_alloc_shrinker_init(); - if (ret) - return ret; - - atomic_set(&binder_transaction_log.cur, ~0U); - atomic_set(&binder_transaction_log_failed.cur, ~0U); - - binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL); - - binder_for_each_debugfs_entry(db_entry) - debugfs_create_file(db_entry->name, - db_entry->mode, - binder_debugfs_dir_entry_root, - db_entry->data, - db_entry->fops); - - binder_debugfs_dir_entry_proc = debugfs_create_dir("proc", - binder_debugfs_dir_entry_root); - - if (!IS_ENABLED(CONFIG_ANDROID_BINDERFS) && - strcmp(binder_devices_param, "") != 0) { - /* - * Copy the module_parameter string, because we don't want to - * tokenize it in-place. - */ - device_names = kstrdup(binder_devices_param, GFP_KERNEL); - if (!device_names) { - ret = -ENOMEM; - goto err_alloc_device_names_failed; - } - - device_tmp = device_names; - while ((device_name = strsep(&device_tmp, ","))) { - ret = init_binder_device(device_name); - if (ret) - goto err_init_binder_device_failed; - } - } - - ret = genl_register_family(&binder_nl_family); - if (ret) - goto err_init_binder_device_failed; - - ret = init_binderfs(); - if (ret) - goto err_init_binderfs_failed; - - return ret; - -err_init_binderfs_failed: - genl_unregister_family(&binder_nl_family); - -err_init_binder_device_failed: - hlist_for_each_entry_safe(device, tmp, &binder_devices, hlist) { - misc_deregister(&device->miscdev); - binder_remove_device(device); - kfree(device); - } - - kfree(device_names); - -err_alloc_device_names_failed: - debugfs_remove_recursive(binder_debugfs_dir_entry_root); - binder_alloc_shrinker_exit(); - - return ret; -} - -device_initcall(binder_init); - -#define CREATE_TRACE_POINTS -#include "binder_trace.h" diff --git a/drivers/android/binder/Makefile b/drivers/android/binder/Makefile index 7e0cd9782a8b..fe474628a94e 100644 --- a/drivers/android/binder/Makefile +++ b/drivers/android/binder/Makefile @@ -1,8 +1,8 @@ # SPDX-License-Identifier: GPL-2.0-only ccflags-y += -I$(src) # needed for trace events -obj-$(CONFIG_ANDROID_BINDER_IPC_RUST) += rust_binder.o -rust_binder-y := \ +obj-$(CONFIG_ANDROID_BINDER_IPC_RUST) += binder.o +binder-y := \ rust_binder_main.o \ rust_binderfs.o \ rust_binder_events.o diff --git a/drivers/android/binder/allocation.rs b/drivers/android/binder/allocation.rs index 8151ba3ea7f4..927bf47d5d20 100644 --- a/drivers/android/binder/allocation.rs +++ b/drivers/android/binder/allocation.rs @@ -20,6 +20,7 @@ use crate::{ defs::*, node::{Node, NodeRef}, process::Process, + trace::trace_transaction_failed_buffer_release, DArc, }; @@ -208,7 +209,7 @@ impl Allocation { let num_close_on_free = files.iter().filter(|entry| entry.close_on_free).count(); let mut close_on_free = KVec::with_capacity(num_close_on_free, GFP_KERNEL)?; - let mut reservations = KVec::with_capacity(files.len(), GFP_KERNEL)?; + let mut reservations = KVVec::with_capacity(files.len(), GFP_KERNEL)?; for file_info in files { let res = FileDescriptorReservation::get_unused_fd_flags(bindings::O_CLOEXEC)?; let fd = res.reserved_fd(); @@ -325,6 +326,12 @@ impl core::ops::DerefMut for NewAllocation { } } +impl Drop for NewAllocation { + fn drop(&mut self) { + trace_transaction_failed_buffer_release(self.debug_id); + } +} + /// A view into the beginning of an allocation. /// /// All attempts to read or write outside of the view will fail. To intentionally access outside of @@ -503,7 +510,7 @@ impl BinderObject { /// The closure should write the bytes for the object into the provided slice. pub(crate) fn read_from_inner<R>(reader: R) -> Result<BinderObject> where - R: FnOnce(&mut [u8; size_of::<BinderObject>()]) -> Result<()>, + R: FnOnce(&mut [u8; size_of::<BinderObject>()]) -> Result, { let mut obj = MaybeUninit::<BinderObject>::zeroed(); @@ -567,7 +574,7 @@ impl BinderObject { #[derive(Default)] struct FileList { - files_to_translate: KVec<FileEntry>, + files_to_translate: KVVec<FileEntry>, close_on_free: KVec<u32>, } @@ -581,7 +588,7 @@ struct FileEntry { } pub(crate) struct TranslatedFds { - reservations: KVec<Reservation>, + reservations: KVVec<Reservation>, /// If commit is called, then these fds should be closed. (If commit is not called, then they /// shouldn't be closed.) close_on_free: FdsCloseOnFree, @@ -595,7 +602,7 @@ struct Reservation { impl TranslatedFds { pub(crate) fn new() -> Self { Self { - reservations: KVec::new(), + reservations: KVVec::new(), close_on_free: FdsCloseOnFree(KVec::new()), } } diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs index 431d6007a9b1..927b65ada836 100644 --- a/drivers/android/binder/context.rs +++ b/drivers/android/binder/context.rs @@ -134,13 +134,13 @@ impl Context { } pub(crate) fn get_manager_node(&self, strong: bool) -> Result<NodeRef, BinderError> { - self.manager + Ok(self + .manager .lock() .node .as_ref() - .ok_or_else(BinderError::new_dead)? - .clone(strong) - .map_err(BinderError::from) + .ok_or_else(|| BinderError::new_dead())? + .clone(strong)?) } pub(crate) fn for_each_proc<F>(&self, mut func: F) diff --git a/drivers/android/binder/defs.rs b/drivers/android/binder/defs.rs index 8ac9bdd7a499..cc4becd6e168 100644 --- a/drivers/android/binder/defs.rs +++ b/drivers/android/binder/defs.rs @@ -77,7 +77,8 @@ pub_no_prefix!( TF_ONE_WAY, TF_ACCEPT_FDS, TF_CLEAR_BUF, - TF_UPDATE_TXN + TF_UPDATE_TXN, + TF_DEFER_COMPLETE, ); pub(crate) use uapi::{ diff --git a/drivers/android/binder/error.rs b/drivers/android/binder/error.rs index 1296072c35d9..89077c5baeaf 100644 --- a/drivers/android/binder/error.rs +++ b/drivers/android/binder/error.rs @@ -2,6 +2,7 @@ // Copyright (C) 2025 Google LLC. +use core::panic::Location; use kernel::fmt; use kernel::prelude::*; @@ -9,62 +10,96 @@ use crate::defs::*; pub(crate) type BinderResult<T = ()> = core::result::Result<T, BinderError>; +/// Wraps a `&'static Location` to format it as `filename.rs:line` without the directory prefix. +#[derive(Copy, Clone)] +#[repr(transparent)] +pub(crate) struct ErrorLocation(&'static Location<'static>); + +// SAFETY: `ErrorLocation` is `repr(transparent)` over a shared reference, which is part of the +// option layout optimization guarantee, so all zeroes is a valid representation for `None`. +unsafe impl pin_init::ZeroableOption for ErrorLocation {} + +impl ErrorLocation { + #[track_caller] + pub(crate) const fn caller() -> Self { + Self(Location::caller()) + } +} + +impl fmt::Display for ErrorLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let file = match self.0.file().rsplit_once('/') { + Some((_, file)) => file, + None => self.0.file(), + }; + write!(f, "{}:{}", file, self.0.line()) + } +} + /// An error that will be returned to userspace via the `BINDER_WRITE_READ` ioctl rather than via /// errno. pub(crate) struct BinderError { pub(crate) reply: u32, pub(crate) source: Option<Error>, + pub(crate) line: ErrorLocation, } impl BinderError { + #[track_caller] pub(crate) fn new_dead() -> Self { Self { reply: BR_DEAD_REPLY, source: None, + line: ErrorLocation::caller(), } } + #[track_caller] pub(crate) fn new_frozen() -> Self { Self { reply: BR_FROZEN_REPLY, source: None, + line: ErrorLocation::caller(), } } + #[track_caller] pub(crate) fn new_frozen_oneway() -> Self { Self { reply: BR_TRANSACTION_PENDING_FROZEN, source: None, + line: ErrorLocation::caller(), } } - - pub(crate) fn is_dead(&self) -> bool { - self.reply == BR_DEAD_REPLY - } } /// Convert an errno into a `BinderError` and store the errno used to construct it. The errno /// should be stored as the thread's extended error when given to userspace. impl From<Error> for BinderError { + #[track_caller] fn from(source: Error) -> Self { Self { reply: BR_FAILED_REPLY, source: Some(source), + line: ErrorLocation::caller(), } } } impl From<kernel::fs::file::BadFdError> for BinderError { + #[track_caller] fn from(source: kernel::fs::file::BadFdError) -> Self { BinderError::from(Error::from(source)) } } impl From<kernel::alloc::AllocError> for BinderError { + #[track_caller] fn from(_: kernel::alloc::AllocError) -> Self { Self { reply: BR_FAILED_REPLY, source: Some(ENOMEM), + line: ErrorLocation::caller(), } } } diff --git a/drivers/android/binder/freeze.rs b/drivers/android/binder/freeze.rs index 7fbde3345ced..69e058e6d818 100644 --- a/drivers/android/binder/freeze.rs +++ b/drivers/android/binder/freeze.rs @@ -75,10 +75,8 @@ impl FreezeMessage { } fn init(ua: UninitFM, cookie: FreezeCookie, pid: i32) -> DLArc<FreezeMessage> { - match ua.pin_init_with(DTRWrap::new(FreezeMessage { cookie, pid })) { - Ok(msg) => ListArc::from(msg), - Err(err) => match err {}, - } + let Ok(msg) = ua.pin_init_with(DTRWrap::new(FreezeMessage { cookie, pid })); + ListArc::from(msg) } } @@ -155,7 +153,7 @@ impl DeliverToRead for FreezeMessage { } #[inline(never)] - fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result { seq_print!(m, "{}has frozen binder\n", prefix); Ok(()) } @@ -177,10 +175,7 @@ impl FreezeListener { } impl Process { - pub(crate) fn request_freeze_notif( - self: &Arc<Self>, - reader: &mut UserSliceReader, - ) -> Result<()> { + pub(crate) fn request_freeze_notif(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result { let hc = reader.read::<BinderHandleCookie>()?; let handle = hc.handle; let cookie = FreezeCookie(hc.cookie); @@ -272,7 +267,7 @@ impl Process { Ok(()) } - pub(crate) fn freeze_notif_done(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result<()> { + pub(crate) fn freeze_notif_done(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result { let cookie = FreezeCookie(reader.read()?); let alloc = FreezeMessage::new(GFP_KERNEL)?; let mut node_refs_guard = self.node_refs.lock(); @@ -313,7 +308,7 @@ impl Process { Ok(()) } - pub(crate) fn clear_freeze_notif(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result<()> { + pub(crate) fn clear_freeze_notif(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result { let hc = reader.read::<BinderHandleCookie>()?; let handle = hc.handle; let cookie = FreezeCookie(hc.cookie); diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index 0c625a25cb73..efdafc202e27 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -255,11 +255,7 @@ impl Node { } #[inline(never)] - pub(crate) fn full_debug_print( - &self, - m: &SeqFile, - owner_inner: &mut ProcessInner, - ) -> Result<()> { + pub(crate) fn full_debug_print(&self, m: &SeqFile, owner_inner: &mut ProcessInner) -> Result { let inner = self.inner.access_mut(owner_inner); seq_print!( m, @@ -759,7 +755,7 @@ impl DeliverToRead for Node { } #[inline(never)] - fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result { seq_print!( m, "{}node work {}: u{:016x} c{:016x}\n", @@ -1154,7 +1150,7 @@ impl DeliverToRead for NodeDeath { } #[inline(never)] - fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result { let inner = self.inner.lock(); let dead_binder = inner.dead && !inner.notification_done; diff --git a/drivers/android/binder/node/wrapper.rs b/drivers/android/binder/node/wrapper.rs index 886626ca0d42..edfbccf6e6da 100644 --- a/drivers/android/binder/node/wrapper.rs +++ b/drivers/android/binder/node/wrapper.rs @@ -20,10 +20,8 @@ impl CritIncrWrapper { } pub(super) fn init(self, node: DArc<Node>) -> DLArc<dyn DeliverToRead> { - match self.inner.pin_init_with(DTRWrap::new(NodeWrapper { node })) { - Ok(initialized) => ListArc::from(initialized) as DLArc<dyn DeliverToRead>, - Err(err) => match err {}, - } + let Ok(initialized) = self.inner.pin_init_with(DTRWrap::new(NodeWrapper { node })); + ListArc::from(initialized) as DLArc<dyn DeliverToRead> } } @@ -96,7 +94,7 @@ impl DeliverToRead for NodeWrapper { } #[inline(never)] - fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result { seq_print!( m, "{}node work {}: u{:016x} c{:016x}\n", diff --git a/drivers/android/binder/page_range.rs b/drivers/android/binder/page_range.rs index e2d46b4d1a29..b0c797523bc2 100644 --- a/drivers/android/binder/page_range.rs +++ b/drivers/android/binder/page_range.rs @@ -66,7 +66,7 @@ impl Shrinker { } /// Register this shrinker with the kernel. - pub(crate) fn register(&'static self, name: &CStr) -> Result<()> { + pub(crate) fn register(&'static self, name: &CStr) -> Result { // SAFETY: These fields are not yet used, so it's okay to zero them. unsafe { self.inner.get().write(ptr::null_mut()); @@ -352,7 +352,7 @@ impl ShrinkablePageRange { /// Make sure that the given pages are allocated and mapped. /// /// Must not be called from an atomic context. - pub(crate) fn use_range(&self, start: usize, end: usize) -> Result<()> { + pub(crate) fn use_range(&self, start: usize, end: usize) -> Result { if start >= end { return Ok(()); } @@ -398,7 +398,7 @@ impl ShrinkablePageRange { /// /// Assumes that `i` is in bounds. #[cold] - unsafe fn use_page_slow(&self, i: usize) -> Result<()> { + unsafe fn use_page_slow(&self, i: usize) -> Result { let new_page = Page::alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO)?; let mm_mutex = self.mm_lock.lock(); diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 65f719e46c3e..a2216c8f9648 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -548,7 +548,7 @@ impl Process { } #[inline(never)] - pub(crate) fn debug_print_stats(&self, m: &SeqFile, ctx: &Context) -> Result<()> { + pub(crate) fn debug_print_stats(&self, m: &SeqFile, ctx: &Context) -> Result { seq_print!(m, "proc {}\n", self.pid_in_current_ns()); seq_print!(m, "context {}\n", &*ctx.name); @@ -596,7 +596,7 @@ impl Process { } #[inline(never)] - pub(crate) fn debug_print(&self, m: &SeqFile, ctx: &Context, print_all: bool) -> Result<()> { + pub(crate) fn debug_print(&self, m: &SeqFile, ctx: &Context, print_all: bool) -> Result { seq_print!(m, "proc {}\n", self.pid_in_current_ns()); seq_print!(m, "context {}\n", &*ctx.name); @@ -686,8 +686,16 @@ impl Process { pub(crate) fn get_work_or_register<'a>( &'a self, thread: &'a Arc<Thread>, + thread_has_deferred_work: bool, ) -> GetWorkOrRegister<'a> { let mut inner = self.inner.lock(); + + if thread_has_deferred_work && !inner.work.is_empty() { + if let Some(work) = thread.pop_work_even_if_deferred() { + return GetWorkOrRegister::Work(work); + } + } + // Try to get work from the process queue. if let Some(work) = inner.work.pop_front() { return GetWorkOrRegister::Work(work); @@ -894,11 +902,8 @@ impl Process { let (info_proc, info_node) = { let info_init = NodeRefInfo::new(node_ref, handle, self.into()); - match info.pin_init_with(info_init) { - Ok(info) => ListArc::pair_from_pin_unique(info), - // error is infallible - Err(err) => match err {}, - } + let Ok(info) = info.pin_init_with(info_init); + ListArc::pair_from_pin_unique(info) }; // Ensure the process is still alive while we insert a new reference. @@ -1068,7 +1073,10 @@ impl Process { let (new_alloc, addr) = loop { let mut inner = self.inner.lock(); - let mapping = inner.mapping.as_mut().ok_or_else(BinderError::new_dead)?; + let mapping = inner + .mapping + .as_mut() + .ok_or_else(|| BinderError::new_dead())?; let alloc_request = match mapping.alloc.reserve_new(reserve_new_args)? { ReserveNew::Success(new_alloc) => break (new_alloc, mapping.address), ReserveNew::NeedAlloc(request) => request, @@ -1208,13 +1216,12 @@ impl Process { let ptr = reader.read::<BinderNodeDebugInfo>()?.ptr; let mut out = BinderNodeDebugInfo::default(); - { + if let Some(start_ptr) = ptr.checked_add(1) { let inner = self.inner.lock(); - for (node_ptr, node) in &inner.nodes { - if *node_ptr > ptr { - node.populate_debug_info(&mut out, &inner); - break; - } + // cursor_lower_bound retrieves the "key" passed or the next existing larger key + if let Some(cursor) = inner.nodes.cursor_lower_bound(&start_ptr) { + let (_, node) = cursor.current(); + node.populate_debug_info(&mut out, &inner); } } @@ -1296,14 +1303,8 @@ impl Process { return Ok(()); } - let death = { - let death_init = NodeDeath::new(info.node_ref().node.clone(), self.clone(), cookie); - match death.pin_init_with(death_init) { - Ok(death) => death, - // error is infallible - Err(err) => match err {}, - } - }; + let death_init = NodeDeath::new(info.node_ref().node.clone(), self.clone(), cookie); + let Ok(death) = death.pin_init_with(death_init); // Register the death notification. { diff --git a/drivers/android/binder/range_alloc/array.rs b/drivers/android/binder/range_alloc/array.rs index 081d19b09d4b..71bf49f9db0d 100644 --- a/drivers/android/binder/range_alloc/array.rs +++ b/drivers/android/binder/range_alloc/array.rs @@ -61,7 +61,7 @@ impl<T> ArrayRangeAllocator<T> { self.ranges.len() == self.ranges.capacity() } - pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> { + pub(crate) fn debug_print(&self, m: &SeqFile) -> Result { for range in &self.ranges { seq_print!( m, diff --git a/drivers/android/binder/range_alloc/mod.rs b/drivers/android/binder/range_alloc/mod.rs index 1f4734468ff1..bb6e0ec9650e 100644 --- a/drivers/android/binder/range_alloc/mod.rs +++ b/drivers/android/binder/range_alloc/mod.rs @@ -141,7 +141,7 @@ impl<T> RangeAllocator<T> { } } - pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> { + pub(crate) fn debug_print(&self, m: &SeqFile) -> Result { match &self.inner { Impl::Empty(_size) => Ok(()), Impl::Array(array) => array.debug_print(m), diff --git a/drivers/android/binder/range_alloc/tree.rs b/drivers/android/binder/range_alloc/tree.rs index 3e3b452909ed..f0cd46f223d5 100644 --- a/drivers/android/binder/range_alloc/tree.rs +++ b/drivers/android/binder/range_alloc/tree.rs @@ -111,7 +111,7 @@ impl<T> TreeRangeAllocator<T> { .count() } - pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> { + pub(crate) fn debug_print(&self, m: &SeqFile) -> Result { for desc in self.tree.values() { let state = match &desc.state { Some(state) => &state.0, diff --git a/drivers/android/binder/rust_binder_events.h b/drivers/android/binder/rust_binder_events.h index 1a446787c8b3..045ab77aa44b 100644 --- a/drivers/android/binder/rust_binder_events.h +++ b/drivers/android/binder/rust_binder_events.h @@ -177,6 +177,48 @@ TRACE_EVENT(binder_return, "unknown") ); + +TRACE_EVENT(binder_transaction_alloc_buf, + TP_PROTO(int debug_id, size_t data_size, size_t offsets_size, size_t extra_buffers_size), + TP_ARGS(debug_id, data_size, offsets_size, extra_buffers_size), + TP_STRUCT__entry( + __field(int, debug_id) + __field(size_t, data_size) + __field(size_t, offsets_size) + __field(size_t, extra_buffers_size) + ), + TP_fast_assign( + __entry->debug_id = debug_id; + __entry->data_size = data_size; + __entry->offsets_size = offsets_size; + __entry->extra_buffers_size = extra_buffers_size; + ), + TP_printk("transaction=%d data_size=%zd offsets_size=%zd extra_buffers_size=%zd", + __entry->debug_id, __entry->data_size, __entry->offsets_size, + __entry->extra_buffers_size) +); + +DECLARE_EVENT_CLASS(binder_buffer_release_class, + TP_PROTO(int debug_id), + TP_ARGS(debug_id), + TP_STRUCT__entry( + __field(int, debug_id) + ), + TP_fast_assign( + __entry->debug_id = debug_id; + ), + TP_printk("transaction=%d", __entry->debug_id) +); + +#define DEFINE_RBINDER_FUNCTION_BUFFER_EVENT(name) \ +DEFINE_EVENT(binder_buffer_release_class, name, \ + TP_PROTO(int debug_id), \ + TP_ARGS(debug_id)) + +DEFINE_RBINDER_FUNCTION_BUFFER_EVENT(binder_transaction_buffer_release); +DEFINE_RBINDER_FUNCTION_BUFFER_EVENT(binder_transaction_failed_buffer_release); +DEFINE_RBINDER_FUNCTION_BUFFER_EVENT(binder_transaction_update_buffer_release); + #endif /* _RUST_BINDER_TRACE_H */ /* This part must be outside protection */ diff --git a/drivers/android/binder/rust_binder_internal.h b/drivers/android/binder/rust_binder_internal.h index 78288fe7964d..50a50df14c77 100644 --- a/drivers/android/binder/rust_binder_internal.h +++ b/drivers/android/binder/rust_binder_internal.h @@ -44,6 +44,7 @@ struct binder_device { int rust_binder_stats_show(struct seq_file *m, void *unused); int rust_binder_state_show(struct seq_file *m, void *unused); int rust_binder_transactions_show(struct seq_file *m, void *unused); +int rust_binder_transaction_log_show(struct seq_file *m, void *unused); int rust_binder_proc_show(struct seq_file *m, void *pid); extern const struct file_operations rust_binder_fops; diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index 955c4c348f73..56c786475237 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -81,7 +81,7 @@ mod binderfs { module! { type: BinderModule, - name: "rust_binder", + name: "binder", authors: ["Wedson Almeida Filho", "Alice Ryhl"], description: "Android Binder", license: "GPL", @@ -98,7 +98,12 @@ static RUST_BINDER_LAYOUT: rust_binder_layout = rust_binder_layout { fn next_debug_id() -> usize { static NEXT_DEBUG_ID: Atomic<usize> = Atomic::new(0); - NEXT_DEBUG_ID.fetch_add(1, Relaxed) + loop { + let id = NEXT_DEBUG_ID.fetch_add(1, Relaxed); + if id != 0 { + return id; + } + } } /// Provides a single place to write Binder return values via the @@ -158,7 +163,7 @@ trait DeliverToRead: ListArcSafe + Send + Sync { /// Generally only set to true for non-oneway transactions. fn should_sync_wakeup(&self) -> bool; - fn debug_print(&self, m: &SeqFile, prefix: &str, transaction_prefix: &str) -> Result<()>; + fn debug_print(&self, m: &SeqFile, prefix: &str, transaction_prefix: &str) -> Result; } // Wrapper around a `DeliverToRead` with linked list links. @@ -274,7 +279,7 @@ impl DeliverToRead for DeliverCode { false } - fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result { seq_print!(m, "{}", prefix); if self.skip.load(Relaxed) { seq_print!(m, "(skipped) "); @@ -305,6 +310,9 @@ impl kernel::Module for BinderModule { // SAFETY: The module initializer never runs twice, so we only call this once. unsafe { crate::context::CONTEXTS.init() }; + crate::transaction::TRANSACTION_LOG.init()?; + crate::transaction::FAILED_TRANSACTION_LOG.init()?; + let netlink = crate::netlink::BINDER_NL_FAMILY.register()?; BINDER_SHRINKER.register(c"android-binder")?; @@ -545,7 +553,28 @@ unsafe extern "C" fn rust_binder_transactions_show( 0 } -fn rust_binder_transactions_show_impl(m: &SeqFile) -> Result<()> { +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_transaction_log_show( + ptr: *mut seq_file, + _: *mut kernel::ffi::c_void, +) -> kernel::ffi::c_int { + // SAFETY: Accessing the private field of `seq_file` is okay. + let is_failed = !unsafe { (*ptr).private }.is_null(); + // SAFETY: The caller ensures that the pointer is valid and exclusive for the duration in which + // this method is called. + let m = unsafe { SeqFile::from_raw(ptr) }; + let log = if is_failed { + &transaction::FAILED_TRANSACTION_LOG + } else { + &transaction::TRANSACTION_LOG + }; + log.debug_print(m); + 0 +} + +fn rust_binder_transactions_show_impl(m: &SeqFile) -> Result { seq_print!(m, "binder transactions:\n"); let contexts = context::get_all_contexts()?; for ctx in contexts { @@ -558,7 +587,7 @@ fn rust_binder_transactions_show_impl(m: &SeqFile) -> Result<()> { Ok(()) } -fn rust_binder_stats_show_impl(m: &SeqFile) -> Result<()> { +fn rust_binder_stats_show_impl(m: &SeqFile) -> Result { seq_print!(m, "binder stats:\n"); stats::GLOBAL_STATS.debug_print("", m); let contexts = context::get_all_contexts()?; @@ -572,7 +601,7 @@ fn rust_binder_stats_show_impl(m: &SeqFile) -> Result<()> { Ok(()) } -fn rust_binder_state_show_impl(m: &SeqFile) -> Result<()> { +fn rust_binder_state_show_impl(m: &SeqFile) -> Result { seq_print!(m, "binder state:\n"); let contexts = context::get_all_contexts()?; for ctx in contexts { @@ -585,7 +614,7 @@ fn rust_binder_state_show_impl(m: &SeqFile) -> Result<()> { Ok(()) } -fn rust_binder_proc_show_impl(m: &SeqFile, pid: Pid) -> Result<()> { +fn rust_binder_proc_show_impl(m: &SeqFile, pid: Pid) -> Result { seq_print!(m, "binder proc state:\n"); let contexts = context::get_all_contexts()?; for ctx in contexts { diff --git a/drivers/android/binder/rust_binderfs.c b/drivers/android/binder/rust_binderfs.c index d48895f84596..5f020226720d 100644 --- a/drivers/android/binder/rust_binderfs.c +++ b/drivers/android/binder/rust_binderfs.c @@ -46,10 +46,11 @@ DEFINE_SHOW_ATTRIBUTE(rust_binder_stats); DEFINE_SHOW_ATTRIBUTE(rust_binder_state); DEFINE_SHOW_ATTRIBUTE(rust_binder_transactions); +DEFINE_SHOW_ATTRIBUTE(rust_binder_transaction_log); DEFINE_SHOW_ATTRIBUTE(rust_binder_proc); char *rust_binder_devices_param = CONFIG_ANDROID_BINDER_DEVICES; -module_param_named(rust_devices, rust_binder_devices_param, charp, 0444); +module_param_named(devices, rust_binder_devices_param, charp, 0444); extern u32 rust_binder_debug_mask; module_param_named(debug_mask, rust_binder_debug_mask, uint, 0644); @@ -611,6 +612,24 @@ static int init_binder_logs(struct super_block *sb) goto out; } + dentry = rust_binderfs_create_file(binder_logs_root_dir, + "transaction_log", + &rust_binder_transaction_log_fops, + (void *)0); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto out; + } + + dentry = rust_binderfs_create_file(binder_logs_root_dir, + "failed_transaction_log", + &rust_binder_transaction_log_fops, + (void *)1); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto out; + } + proc_log_dir = binderfs_create_dir(binder_logs_root_dir, "proc"); if (IS_ERR(proc_log_dir)) { ret = PTR_ERR(proc_log_dir); diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index e0ed20f510fd..f5965e838a96 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -30,6 +30,7 @@ use crate::{ process::{GetWorkOrRegister, Process}, ptr_align, stats::GLOBAL_STATS, + trace::{trace_transaction_alloc_buf, trace_transaction_buffer_release}, transaction::{Transaction, TransactionFlag, TransactionFlags, TransactionInfo}, BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverCode, DeliverToRead, }; @@ -344,13 +345,13 @@ impl InnerThread { } } - fn push_return_work(&mut self, reply: u32) { + fn push_return_work(&mut self, reply: u32) -> PushWorkRes { if let Ok(work) = ListArc::try_from_arc(self.return_work.clone()) { work.set_error_code(reply); - // Not notifying: Reply to current thread. - let _ = self.push_work(work); + self.push_work(work) } else { pr_warn_ratelimited!("Thread return work is already in use.\n"); + PushWorkRes::Ok } } @@ -481,7 +482,7 @@ impl Thread { } #[inline(never)] - pub(crate) fn debug_print(self: &Arc<Self>, m: &SeqFile, print_all: bool) -> Result<()> { + pub(crate) fn debug_print(self: &Arc<Self>, m: &SeqFile, print_all: bool) -> Result { let inner = self.inner.lock(); if print_all || inner.current_transaction.is_some() || !inner.work_list.is_empty() { @@ -584,9 +585,21 @@ impl Thread { // mangled symbol names. #[export_name = "rust_binder_wait"] fn get_work(self: &Arc<Self>, wait: bool) -> Result<Option<DLArc<dyn DeliverToRead>>> { + let thread_has_deferred_work; + // Try to get work from the thread's work queue, using only a local lock. { let mut inner = self.inner.lock(); + + // The process_work_list boolean is used to make us go to sleep even if there is work + // in the thread todo-list, but it doesn't apply to the process todo-list. Furthermore, + // work in the thread todo-list must still be delivered before the process list. + // + // Thus, in some scenarios we must return the thread work now even if we were requested + // to wait. Adjust `process_work_list` to `true` accordingly. + inner.process_work_list |= inner.looper_need_return; + inner.process_work_list |= !wait; + if let Some(work) = inner.pop_work() { return Ok(Some(work)); } @@ -594,18 +607,26 @@ impl Thread { drop(inner); return Ok(self.process.get_work()); } + + // Note that if the thread list is empty, then the call to `pop_work()` has changed + // `process_work_list` back to `false` even if we set it to `true` above. + thread_has_deferred_work = !inner.work_list.is_empty(); } // If the caller doesn't want to wait, try to grab work from the process queue. // // We know nothing will have been queued directly to the thread queue because it is not in - // a transaction and it is not in the process' ready list. + // a transaction and it is not in the process' ready list. We also know the thread list has + // no deferred work due to the `inner.process_work_list |= !wait` call above. if !wait { return self.process.get_work().ok_or(EAGAIN).map(Some); } // Get work from the process queue. If none is available, atomically register as ready. - let reg = match self.process.get_work_or_register(self) { + let reg = match self + .process + .get_work_or_register(self, thread_has_deferred_work) + { GetWorkOrRegister::Work(work) => return Ok(Some(work)), GetWorkOrRegister::Register(reg) => reg, }; @@ -621,14 +642,18 @@ impl Thread { inner.looper_flags &= !(LooperFlag::Waiting | LooperFlag::WaitingProc); if signal_pending || inner.looper_need_return { - // We need to return now. We need to pull the thread off the list of ready threads - // (by dropping `reg`), then check the state again after it's off the list to - // ensure that something was not queued in the meantime. If something has been - // queued, we just return it (instead of the error). + // We need to return now. + // + // We need to pull the thread off the list of ready threads (by dropping `reg`), + // then check the state again after it's off the list to ensure that something was + // not queued in the meantime. If something has been queued (or if there is + // deferred work), we just return it (instead of the error). drop(inner); drop(reg); - let res = match self.inner.lock().pop_work() { + inner = self.inner.lock(); + inner.process_work_list = true; + let res = match inner.pop_work() { Some(work) => Ok(Some(work)), None if signal_pending => Err(EINTR), None => Ok(None), @@ -669,8 +694,12 @@ impl Thread { pub(crate) fn push_work_if_looper(&self, work: DLArc<dyn DeliverToRead>) -> BinderResult { let mut inner = self.inner.lock(); if inner.is_looper() && !inner.is_dead { - // Not notifying: Reply to current thread. - let _ = inner.push_work(work); + // Not notifying work_condvar: Reply to current thread. + let res = inner.push_work(work); + drop(inner); + if matches!(res, PushWorkRes::OkNotifyPoll) { + self.process.notify_poll(false); + } Ok(()) } else { drop(inner); @@ -683,7 +712,11 @@ impl Thread { } pub(crate) fn push_return_work(&self, reply: u32) { - self.inner.lock().push_return_work(reply); + // Not notifying work_condvar: Reply to current thread. + let res = self.inner.lock().push_return_work(reply); + if matches!(res, PushWorkRes::OkNotifyPoll) { + self.process.notify_poll(false); + } } pub(crate) fn pop_work_even_if_deferred(&self) -> Option<DLArc<dyn DeliverToRead>> { @@ -1046,17 +1079,9 @@ impl Thread { size_of::<u64>(), ); let secctx_off = aligned_data_size + offsets_size + buffers_size; - let mut alloc = match to_process.buffer_alloc(debug_id, len, info) { - Ok(alloc) => alloc, - Err(err) => { - pr_warn!( - "Failed to allocate buffer. len:{}, is_oneway:{}", - len, - info.is_oneway(), - ); - return Err(err); - } - }; + let mut alloc = to_process.buffer_alloc(debug_id, len, info)?; + + trace_transaction_alloc_buf(debug_id, data_size, offsets_size, buffers_size); let mut buffer_reader = UserSlice::new(info.data_ptr, data_size).reader(); let mut end_of_previous_object = 0; @@ -1262,7 +1287,7 @@ impl Thread { cmd: u32, reader: &mut UserSliceReader, info: &mut TransactionInfo, - ) -> Result<()> { + ) -> Result { let td = match cmd { BC_TRANSACTION | BC_REPLY => { reader.read::<BinderTransactionData>()?.with_buffers_size(0) @@ -1293,7 +1318,7 @@ impl Thread { } #[inline(never)] - fn transaction(self: &Arc<Self>, cmd: u32, reader: &mut UserSliceReader) -> Result<()> { + fn transaction(self: &Arc<Self>, cmd: u32, reader: &mut UserSliceReader) -> Result { let mut info = TransactionInfo::zeroed(); self.read_transaction_info(cmd, reader, &mut info)?; @@ -1307,47 +1332,57 @@ impl Thread { self.transaction_inner(&mut info) }; + // This runs when return work is passed to the caller. This is not + // always the same as the transaction failing, as reply errors are + // delivered to the remote process. if let Err(err) = ret { self.push_return_work(err.reply); if err.reply != BR_TRANSACTION_COMPLETE { info.reply = err.reply; - if let Some(source) = &err.source { - info.errno = source.to_errno(); + info.error_line = Some(err.line); + if let Some(source) = err.source { + info.errno = Some(source); - { - let mut inner = self.inner.lock(); - inner.extended_error = - ExtendedError::new(info.debug_id as u32, err.reply, source.to_errno()); - } - - binder_debug!( - FailedTransaction, - "transaction {} to {}:{} failed {:?}, code {} size {}-{}", - if info.is_reply { - "reply" - } else if info.is_oneway() { - "async" - } else { - "call" - }, - info.to_pid, - info.to_tid, - err, - info.code, - info.data_size, - info.offsets_size - ); + self.inner.lock().extended_error = + ExtendedError::new(info.debug_id as u32, err.reply, source.to_errno()); } } } + info.write_log(&self.process.ctx); + if info.oneway_spam_suspect { // If this is both a oneway spam suspect and a failure, we report it twice. This is // useful in case the transaction failed with BR_TRANSACTION_PENDING_FROZEN. info.report_netlink(BR_ONEWAY_SPAM_SUSPECT, &self.process.ctx); } + // This runs when the transaction failed. if info.reply != 0 { info.report_netlink(info.reply, &self.process.ctx); + if let Some(errno) = info.errno { + let error_line: &dyn kernel::fmt::Display = match &info.error_line { + Some(line) => line, + None => &0, + }; + binder_debug!( + FailedTransaction, + "transaction {} to {}:{} failed {:?}, code {} size {}-{} line {}", + if info.is_reply { + "reply" + } else if info.is_oneway() { + "async" + } else { + "call" + }, + info.to_pid, + info.to_tid, + errno, + info.code, + info.data_size, + info.offsets_size, + error_line + ); + } } Ok(()) @@ -1356,6 +1391,7 @@ impl Thread { fn transaction_inner(self: &Arc<Self>, info: &mut TransactionInfo) -> BinderResult { let node_ref = self.process.get_transaction_node(info.target_handle)?; info.to_pid = node_ref.node.owner.task.pid(); + info.to_node_debug_id = node_ref.node.debug_id; security::binder_transaction(&self.process.cred, &node_ref.node.owner.cred)?; // TODO: We need to ensure that there isn't a pending transaction in the work queue. How // could this happen? @@ -1422,8 +1458,17 @@ impl Thread { let process = orig.from.process.clone(); let allow_fds = orig.flags.contains(TransactionFlag::AcceptFds); let reply = Transaction::new_reply(self, process, info, allow_fds)?; - // Not notifying: Reply to current thread. - let _ = self.inner.lock().push_work(completion); + if info.flags.contains(TransactionFlag::DeferComplete) { + // The flag is set. Perform a deferred push so that `read` can wait for the + // next incoming transaction without a userspace roundtrip. + self.inner.lock().push_work_deferred(completion); + } else { + // Not notifying work_condvar: Reply to current thread. + let res = self.inner.lock().push_work(completion); + if matches!(res, PushWorkRes::OkNotifyPoll) { + self.process.notify_poll(false); + } + } orig.from.deliver_reply(Ok(reply), &orig, None); Ok(()) })() @@ -1431,17 +1476,13 @@ impl Thread { // At this point we only return `BR_TRANSACTION_COMPLETE` to the caller, and we must let // the sender know that the transaction has completed (with an error in this case). - pr_warn!( - "{}:{} reply to {} failed: {err:?}", - info.from_pid, - info.from_tid, - info.to_pid - ); let param = err.source.as_ref().map_or(0, |e| e.to_errno()); let ee = ExtendedError::new(info.debug_id as u32, err.reply, param); orig.from .deliver_reply(Err(BR_FAILED_REPLY), &orig, Some(ee)); info.reply = BR_FAILED_REPLY; + info.errno = err.source; + info.error_line = Some(err.line); err.reply = BR_TRANSACTION_COMPLETE; err }); @@ -1452,6 +1493,7 @@ impl Thread { fn oneway_transaction_inner(self: &Arc<Self>, info: &mut TransactionInfo) -> BinderResult { let node_ref = self.process.get_transaction_node(info.target_handle)?; info.to_pid = node_ref.node.owner.task.pid(); + info.to_node_debug_id = node_ref.node.debug_id; security::binder_transaction(&self.process.cred, &node_ref.node.owner.cred)?; let transaction = Transaction::new(node_ref, None, self, info)?; let code = if self.process.is_oneway_spam_detection_enabled() && info.oneway_spam_suspect { @@ -1462,10 +1504,15 @@ impl Thread { let list_completion = DTRWrap::arc_try_new(DeliverCode::new(code, self.process.task.pid()))?; let completion = list_completion.clone_arc(); - // Not notifying: Reply to current thread. - let _ = self.inner.lock().push_work(list_completion); + // Not notifying work_condvar: Reply to current thread. + let res = self.inner.lock().push_work(list_completion); match transaction.submit(info) { - Ok(()) => Ok(()), + Ok(()) => { + if matches!(res, PushWorkRes::OkNotifyPoll) { + self.process.notify_poll(false); + } + Ok(()) + } Err(err) => { completion.skip(); Err(err) @@ -1495,6 +1542,7 @@ impl Thread { if buffer.looper_need_return_on_free() { self.inner.lock().looper_need_return = true; } + trace_transaction_buffer_release(buffer.debug_id); drop(buffer); } } @@ -1750,7 +1798,7 @@ impl DeliverToRead for ThreadError { false } - fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result { seq_print!( m, "{}transaction error: {}\n", diff --git a/drivers/android/binder/trace.rs b/drivers/android/binder/trace.rs index 06aabb3cc2f1..de2894bcb368 100644 --- a/drivers/android/binder/trace.rs +++ b/drivers/android/binder/trace.rs @@ -24,6 +24,11 @@ declare_trace! { unsafe fn binder_transaction_fd_recv(t_debug_id: c_int, fd: c_int, offset: usize); unsafe fn binder_command(cmd: u32); unsafe fn binder_return(ret: u32); + unsafe fn binder_transaction_alloc_buf( + debug_id: c_int,data_size: usize, offset_size: usize, extra_buffers_size: usize); + unsafe fn binder_transaction_buffer_release(debug_id: c_int); + unsafe fn binder_transaction_failed_buffer_release(debug_id: c_int); + unsafe fn binder_transaction_update_buffer_release(debug_id: c_int); } #[inline] @@ -105,3 +110,35 @@ pub(crate) fn trace_return(ret: u32) { // SAFETY: This function is always safe to call. unsafe { binder_return(ret) } } +#[inline] +pub(crate) fn trace_transaction_alloc_buf( + debug_id: usize, + data_size: usize, + offset_size: usize, + extra_buffers_size: usize, +) { + // SAFETY: The `data` pointer is valid. + unsafe { + binder_transaction_alloc_buf( + debug_id as c_int, + data_size, + offset_size, + extra_buffers_size, + ) + } +} +#[inline] +pub(crate) fn trace_transaction_buffer_release(debug_id: usize) { + // SAFETY: This function is always safe to call. + unsafe { binder_transaction_buffer_release(debug_id as c_int) } +} +#[inline] +pub(crate) fn trace_transaction_failed_buffer_release(debug_id: usize) { + // SAFETY: This function is always safe to call. + unsafe { binder_transaction_failed_buffer_release(debug_id as c_int) } +} +#[inline] +pub(crate) fn trace_transaction_update_buffer_release(debug_id: usize) { + // SAFETY: This function is always safe to call. + unsafe { binder_transaction_update_buffer_release(debug_id as c_int) } +} diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs index 66045547c23a..48ac942bb75a 100644 --- a/drivers/android/binder/transaction.rs +++ b/drivers/android/binder/transaction.rs @@ -7,8 +7,9 @@ use kernel::{ prelude::*, seq_file::SeqFile, seq_print, + str::BStr, sync::atomic::{ordering::Relaxed, Atomic}, - sync::{Arc, SpinLock}, + sync::{Arc, SetOnce, SpinLock}, task::{Kuid, Pid}, time::{Instant, Monotonic}, types::ScopeGuard, @@ -18,12 +19,13 @@ use kernel::{ use crate::{ allocation::{Allocation, TranslatedFds}, defs::*, - error::{BinderError, BinderResult}, + error::{BinderError, BinderResult, ErrorLocation}, netlink::Report, node::{Node, NodeRef}, process::{Process, ProcessInner}, ptr_align, thread::{PushWorkRes, Thread}, + trace::trace_transaction_update_buffer_release, BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead, }; @@ -39,6 +41,7 @@ kernel::impl_flags!( AcceptFds = TF_ACCEPT_FDS, ClearBuf = TF_CLEAR_BUF, UpdateTxn = TF_UPDATE_TXN, + DeferComplete = TF_DEFER_COMPLETE, } ); @@ -54,12 +57,199 @@ impl TransactionFlags { } } +const LOG_SIZE: usize = 32; + +pub(crate) static TRANSACTION_LOG: TransactionLog = TransactionLog::new(); +pub(crate) static FAILED_TRANSACTION_LOG: TransactionLog = TransactionLog::new(); + +#[derive(Copy, Clone)] +enum CallType { + Call, + Async, + Reply, +} + +#[derive(Copy, Clone)] +pub(crate) struct TransactionLogEntry { + debug_id: usize, + call_type: CallType, + from_proc: Pid, + from_thread: Pid, + // The kernel ignores `target.handle` on replies (where `libbinder` passes `-1` / `0xffffffff`), + // and C Binder stores and prints this field as a signed `int` (`%d`). + target_handle: i32, + to_proc: Pid, + to_thread: Pid, + to_node: usize, + data_size: usize, + offsets_size: usize, + return_error_line: Option<ErrorLocation>, + return_error: u32, + return_error_param: i32, + context_name: [u8; 16], +} + +impl TransactionLogEntry { + const fn empty() -> Self { + Self { + debug_id: 0, + call_type: CallType::Call, + from_proc: 0, + from_thread: 0, + target_handle: 0, + to_proc: 0, + to_thread: 0, + to_node: 0, + data_size: 0, + offsets_size: 0, + return_error_line: None, + return_error: 0, + return_error_param: 0, + context_name: [0; 16], + } + } + + fn new(info: &TransactionInfo, ctx: &crate::Context) -> Self { + let call_type = if info.is_reply { + CallType::Reply + } else if info.is_oneway() { + CallType::Async + } else { + CallType::Call + }; + let name_bytes = ctx.name.to_bytes(); + let mut context_name = [0u8; 16]; + let len = usize::min(name_bytes.len(), context_name.len()); + context_name[..len].copy_from_slice(&name_bytes[..len]); + + let failed = info.reply != 0 && info.reply != BR_TRANSACTION_PENDING_FROZEN; + Self { + debug_id: info.debug_id, + call_type, + from_proc: info.from_pid, + from_thread: info.from_tid, + target_handle: info.target_handle as i32, + to_proc: info.to_pid, + to_thread: info.to_tid, + to_node: info.to_node_debug_id, + data_size: info.data_size, + offsets_size: info.offsets_size, + return_error_line: if failed { info.error_line } else { None }, + return_error: if failed { info.reply } else { 0 }, + return_error_param: if failed { + info.errno.map_or(0, |e| e.to_errno()) + } else { + 0 + }, + context_name, + } + } +} + +#[pin_data] +#[repr(align(64))] +struct TransactionLogSlot { + #[pin] + entry: SpinLock<TransactionLogEntry>, +} + +pub(crate) struct TransactionLog { + cur: Atomic<usize>, + entries: SetOnce<Pin<KBox<[TransactionLogSlot; LOG_SIZE]>>>, +} + +impl TransactionLog { + const fn new() -> Self { + Self { + cur: Atomic::new(0), + entries: SetOnce::new(), + } + } + + pub(crate) fn init(&self) -> Result { + let entries = KBox::pin_init( + pin_init::pin_init_array_from_fn(|_| { + pin_init!(TransactionLogSlot { + entry <- kernel::new_spinlock!( + TransactionLogEntry::empty(), + "TransactionLog::entries" + ), + }) + }), + GFP_KERNEL, + )?; + self.entries.populate(entries); + Ok(()) + } + + fn add(&self, entry: &TransactionLogEntry) { + let Some(entries) = self.entries.as_ref() else { + return; + }; + let idx = self.cur.fetch_add(1, Relaxed) % LOG_SIZE; + let mut slot = entries[idx].entry.lock(); + // Avoid overwriting a newer entry if the ring buffer wraps around between `fetch_add` and + // acquiring the lock. + // CAST: Overflowing behavior of this cast is intentional to handle `debug_id` wrap-around. + let diff = slot.debug_id.wrapping_sub(entry.debug_id) as isize; + if slot.debug_id == 0 || diff < 0 { + *slot = *entry; + } + } + + pub(crate) fn debug_print(&self, m: &SeqFile) { + let Some(entries) = self.entries.as_ref() else { + return; + }; + let cur = self.cur.load(Relaxed); + for i in 0..LOG_SIZE { + let idx = cur.wrapping_add(i) % LOG_SIZE; + let entry = *entries[idx].entry.lock(); + if entry.debug_id == 0 { + continue; + } + let call_type = match entry.call_type { + CallType::Call => "call ", + CallType::Async => "async", + CallType::Reply => "reply", + }; + let ctx_name = match CStr::from_bytes_until_nul(&entry.context_name) { + Ok(cstr) => BStr::from_bytes(cstr.to_bytes()), + Err(_) => BStr::from_bytes(&entry.context_name), + }; + let return_error_line: &dyn kernel::fmt::Display = match &entry.return_error_line { + Some(line) => line, + None => &0, + }; + seq_print!( + m, + "{}: {} from {}:{} to {}:{} context {} node {} handle {} size {}:{} ret {}/{} l={}\n", + entry.debug_id, + call_type, + entry.from_proc, + entry.from_thread, + entry.to_proc, + entry.to_thread, + ctx_name, + entry.to_node, + entry.target_handle, + entry.data_size, + entry.offsets_size, + entry.return_error, + entry.return_error_param, + return_error_line, + ); + } + } +} + #[derive(Zeroable)] pub(crate) struct TransactionInfo { pub(crate) from_pid: Pid, pub(crate) from_tid: Pid, pub(crate) to_pid: Pid, pub(crate) to_tid: Pid, + pub(crate) to_node_debug_id: usize, pub(crate) code: u32, pub(crate) flags: TransactionFlags, pub(crate) data_ptr: UserPtr, @@ -68,8 +258,9 @@ pub(crate) struct TransactionInfo { pub(crate) offsets_size: usize, pub(crate) buffers_size: usize, pub(crate) target_handle: u32, - pub(crate) errno: i32, + pub(crate) errno: Option<Error>, pub(crate) reply: u32, + pub(crate) error_line: Option<ErrorLocation>, pub(crate) oneway_spam_suspect: bool, pub(crate) is_reply: bool, pub(crate) debug_id: usize, @@ -81,6 +272,14 @@ impl TransactionInfo { self.flags.is_oneway() } + pub(crate) fn write_log(&self, ctx: &crate::Context) { + let entry = TransactionLogEntry::new(self, ctx); + TRANSACTION_LOG.add(&entry); + if self.reply != 0 && self.reply != BR_TRANSACTION_PENDING_FROZEN { + FAILED_TRANSACTION_LOG.add(&entry); + } + } + pub(crate) fn report_netlink(&self, reply: u32, ctx: &crate::Context) { if let Err(err) = self.report_netlink_inner(reply, ctx) { pr_warn_ratelimited!( @@ -166,21 +365,13 @@ impl Transaction { let txn_security_ctx = node_ref.node.flags & FLAT_BINDER_FLAG_TXN_SECURITY_CTX != 0; let mut txn_security_ctx_off = if txn_security_ctx { Some(0) } else { None }; let to = node_ref.node.owner.clone(); - let mut alloc = match from.copy_transaction_data( + let mut alloc = from.copy_transaction_data( to.clone(), info, info.debug_id, allow_fds, txn_security_ctx_off.as_mut(), - ) { - Ok(alloc) => alloc, - Err(err) => { - if !err.is_dead() { - pr_warn!("Failure in copy_transaction_data: {:?}", err); - } - return Err(err); - } - }; + )?; if info.is_oneway() { if from_parent.is_some() { pr_warn_ratelimited!("Oneway transaction should not be in a transaction stack.\n"); @@ -221,13 +412,7 @@ impl Transaction { allow_fds: bool, ) -> BinderResult<DLArc<Self>> { let mut alloc = - match from.copy_transaction_data(to.clone(), info, info.debug_id, allow_fds, None) { - Ok(alloc) => alloc, - Err(err) => { - pr_warn!("Failure in copy_transaction_data: {:?}", err); - return Err(err); - } - }; + from.copy_transaction_data(to.clone(), info, info.debug_id, allow_fds, None)?; if info.flags.contains(TransactionFlag::ClearBuf) { alloc.set_info_clear_on_drop(); } @@ -355,6 +540,7 @@ impl Transaction { if let Some(t_outdated) = target_node.take_outdated_transaction(&self, &mut process_inner) { + trace_transaction_update_buffer_release(t_outdated.debug_id); let mut alloc_guard = t_outdated.allocation.lock(); if let Some(alloc) = (*alloc_guard).as_mut() { // Take the oneway node to prevent `Allocation::drop` from calling @@ -576,7 +762,7 @@ impl DeliverToRead for Transaction { !self.flags.is_oneway() } - fn debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result<()> { + fn debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result { self.debug_print_inner(m, tprefix); Ok(()) } diff --git a/drivers/android/binder_alloc.c b/drivers/android/binder_alloc.c deleted file mode 100644 index d6eae0aa7085..000000000000 --- a/drivers/android/binder_alloc.c +++ /dev/null @@ -1,1398 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* binder_alloc.c - * - * Android IPC Subsystem - * - * Copyright (C) 2007-2017 Google, Inc. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/list.h> -#include <linux/sched/mm.h> -#include <linux/module.h> -#include <linux/rtmutex.h> -#include <linux/rbtree.h> -#include <linux/seq_file.h> -#include <linux/vmalloc.h> -#include <linux/slab.h> -#include <linux/sched.h> -#include <linux/list_lru.h> -#include <linux/ratelimit.h> -#include <asm/cacheflush.h> -#include <linux/uaccess.h> -#include <linux/highmem.h> -#include <linux/sizes.h> -#include <kunit/visibility.h> -#include "binder_alloc.h" -#include "binder_trace.h" - -static struct list_lru binder_freelist; - -static DEFINE_MUTEX(binder_alloc_mmap_lock); - -enum { - BINDER_DEBUG_USER_ERROR = 1U << 0, - BINDER_DEBUG_OPEN_CLOSE = 1U << 1, - BINDER_DEBUG_BUFFER_ALLOC = 1U << 2, - BINDER_DEBUG_BUFFER_ALLOC_ASYNC = 1U << 3, -}; -static uint32_t binder_alloc_debug_mask = BINDER_DEBUG_USER_ERROR; - -module_param_named(debug_mask, binder_alloc_debug_mask, - uint, 0644); - -#define binder_alloc_debug(mask, x...) \ - do { \ - if (binder_alloc_debug_mask & mask) \ - pr_info_ratelimited(x); \ - } while (0) - -static struct binder_buffer *binder_buffer_next(struct binder_buffer *buffer) -{ - return list_entry(buffer->entry.next, struct binder_buffer, entry); -} - -static struct binder_buffer *binder_buffer_prev(struct binder_buffer *buffer) -{ - return list_entry(buffer->entry.prev, struct binder_buffer, entry); -} - -VISIBLE_IF_KUNIT size_t binder_alloc_buffer_size(struct binder_alloc *alloc, - struct binder_buffer *buffer) -{ - if (list_is_last(&buffer->entry, &alloc->buffers)) - return alloc->vm_start + alloc->buffer_size - buffer->user_data; - return binder_buffer_next(buffer)->user_data - buffer->user_data; -} -EXPORT_SYMBOL_IF_KUNIT(binder_alloc_buffer_size); - -static void binder_insert_free_buffer(struct binder_alloc *alloc, - struct binder_buffer *new_buffer) -{ - struct rb_node **p = &alloc->free_buffers.rb_node; - struct rb_node *parent = NULL; - struct binder_buffer *buffer; - size_t buffer_size; - size_t new_buffer_size; - - BUG_ON(!new_buffer->free); - - new_buffer_size = binder_alloc_buffer_size(alloc, new_buffer); - - binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%d: add free buffer, size %zd, at %p\n", - alloc->pid, new_buffer_size, new_buffer); - - while (*p) { - parent = *p; - buffer = rb_entry(parent, struct binder_buffer, rb_node); - BUG_ON(!buffer->free); - - buffer_size = binder_alloc_buffer_size(alloc, buffer); - - if (new_buffer_size < buffer_size) - p = &parent->rb_left; - else - p = &parent->rb_right; - } - rb_link_node(&new_buffer->rb_node, parent, p); - rb_insert_color(&new_buffer->rb_node, &alloc->free_buffers); -} - -static void binder_insert_allocated_buffer_locked( - struct binder_alloc *alloc, struct binder_buffer *new_buffer) -{ - struct rb_node **p = &alloc->allocated_buffers.rb_node; - struct rb_node *parent = NULL; - struct binder_buffer *buffer; - - BUG_ON(new_buffer->free); - - while (*p) { - parent = *p; - buffer = rb_entry(parent, struct binder_buffer, rb_node); - BUG_ON(buffer->free); - - if (new_buffer->user_data < buffer->user_data) - p = &parent->rb_left; - else if (new_buffer->user_data > buffer->user_data) - p = &parent->rb_right; - else - BUG(); - } - rb_link_node(&new_buffer->rb_node, parent, p); - rb_insert_color(&new_buffer->rb_node, &alloc->allocated_buffers); -} - -static struct binder_buffer *binder_alloc_prepare_to_free_locked( - struct binder_alloc *alloc, - unsigned long user_ptr) -{ - struct rb_node *n = alloc->allocated_buffers.rb_node; - struct binder_buffer *buffer; - - while (n) { - buffer = rb_entry(n, struct binder_buffer, rb_node); - BUG_ON(buffer->free); - - if (user_ptr < buffer->user_data) { - n = n->rb_left; - } else if (user_ptr > buffer->user_data) { - n = n->rb_right; - } else { - /* - * Guard against user threads attempting to - * free the buffer when in use by kernel or - * after it's already been freed. - */ - if (!buffer->allow_user_free) - return ERR_PTR(-EPERM); - buffer->allow_user_free = 0; - return buffer; - } - } - return NULL; -} - -/** - * binder_alloc_prepare_to_free() - get buffer given user ptr - * @alloc: binder_alloc for this proc - * @user_ptr: User pointer to buffer data - * - * Validate userspace pointer to buffer data and return buffer corresponding to - * that user pointer. Search the rb tree for buffer that matches user data - * pointer. - * - * Return: Pointer to buffer or NULL - */ -struct binder_buffer *binder_alloc_prepare_to_free(struct binder_alloc *alloc, - unsigned long user_ptr) -{ - guard(mutex)(&alloc->mutex); - return binder_alloc_prepare_to_free_locked(alloc, user_ptr); -} - -static inline void -binder_set_installed_page(struct binder_alloc *alloc, - unsigned long index, - struct page *page) -{ - /* Pairs with acquire in binder_get_installed_page() */ - smp_store_release(&alloc->pages[index], page); -} - -static inline struct page * -binder_get_installed_page(struct binder_alloc *alloc, unsigned long index) -{ - /* Pairs with release in binder_set_installed_page() */ - return smp_load_acquire(&alloc->pages[index]); -} - -static void binder_lru_freelist_add(struct binder_alloc *alloc, - unsigned long start, unsigned long end) -{ - unsigned long page_addr; - struct page *page; - - trace_binder_update_page_range(alloc, false, start, end); - - for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) { - size_t index; - int ret; - - index = (page_addr - alloc->vm_start) / PAGE_SIZE; - page = binder_get_installed_page(alloc, index); - if (!page) - continue; - - trace_binder_free_lru_start(alloc, index); - - ret = list_lru_add(alloc->freelist, - page_to_lru(page), - page_to_nid(page), - NULL); - WARN_ON(!ret); - - trace_binder_free_lru_end(alloc, index); - } -} - -static inline -void binder_alloc_set_mapped(struct binder_alloc *alloc, bool state) -{ - /* pairs with smp_load_acquire in binder_alloc_is_mapped() */ - smp_store_release(&alloc->mapped, state); -} - -static inline bool binder_alloc_is_mapped(struct binder_alloc *alloc) -{ - /* pairs with smp_store_release in binder_alloc_set_mapped() */ - return smp_load_acquire(&alloc->mapped); -} - -static struct page *binder_page_lookup(struct binder_alloc *alloc, - unsigned long addr) -{ - struct mm_struct *mm = alloc->mm; - struct page *page; - long npages = 0; - - /* - * Find an existing page in the remote mm. If missing, - * don't attempt to fault-in just propagate an error. - */ - mmap_read_lock(mm); - if (binder_alloc_is_mapped(alloc)) - npages = get_user_pages_remote(mm, addr, 1, FOLL_NOFAULT, - &page, NULL); - mmap_read_unlock(mm); - - return npages > 0 ? page : NULL; -} - -static int binder_page_insert(struct binder_alloc *alloc, - unsigned long addr, - struct page *page) -{ - struct mm_struct *mm = alloc->mm; - struct vm_area_struct *vma; - int ret = -ESRCH; - - vma = vma_start_read_unlocked(mm, addr); - if (!vma) - return ret; - - if (binder_alloc_is_mapped(alloc)) - ret = vm_insert_page(vma, addr, page); - - vma_end_read(vma); - - return ret; -} - -static struct page *binder_page_alloc(struct binder_alloc *alloc, - unsigned long index) -{ - struct binder_shrinker_mdata *mdata; - struct page *page; - - page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO); - if (!page) - return NULL; - - /* allocate and install shrinker metadata under page->private */ - mdata = kzalloc_obj(*mdata); - if (!mdata) { - __free_page(page); - return NULL; - } - - mdata->alloc = alloc; - mdata->page_index = index; - INIT_LIST_HEAD(&mdata->lru); - set_page_private(page, (unsigned long)mdata); - - return page; -} - -static void binder_free_page(struct page *page) -{ - kfree((struct binder_shrinker_mdata *)page_private(page)); - __free_page(page); -} - -static int binder_install_single_page(struct binder_alloc *alloc, - unsigned long index, - unsigned long addr) -{ - struct page *page; - int ret; - - if (!mmget_not_zero(alloc->mm)) - return -ESRCH; - - page = binder_page_alloc(alloc, index); - if (!page) { - ret = -ENOMEM; - goto out; - } - - ret = binder_page_insert(alloc, addr, page); - switch (ret) { - case -EBUSY: - /* - * EBUSY is ok. Someone installed the pte first but the - * alloc->pages[index] has not been updated yet. Discard - * our page and look up the one already installed. - */ - ret = 0; - binder_free_page(page); - page = binder_page_lookup(alloc, addr); - if (!page) { - pr_err("%d: failed to find page at offset %lx\n", - alloc->pid, addr - alloc->vm_start); - ret = -ESRCH; - break; - } - fallthrough; - case 0: - /* Mark page installation complete and safe to use */ - binder_set_installed_page(alloc, index, page); - break; - default: - binder_free_page(page); - pr_err("%d: %s failed to insert page at offset %lx with %d\n", - alloc->pid, __func__, addr - alloc->vm_start, ret); - break; - } -out: - mmput_async(alloc->mm); - return ret; -} - -static int binder_install_buffer_pages(struct binder_alloc *alloc, - struct binder_buffer *buffer, - size_t size) -{ - unsigned long start, final; - unsigned long page_addr; - - start = buffer->user_data & PAGE_MASK; - final = PAGE_ALIGN(buffer->user_data + size); - - for (page_addr = start; page_addr < final; page_addr += PAGE_SIZE) { - unsigned long index; - int ret; - - index = (page_addr - alloc->vm_start) / PAGE_SIZE; - if (binder_get_installed_page(alloc, index)) - continue; - - trace_binder_alloc_page_start(alloc, index); - - ret = binder_install_single_page(alloc, index, page_addr); - if (ret) - return ret; - - trace_binder_alloc_page_end(alloc, index); - } - - return 0; -} - -/* The range of pages should exclude those shared with other buffers */ -static void binder_lru_freelist_del(struct binder_alloc *alloc, - unsigned long start, unsigned long end) -{ - unsigned long page_addr; - struct page *page; - - trace_binder_update_page_range(alloc, true, start, end); - - for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) { - unsigned long index; - bool on_lru; - - index = (page_addr - alloc->vm_start) / PAGE_SIZE; - page = binder_get_installed_page(alloc, index); - - if (page) { - trace_binder_alloc_lru_start(alloc, index); - - on_lru = list_lru_del(alloc->freelist, - page_to_lru(page), - page_to_nid(page), - NULL); - WARN_ON(!on_lru); - - trace_binder_alloc_lru_end(alloc, index); - continue; - } - - if (index + 1 > alloc->pages_high) - alloc->pages_high = index + 1; - } -} - -static void debug_no_space_locked(struct binder_alloc *alloc) -{ - size_t largest_alloc_size = 0; - struct binder_buffer *buffer; - size_t allocated_buffers = 0; - size_t largest_free_size = 0; - size_t total_alloc_size = 0; - size_t total_free_size = 0; - size_t free_buffers = 0; - size_t buffer_size; - struct rb_node *n; - - for (n = rb_first(&alloc->allocated_buffers); n; n = rb_next(n)) { - buffer = rb_entry(n, struct binder_buffer, rb_node); - buffer_size = binder_alloc_buffer_size(alloc, buffer); - allocated_buffers++; - total_alloc_size += buffer_size; - if (buffer_size > largest_alloc_size) - largest_alloc_size = buffer_size; - } - - for (n = rb_first(&alloc->free_buffers); n; n = rb_next(n)) { - buffer = rb_entry(n, struct binder_buffer, rb_node); - buffer_size = binder_alloc_buffer_size(alloc, buffer); - free_buffers++; - total_free_size += buffer_size; - if (buffer_size > largest_free_size) - largest_free_size = buffer_size; - } - - binder_alloc_debug(BINDER_DEBUG_USER_ERROR, - "allocated: %zd (num: %zd largest: %zd), free: %zd (num: %zd largest: %zd)\n", - total_alloc_size, allocated_buffers, - largest_alloc_size, total_free_size, - free_buffers, largest_free_size); -} - -static bool debug_low_async_space_locked(struct binder_alloc *alloc) -{ - /* - * Find the amount and size of buffers allocated by the current caller; - * The idea is that once we cross the threshold, whoever is responsible - * for the low async space is likely to try to send another async txn, - * and at some point we'll catch them in the act. This is more efficient - * than keeping a map per pid. - */ - struct binder_buffer *buffer; - size_t total_alloc_size = 0; - int pid = current->tgid; - size_t num_buffers = 0; - struct rb_node *n; - - /* - * Only start detecting spammers once we have less than 20% of async - * space left (which is less than 10% of total buffer size). - */ - if (alloc->free_async_space >= alloc->buffer_size / 10) { - alloc->oneway_spam_detected = false; - return false; - } - - for (n = rb_first(&alloc->allocated_buffers); n != NULL; - n = rb_next(n)) { - buffer = rb_entry(n, struct binder_buffer, rb_node); - if (buffer->pid != pid) - continue; - if (!buffer->async_transaction) - continue; - total_alloc_size += binder_alloc_buffer_size(alloc, buffer); - num_buffers++; - } - - /* - * Warn if this pid has more than 50 transactions, or more than 50% of - * async space (which is 25% of total buffer size). Oneway spam is only - * detected when the threshold is exceeded. - */ - if (num_buffers > 50 || total_alloc_size > alloc->buffer_size / 4) { - binder_alloc_debug(BINDER_DEBUG_USER_ERROR, - "%d: pid %d spamming oneway? %zd buffers allocated for a total size of %zd\n", - alloc->pid, pid, num_buffers, total_alloc_size); - if (!alloc->oneway_spam_detected) { - alloc->oneway_spam_detected = true; - return true; - } - } - return false; -} - -/* Callers preallocate @new_buffer, it is freed by this function if unused */ -static struct binder_buffer *binder_alloc_new_buf_locked( - struct binder_alloc *alloc, - struct binder_buffer *new_buffer, - size_t size, - int is_async) -{ - struct rb_node *n = alloc->free_buffers.rb_node; - struct rb_node *best_fit = NULL; - struct binder_buffer *buffer; - unsigned long next_used_page; - unsigned long curr_last_page; - size_t buffer_size; - - if (is_async && alloc->free_async_space < size) { - binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%d: binder_alloc_buf size %zd failed, no async space left\n", - alloc->pid, size); - buffer = ERR_PTR(-ENOSPC); - goto out; - } - - while (n) { - buffer = rb_entry(n, struct binder_buffer, rb_node); - BUG_ON(!buffer->free); - buffer_size = binder_alloc_buffer_size(alloc, buffer); - - if (size < buffer_size) { - best_fit = n; - n = n->rb_left; - } else if (size > buffer_size) { - n = n->rb_right; - } else { - best_fit = n; - break; - } - } - - if (unlikely(!best_fit)) { - binder_alloc_debug(BINDER_DEBUG_USER_ERROR, - "%d: binder_alloc_buf size %zd failed, no address space\n", - alloc->pid, size); - debug_no_space_locked(alloc); - buffer = ERR_PTR(-ENOSPC); - goto out; - } - - if (buffer_size != size) { - /* Found an oversized buffer and needs to be split */ - buffer = rb_entry(best_fit, struct binder_buffer, rb_node); - buffer_size = binder_alloc_buffer_size(alloc, buffer); - - WARN_ON(n || buffer_size == size); - new_buffer->user_data = buffer->user_data + size; - list_add(&new_buffer->entry, &buffer->entry); - new_buffer->free = 1; - binder_insert_free_buffer(alloc, new_buffer); - new_buffer = NULL; - } - - binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%d: binder_alloc_buf size %zd got buffer %p size %zd\n", - alloc->pid, size, buffer, buffer_size); - - /* - * Now we remove the pages from the freelist. A clever calculation - * with buffer_size determines if the last page is shared with an - * adjacent in-use buffer. In such case, the page has been already - * removed from the freelist so we trim our range short. - */ - next_used_page = (buffer->user_data + buffer_size) & PAGE_MASK; - curr_last_page = PAGE_ALIGN(buffer->user_data + size); - binder_lru_freelist_del(alloc, PAGE_ALIGN(buffer->user_data), - min(next_used_page, curr_last_page)); - - rb_erase(&buffer->rb_node, &alloc->free_buffers); - buffer->free = 0; - buffer->allow_user_free = 0; - binder_insert_allocated_buffer_locked(alloc, buffer); - buffer->async_transaction = is_async; - buffer->oneway_spam_suspect = false; - if (is_async) { - alloc->free_async_space -= size; - binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC, - "%d: binder_alloc_buf size %zd async free %zd\n", - alloc->pid, size, alloc->free_async_space); - if (debug_low_async_space_locked(alloc)) - buffer->oneway_spam_suspect = true; - } - -out: - /* Discard possibly unused new_buffer */ - kfree(new_buffer); - return buffer; -} - -/* Calculate the sanitized total size, returns 0 for invalid request */ -static inline size_t sanitized_size(size_t data_size, - size_t offsets_size, - size_t extra_buffers_size) -{ - size_t total, tmp; - - /* Align to pointer size and check for overflows */ - tmp = ALIGN(data_size, sizeof(void *)) + - ALIGN(offsets_size, sizeof(void *)); - if (tmp < data_size || tmp < offsets_size) - return 0; - total = tmp + ALIGN(extra_buffers_size, sizeof(void *)); - if (total < tmp || total < extra_buffers_size) - return 0; - - /* Pad 0-sized buffers so they get a unique address */ - total = max(total, sizeof(void *)); - - return total; -} - -/** - * binder_alloc_new_buf() - Allocate a new binder buffer - * @alloc: binder_alloc for this proc - * @data_size: size of user data buffer - * @offsets_size: user specified buffer offset - * @extra_buffers_size: size of extra space for meta-data (eg, security context) - * @is_async: buffer for async transaction - * - * Allocate a new buffer given the requested sizes. Returns - * the kernel version of the buffer pointer. The size allocated - * is the sum of the three given sizes (each rounded up to - * pointer-sized boundary) - * - * Return: The allocated buffer or %ERR_PTR(-errno) if error - */ -struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc, - size_t data_size, - size_t offsets_size, - size_t extra_buffers_size, - int is_async) -{ - struct binder_buffer *buffer, *next; - size_t size; - int ret; - - /* Check binder_alloc is fully initialized */ - if (!binder_alloc_is_mapped(alloc)) { - binder_alloc_debug(BINDER_DEBUG_USER_ERROR, - "%d: binder_alloc_buf, no vma\n", - alloc->pid); - return ERR_PTR(-ESRCH); - } - - size = sanitized_size(data_size, offsets_size, extra_buffers_size); - if (unlikely(!size)) { - binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%d: got transaction with invalid size %zd-%zd-%zd\n", - alloc->pid, data_size, offsets_size, - extra_buffers_size); - return ERR_PTR(-EINVAL); - } - - /* Preallocate the next buffer */ - next = kzalloc_obj(*next); - if (!next) - return ERR_PTR(-ENOMEM); - - mutex_lock(&alloc->mutex); - buffer = binder_alloc_new_buf_locked(alloc, next, size, is_async); - if (IS_ERR(buffer)) { - mutex_unlock(&alloc->mutex); - goto out; - } - - buffer->data_size = data_size; - buffer->offsets_size = offsets_size; - buffer->extra_buffers_size = extra_buffers_size; - buffer->pid = current->tgid; - mutex_unlock(&alloc->mutex); - - ret = binder_install_buffer_pages(alloc, buffer, size); - if (ret) { - binder_alloc_free_buf(alloc, buffer); - buffer = ERR_PTR(ret); - } -out: - return buffer; -} -EXPORT_SYMBOL_IF_KUNIT(binder_alloc_new_buf); - -static unsigned long buffer_start_page(struct binder_buffer *buffer) -{ - return buffer->user_data & PAGE_MASK; -} - -static unsigned long prev_buffer_end_page(struct binder_buffer *buffer) -{ - return (buffer->user_data - 1) & PAGE_MASK; -} - -static void binder_delete_free_buffer(struct binder_alloc *alloc, - struct binder_buffer *buffer) -{ - struct binder_buffer *prev, *next; - - if (PAGE_ALIGNED(buffer->user_data)) - goto skip_freelist; - - BUG_ON(alloc->buffers.next == &buffer->entry); - prev = binder_buffer_prev(buffer); - BUG_ON(!prev->free); - if (prev_buffer_end_page(prev) == buffer_start_page(buffer)) - goto skip_freelist; - - if (!list_is_last(&buffer->entry, &alloc->buffers)) { - next = binder_buffer_next(buffer); - if (buffer_start_page(next) == buffer_start_page(buffer)) - goto skip_freelist; - } - - binder_lru_freelist_add(alloc, buffer_start_page(buffer), - buffer_start_page(buffer) + PAGE_SIZE); -skip_freelist: - list_del(&buffer->entry); - kfree(buffer); -} - -static void binder_free_buf_locked(struct binder_alloc *alloc, - struct binder_buffer *buffer) -{ - size_t size, buffer_size; - - buffer_size = binder_alloc_buffer_size(alloc, buffer); - - size = ALIGN(buffer->data_size, sizeof(void *)) + - ALIGN(buffer->offsets_size, sizeof(void *)) + - ALIGN(buffer->extra_buffers_size, sizeof(void *)); - - binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%d: binder_free_buf %p size %zd buffer_size %zd\n", - alloc->pid, buffer, size, buffer_size); - - BUG_ON(buffer->free); - BUG_ON(size > buffer_size); - BUG_ON(buffer->transaction != NULL); - BUG_ON(buffer->user_data < alloc->vm_start); - BUG_ON(buffer->user_data > alloc->vm_start + alloc->buffer_size); - - if (buffer->async_transaction) { - alloc->free_async_space += buffer_size; - binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC, - "%d: binder_free_buf size %zd async free %zd\n", - alloc->pid, size, alloc->free_async_space); - } - - binder_lru_freelist_add(alloc, PAGE_ALIGN(buffer->user_data), - (buffer->user_data + buffer_size) & PAGE_MASK); - - rb_erase(&buffer->rb_node, &alloc->allocated_buffers); - buffer->free = 1; - if (!list_is_last(&buffer->entry, &alloc->buffers)) { - struct binder_buffer *next = binder_buffer_next(buffer); - - if (next->free) { - rb_erase(&next->rb_node, &alloc->free_buffers); - binder_delete_free_buffer(alloc, next); - } - } - if (alloc->buffers.next != &buffer->entry) { - struct binder_buffer *prev = binder_buffer_prev(buffer); - - if (prev->free) { - binder_delete_free_buffer(alloc, buffer); - rb_erase(&prev->rb_node, &alloc->free_buffers); - buffer = prev; - } - } - binder_insert_free_buffer(alloc, buffer); -} - -/** - * binder_alloc_get_page() - get kernel pointer for given buffer offset - * @alloc: binder_alloc for this proc - * @buffer: binder buffer to be accessed - * @buffer_offset: offset into @buffer data - * @pgoffp: address to copy final page offset to - * - * Lookup the struct page corresponding to the address - * at @buffer_offset into @buffer->user_data. If @pgoffp is not - * NULL, the byte-offset into the page is written there. - * - * The caller is responsible to ensure that the offset points - * to a valid address within the @buffer and that @buffer is - * not freeable by the user. Since it can't be freed, we are - * guaranteed that the corresponding elements of @alloc->pages[] - * cannot change. - * - * Return: struct page - */ -static struct page *binder_alloc_get_page(struct binder_alloc *alloc, - struct binder_buffer *buffer, - binder_size_t buffer_offset, - pgoff_t *pgoffp) -{ - binder_size_t buffer_space_offset = buffer_offset + - (buffer->user_data - alloc->vm_start); - pgoff_t pgoff = buffer_space_offset & ~PAGE_MASK; - size_t index = buffer_space_offset >> PAGE_SHIFT; - - *pgoffp = pgoff; - - return alloc->pages[index]; -} - -/** - * binder_alloc_clear_buf() - zero out buffer - * @alloc: binder_alloc for this proc - * @buffer: binder buffer to be cleared - * - * memset the given buffer to 0 - */ -static void binder_alloc_clear_buf(struct binder_alloc *alloc, - struct binder_buffer *buffer) -{ - size_t bytes = binder_alloc_buffer_size(alloc, buffer); - binder_size_t buffer_offset = 0; - - while (bytes) { - unsigned long size; - struct page *page; - pgoff_t pgoff; - - page = binder_alloc_get_page(alloc, buffer, - buffer_offset, &pgoff); - size = min_t(size_t, bytes, PAGE_SIZE - pgoff); - memset_page(page, pgoff, 0, size); - bytes -= size; - buffer_offset += size; - } -} - -/** - * binder_alloc_free_buf() - free a binder buffer - * @alloc: binder_alloc for this proc - * @buffer: kernel pointer to buffer - * - * Free the buffer allocated via binder_alloc_new_buf() - */ -void binder_alloc_free_buf(struct binder_alloc *alloc, - struct binder_buffer *buffer) -{ - /* - * We could eliminate the call to binder_alloc_clear_buf() - * from binder_alloc_deferred_release() by moving this to - * binder_free_buf_locked(). However, that could - * increase contention for the alloc mutex if clear_on_free - * is used frequently for large buffers. The mutex is not - * needed for correctness here. - */ - if (buffer->clear_on_free) { - binder_alloc_clear_buf(alloc, buffer); - buffer->clear_on_free = false; - } - mutex_lock(&alloc->mutex); - binder_free_buf_locked(alloc, buffer); - mutex_unlock(&alloc->mutex); -} -EXPORT_SYMBOL_IF_KUNIT(binder_alloc_free_buf); - -/** - * binder_alloc_mmap_handler() - map virtual address space for proc - * @alloc: alloc structure for this proc - * @vma: vma passed to mmap() - * - * Called by binder_mmap() to initialize the space specified in - * vma for allocating binder buffers - * - * Return: - * 0 = success - * -EBUSY = address space already mapped - * -ENOMEM = failed to map memory to given address space - */ -int binder_alloc_mmap_handler(struct binder_alloc *alloc, - struct vm_area_struct *vma) -{ - struct binder_buffer *buffer; - const char *failure_string; - int ret; - - if (unlikely(vma->vm_mm != alloc->mm)) { - ret = -EINVAL; - failure_string = "invalid vma->vm_mm"; - goto err_invalid_mm; - } - - mutex_lock(&binder_alloc_mmap_lock); - if (alloc->buffer_size) { - ret = -EBUSY; - failure_string = "already mapped"; - goto err_already_mapped; - } - alloc->buffer_size = min_t(unsigned long, vma->vm_end - vma->vm_start, - SZ_4M); - mutex_unlock(&binder_alloc_mmap_lock); - - alloc->vm_start = vma->vm_start; - - alloc->pages = kvzalloc_objs(alloc->pages[0], - alloc->buffer_size / PAGE_SIZE); - if (!alloc->pages) { - ret = -ENOMEM; - failure_string = "alloc page array"; - goto err_alloc_pages_failed; - } - - buffer = kzalloc_obj(*buffer); - if (!buffer) { - ret = -ENOMEM; - failure_string = "alloc buffer struct"; - goto err_alloc_buf_struct_failed; - } - - buffer->user_data = alloc->vm_start; - list_add(&buffer->entry, &alloc->buffers); - buffer->free = 1; - binder_insert_free_buffer(alloc, buffer); - alloc->free_async_space = alloc->buffer_size / 2; - - /* Signal binder_alloc is fully initialized */ - binder_alloc_set_mapped(alloc, true); - - return 0; - -err_alloc_buf_struct_failed: - kvfree(alloc->pages); - alloc->pages = NULL; -err_alloc_pages_failed: - alloc->vm_start = 0; - mutex_lock(&binder_alloc_mmap_lock); - alloc->buffer_size = 0; -err_already_mapped: - mutex_unlock(&binder_alloc_mmap_lock); -err_invalid_mm: - binder_alloc_debug(BINDER_DEBUG_USER_ERROR, - "%s: %d %lx-%lx %s failed %d\n", __func__, - alloc->pid, vma->vm_start, vma->vm_end, - failure_string, ret); - return ret; -} -EXPORT_SYMBOL_IF_KUNIT(binder_alloc_mmap_handler); - -void binder_alloc_deferred_release(struct binder_alloc *alloc) -{ - struct rb_node *n; - int buffers, page_count; - struct binder_buffer *buffer; - - buffers = 0; - mutex_lock(&alloc->mutex); - BUG_ON(alloc->mapped); - - while ((n = rb_first(&alloc->allocated_buffers))) { - buffer = rb_entry(n, struct binder_buffer, rb_node); - - /* Transaction should already have been freed */ - BUG_ON(buffer->transaction); - - if (buffer->clear_on_free) { - binder_alloc_clear_buf(alloc, buffer); - buffer->clear_on_free = false; - } - binder_free_buf_locked(alloc, buffer); - buffers++; - } - - while (!list_empty(&alloc->buffers)) { - buffer = list_first_entry(&alloc->buffers, - struct binder_buffer, entry); - WARN_ON(!buffer->free); - - list_del(&buffer->entry); - WARN_ON_ONCE(!list_empty(&alloc->buffers)); - kfree(buffer); - } - - page_count = 0; - if (alloc->pages) { - int i; - - for (i = 0; i < alloc->buffer_size / PAGE_SIZE; i++) { - struct page *page; - bool on_lru; - - page = binder_get_installed_page(alloc, i); - if (!page) - continue; - - on_lru = list_lru_del(alloc->freelist, - page_to_lru(page), - page_to_nid(page), - NULL); - binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%s: %d: page %d %s\n", - __func__, alloc->pid, i, - on_lru ? "on lru" : "active"); - binder_free_page(page); - page_count++; - } - } - mutex_unlock(&alloc->mutex); - kvfree(alloc->pages); - if (alloc->mm) - mmdrop(alloc->mm); - - binder_alloc_debug(BINDER_DEBUG_OPEN_CLOSE, - "%s: %d buffers %d, pages %d\n", - __func__, alloc->pid, buffers, page_count); -} -EXPORT_SYMBOL_IF_KUNIT(binder_alloc_deferred_release); - -/** - * binder_alloc_print_allocated() - print buffer info - * @m: seq_file for output via seq_printf() - * @alloc: binder_alloc for this proc - * - * Prints information about every buffer associated with - * the binder_alloc state to the given seq_file - */ -void binder_alloc_print_allocated(struct seq_file *m, - struct binder_alloc *alloc) -{ - struct binder_buffer *buffer; - struct rb_node *n; - - guard(mutex)(&alloc->mutex); - for (n = rb_first(&alloc->allocated_buffers); n; n = rb_next(n)) { - buffer = rb_entry(n, struct binder_buffer, rb_node); - seq_printf(m, " buffer %d: %lx size %zd:%zd:%zd %s\n", - buffer->debug_id, - buffer->user_data - alloc->vm_start, - buffer->data_size, buffer->offsets_size, - buffer->extra_buffers_size, - buffer->transaction ? "active" : "delivered"); - } -} - -/** - * binder_alloc_print_pages() - print page usage - * @m: seq_file for output via seq_printf() - * @alloc: binder_alloc for this proc - */ -void binder_alloc_print_pages(struct seq_file *m, - struct binder_alloc *alloc) -{ - struct page *page; - int i; - int active = 0; - int lru = 0; - int free = 0; - - mutex_lock(&alloc->mutex); - /* - * Make sure the binder_alloc is fully initialized, otherwise we might - * read inconsistent state. - */ - if (binder_alloc_is_mapped(alloc)) { - for (i = 0; i < alloc->buffer_size / PAGE_SIZE; i++) { - page = binder_get_installed_page(alloc, i); - if (!page) - free++; - else if (list_empty(page_to_lru(page))) - active++; - else - lru++; - } - } - mutex_unlock(&alloc->mutex); - seq_printf(m, " pages: %d:%d:%d\n", active, lru, free); - seq_printf(m, " pages high watermark: %zu\n", alloc->pages_high); -} - -/** - * binder_alloc_get_allocated_count() - return count of buffers - * @alloc: binder_alloc for this proc - * - * Return: count of allocated buffers - */ -int binder_alloc_get_allocated_count(struct binder_alloc *alloc) -{ - struct rb_node *n; - int count = 0; - - guard(mutex)(&alloc->mutex); - for (n = rb_first(&alloc->allocated_buffers); n != NULL; n = rb_next(n)) - count++; - return count; -} - - -/** - * binder_alloc_vma_close() - invalidate address space - * @alloc: binder_alloc for this proc - * - * Called from binder_vma_close() when releasing address space. - * Clears alloc->mapped to prevent new incoming transactions from - * allocating more buffers. - */ -void binder_alloc_vma_close(struct binder_alloc *alloc) -{ - binder_alloc_set_mapped(alloc, false); -} -EXPORT_SYMBOL_IF_KUNIT(binder_alloc_vma_close); - -/** - * binder_alloc_free_page() - shrinker callback to free pages - * @item: item to free - * @lru: list_lru instance of the item - * @cb_arg: callback argument - * - * Called from list_lru_walk() in binder_shrink_scan() to free - * up pages when the system is under memory pressure. - */ -enum lru_status binder_alloc_free_page(struct list_head *item, - struct list_lru_one *lru, - void *cb_arg) - __must_hold(&lru->lock) -{ - struct binder_shrinker_mdata *mdata = container_of(item, typeof(*mdata), lru); - struct binder_alloc *alloc = mdata->alloc; - struct mm_struct *mm = alloc->mm; - struct vm_area_struct *vma; - struct page *page_to_free; - unsigned long page_addr; - size_t index; - - if (!mmget_not_zero(mm)) - goto err_mmget; - - index = mdata->page_index; - page_addr = alloc->vm_start + index * PAGE_SIZE; - - /* - * Attempt per-vma lock. This is essentially a - * "trylock". It can fail even if the VMA exists - * for 'page_addr'. - */ - vma = lock_vma_under_rcu(mm, page_addr); - if (!vma) { - /* - * If the vma exists, we can't continue because we cannot - * remove the page from the vma. However, if the vma was - * unmapped, it's okay to continue. - */ - if (binder_alloc_is_mapped(alloc)) - goto err_vma_lock_failed; - } - - if (!mutex_trylock(&alloc->mutex)) - goto err_get_alloc_mutex_failed; - - trace_binder_unmap_kernel_start(alloc, index); - - page_to_free = alloc->pages[index]; - binder_set_installed_page(alloc, index, NULL); - - trace_binder_unmap_kernel_end(alloc, index); - - list_lru_isolate(lru, item); - spin_unlock(&lru->lock); - - /* - * Since a binder_alloc can only be mapped once, we ensure - * the vma corresponds to this mapping by checking whether - * the binder_alloc is still mapped. - */ - if (vma && binder_alloc_is_mapped(alloc)) { - trace_binder_unmap_user_start(alloc, index); - - zap_vma_range(vma, page_addr, PAGE_SIZE); - - trace_binder_unmap_user_end(alloc, index); - } - - mutex_unlock(&alloc->mutex); - if (vma) - vma_end_read(vma); - mmput_async(mm); - binder_free_page(page_to_free); - - return LRU_REMOVED_RETRY; - -err_get_alloc_mutex_failed: - if (vma) - vma_end_read(vma); -err_vma_lock_failed: - mmput_async(mm); -err_mmget: - return LRU_SKIP; -} -EXPORT_SYMBOL_IF_KUNIT(binder_alloc_free_page); - -static unsigned long -binder_shrink_count(struct shrinker *shrink, struct shrink_control *sc) -{ - return list_lru_count(&binder_freelist); -} - -static unsigned long -binder_shrink_scan(struct shrinker *shrink, struct shrink_control *sc) -{ - return list_lru_walk(&binder_freelist, binder_alloc_free_page, - NULL, sc->nr_to_scan); -} - -static struct shrinker *binder_shrinker; - -VISIBLE_IF_KUNIT void __binder_alloc_init(struct binder_alloc *alloc, - struct list_lru *freelist) -{ - alloc->pid = current->tgid; - alloc->mm = current->mm; - mmgrab(alloc->mm); - mutex_init(&alloc->mutex); - INIT_LIST_HEAD(&alloc->buffers); - alloc->freelist = freelist; -} -EXPORT_SYMBOL_IF_KUNIT(__binder_alloc_init); - -/** - * binder_alloc_init() - called by binder_open() for per-proc initialization - * @alloc: binder_alloc for this proc - * - * Called from binder_open() to initialize binder_alloc fields for - * new binder proc - */ -void binder_alloc_init(struct binder_alloc *alloc) -{ - __binder_alloc_init(alloc, &binder_freelist); -} - -int binder_alloc_shrinker_init(void) -{ - int ret; - - ret = list_lru_init(&binder_freelist); - if (ret) - return ret; - - binder_shrinker = shrinker_alloc(0, "android-binder"); - if (!binder_shrinker) { - list_lru_destroy(&binder_freelist); - return -ENOMEM; - } - - binder_shrinker->count_objects = binder_shrink_count; - binder_shrinker->scan_objects = binder_shrink_scan; - - shrinker_register(binder_shrinker); - - return 0; -} - -void binder_alloc_shrinker_exit(void) -{ - shrinker_free(binder_shrinker); - list_lru_destroy(&binder_freelist); -} - -/** - * check_buffer() - verify that buffer/offset is safe to access - * @alloc: binder_alloc for this proc - * @buffer: binder buffer to be accessed - * @offset: offset into @buffer data - * @bytes: bytes to access from offset - * - * Check that the @offset/@bytes are within the size of the given - * @buffer and that the buffer is currently active and not freeable. - * Offsets must also be multiples of sizeof(u32). The kernel is - * allowed to touch the buffer in two cases: - * - * 1) when the buffer is being created: - * (buffer->free == 0 && buffer->allow_user_free == 0) - * 2) when the buffer is being torn down: - * (buffer->free == 0 && buffer->transaction == NULL). - * - * Return: true if the buffer is safe to access - */ -static inline bool check_buffer(struct binder_alloc *alloc, - struct binder_buffer *buffer, - binder_size_t offset, size_t bytes) -{ - size_t buffer_size = binder_alloc_buffer_size(alloc, buffer); - - return buffer_size >= bytes && - offset <= buffer_size - bytes && - IS_ALIGNED(offset, sizeof(u32)) && - !buffer->free && - (!buffer->allow_user_free || !buffer->transaction); -} - -/** - * binder_alloc_copy_user_to_buffer() - copy src user to tgt user - * @alloc: binder_alloc for this proc - * @buffer: binder buffer to be accessed - * @buffer_offset: offset into @buffer data - * @from: userspace pointer to source buffer - * @bytes: bytes to copy - * - * Copy bytes from source userspace to target buffer. - * - * Return: bytes remaining to be copied - */ -unsigned long -binder_alloc_copy_user_to_buffer(struct binder_alloc *alloc, - struct binder_buffer *buffer, - binder_size_t buffer_offset, - const void __user *from, - size_t bytes) -{ - if (!check_buffer(alloc, buffer, buffer_offset, bytes)) - return bytes; - - while (bytes) { - unsigned long size; - unsigned long ret; - struct page *page; - pgoff_t pgoff; - void *kptr; - - page = binder_alloc_get_page(alloc, buffer, - buffer_offset, &pgoff); - size = min_t(size_t, bytes, PAGE_SIZE - pgoff); - kptr = kmap_local_page(page) + pgoff; - ret = copy_from_user(kptr, from, size); - kunmap_local(kptr); - if (ret) - return bytes - size + ret; - bytes -= size; - from += size; - buffer_offset += size; - } - return 0; -} - -static int binder_alloc_do_buffer_copy(struct binder_alloc *alloc, - bool to_buffer, - struct binder_buffer *buffer, - binder_size_t buffer_offset, - void *ptr, - size_t bytes) -{ - /* All copies must be 32-bit aligned and 32-bit size */ - if (!check_buffer(alloc, buffer, buffer_offset, bytes)) - return -EINVAL; - - while (bytes) { - unsigned long size; - struct page *page; - pgoff_t pgoff; - - page = binder_alloc_get_page(alloc, buffer, - buffer_offset, &pgoff); - size = min_t(size_t, bytes, PAGE_SIZE - pgoff); - if (to_buffer) - memcpy_to_page(page, pgoff, ptr, size); - else - memcpy_from_page(ptr, page, pgoff, size); - bytes -= size; - pgoff = 0; - ptr = ptr + size; - buffer_offset += size; - } - return 0; -} - -int binder_alloc_copy_to_buffer(struct binder_alloc *alloc, - struct binder_buffer *buffer, - binder_size_t buffer_offset, - void *src, - size_t bytes) -{ - return binder_alloc_do_buffer_copy(alloc, true, buffer, buffer_offset, - src, bytes); -} - -int binder_alloc_copy_from_buffer(struct binder_alloc *alloc, - void *dest, - struct binder_buffer *buffer, - binder_size_t buffer_offset, - size_t bytes) -{ - return binder_alloc_do_buffer_copy(alloc, false, buffer, buffer_offset, - dest, bytes); -} diff --git a/drivers/android/binder_alloc.h b/drivers/android/binder_alloc.h deleted file mode 100644 index d6f1f6f2d00e..000000000000 --- a/drivers/android/binder_alloc.h +++ /dev/null @@ -1,189 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2017 Google, Inc. - */ - -#ifndef _LINUX_BINDER_ALLOC_H -#define _LINUX_BINDER_ALLOC_H - -#include <linux/rbtree.h> -#include <linux/list.h> -#include <linux/mm.h> -#include <linux/rtmutex.h> -#include <linux/vmalloc.h> -#include <linux/slab.h> -#include <linux/list_lru.h> -#include <uapi/linux/android/binder.h> - -struct binder_transaction; - -/** - * struct binder_buffer - buffer used for binder transactions - * @entry: entry alloc->buffers - * @rb_node: node for allocated_buffers/free_buffers rb trees - * @free: %true if buffer is free - * @clear_on_free: %true if buffer must be zeroed after use - * @allow_user_free: %true if user is allowed to free buffer - * @async_transaction: %true if buffer is in use for an async txn - * @oneway_spam_suspect: %true if total async allocate size just exceed - * spamming detect threshold - * @debug_id: unique ID for debugging - * @transaction: pointer to associated struct binder_transaction - * @target_node: struct binder_node associated with this buffer - * @data_size: size of @transaction data - * @offsets_size: size of array of offsets - * @extra_buffers_size: size of space for other objects (like sg lists) - * @user_data: user pointer to base of buffer space - * @pid: pid to attribute the buffer to (caller) - * - * Bookkeeping structure for binder transaction buffers - */ -struct binder_buffer { - struct list_head entry; /* free and allocated entries by address */ - struct rb_node rb_node; /* free entry by size or allocated entry */ - /* by address */ - unsigned free:1; - unsigned clear_on_free:1; - unsigned allow_user_free:1; - unsigned async_transaction:1; - unsigned oneway_spam_suspect:1; - unsigned debug_id:27; - struct binder_transaction *transaction; - struct binder_node *target_node; - size_t data_size; - size_t offsets_size; - size_t extra_buffers_size; - unsigned long user_data; - int pid; -}; - -/** - * struct binder_shrinker_mdata - binder metadata used to reclaim pages - * @lru: LRU entry in binder_freelist - * @alloc: binder_alloc owning the page to reclaim - * @page_index: offset in @alloc->pages[] into the page to reclaim - */ -struct binder_shrinker_mdata { - struct list_head lru; - struct binder_alloc *alloc; - unsigned long page_index; -}; - -static inline struct list_head *page_to_lru(struct page *p) -{ - struct binder_shrinker_mdata *mdata; - - mdata = (struct binder_shrinker_mdata *)page_private(p); - - return &mdata->lru; -} - -/** - * struct binder_alloc - per-binder proc state for binder allocator - * @mutex: protects binder_alloc fields - * @mm: copy of task->mm (invariant after open) - * @vm_start: base of per-proc address space mapped via mmap - * @buffers: list of all buffers for this proc - * @free_buffers: rb tree of buffers available for allocation - * sorted by size - * @allocated_buffers: rb tree of allocated buffers sorted by address - * @free_async_space: VA space available for async buffers. This is - * initialized at mmap time to 1/2 the full VA space - * @pages: array of struct page * - * @freelist: lru list to use for free pages (invariant after init) - * @buffer_size: size of address space specified via mmap - * @pid: pid for associated binder_proc (invariant after init) - * @pages_high: high watermark of offset in @pages - * @mapped: whether the vm area is mapped, each binder instance is - * allowed a single mapping throughout its lifetime - * @oneway_spam_detected: %true if oneway spam detection fired, clear that - * flag once the async buffer has returned to a healthy state - * - * Bookkeeping structure for per-proc address space management for binder - * buffers. It is normally initialized during binder_init() and binder_mmap() - * calls. The address space is used for both user-visible buffers and for - * struct binder_buffer objects used to track the user buffers - */ -struct binder_alloc { - struct mutex mutex; - struct mm_struct *mm; - unsigned long vm_start; - struct list_head buffers; - struct rb_root free_buffers; - struct rb_root allocated_buffers; - size_t free_async_space; - struct page **pages; - struct list_lru *freelist; - size_t buffer_size; - int pid; - size_t pages_high; - bool mapped; - bool oneway_spam_detected; -}; - -enum lru_status binder_alloc_free_page(struct list_head *item, - struct list_lru_one *lru, - void *cb_arg); -struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc, - size_t data_size, - size_t offsets_size, - size_t extra_buffers_size, - int is_async); -void binder_alloc_init(struct binder_alloc *alloc); -int binder_alloc_shrinker_init(void); -void binder_alloc_shrinker_exit(void); -void binder_alloc_vma_close(struct binder_alloc *alloc); -struct binder_buffer * -binder_alloc_prepare_to_free(struct binder_alloc *alloc, - unsigned long user_ptr); -void binder_alloc_free_buf(struct binder_alloc *alloc, - struct binder_buffer *buffer); -int binder_alloc_mmap_handler(struct binder_alloc *alloc, - struct vm_area_struct *vma); -void binder_alloc_deferred_release(struct binder_alloc *alloc); -int binder_alloc_get_allocated_count(struct binder_alloc *alloc); -void binder_alloc_print_allocated(struct seq_file *m, - struct binder_alloc *alloc); -void binder_alloc_print_pages(struct seq_file *m, - struct binder_alloc *alloc); - -/** - * binder_alloc_get_free_async_space() - get free space available for async - * @alloc: binder_alloc for this proc - * - * Return: the bytes remaining in the address-space for async transactions - */ -static inline size_t -binder_alloc_get_free_async_space(struct binder_alloc *alloc) -{ - guard(mutex)(&alloc->mutex); - return alloc->free_async_space; -} - -unsigned long -binder_alloc_copy_user_to_buffer(struct binder_alloc *alloc, - struct binder_buffer *buffer, - binder_size_t buffer_offset, - const void __user *from, - size_t bytes); - -int binder_alloc_copy_to_buffer(struct binder_alloc *alloc, - struct binder_buffer *buffer, - binder_size_t buffer_offset, - void *src, - size_t bytes); - -int binder_alloc_copy_from_buffer(struct binder_alloc *alloc, - void *dest, - struct binder_buffer *buffer, - binder_size_t buffer_offset, - size_t bytes); - -#if IS_ENABLED(CONFIG_KUNIT) -void __binder_alloc_init(struct binder_alloc *alloc, struct list_lru *freelist); -size_t binder_alloc_buffer_size(struct binder_alloc *alloc, - struct binder_buffer *buffer); -#endif - -#endif /* _LINUX_BINDER_ALLOC_H */ - diff --git a/drivers/android/binder_internal.h b/drivers/android/binder_internal.h deleted file mode 100644 index 342574bfd28a..000000000000 --- a/drivers/android/binder_internal.h +++ /dev/null @@ -1,597 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef _LINUX_BINDER_INTERNAL_H -#define _LINUX_BINDER_INTERNAL_H - -#include <linux/fs.h> -#include <linux/list.h> -#include <linux/miscdevice.h> -#include <linux/mutex.h> -#include <linux/refcount.h> -#include <linux/stddef.h> -#include <linux/types.h> -#include <linux/uidgid.h> -#include <uapi/linux/android/binderfs.h> -#include "binder_alloc.h" -#include "dbitmap.h" - -struct binder_context { - struct binder_node *binder_context_mgr_node; - struct mutex context_mgr_node_lock; - kuid_t binder_context_mgr_uid; - const char *name; -}; - -/** - * struct binder_device - information about a binder device node - * @hlist: list of binder devices - * @miscdev: information about a binder character device node - * @context: binder context information - * @binderfs_inode: This is the inode of the root dentry of the super block - * belonging to a binderfs mount. - */ -struct binder_device { - struct hlist_node hlist; - struct miscdevice miscdev; - struct binder_context context; - struct inode *binderfs_inode; - refcount_t ref; -}; - -/** - * binderfs_mount_opts - mount options for binderfs - * @max: maximum number of allocatable binderfs binder devices - * @stats_mode: enable binder stats in binderfs. - */ -struct binderfs_mount_opts { - int max; - int stats_mode; -}; - -/** - * binderfs_info - information about a binderfs mount - * @ipc_ns: The ipc namespace the binderfs mount belongs to. - * @control_dentry: This records the dentry of this binderfs mount - * binder-control device. - * @root_uid: uid that needs to be used when a new binder device is - * created. - * @root_gid: gid that needs to be used when a new binder device is - * created. - * @mount_opts: The mount options in use. - * @device_count: The current number of allocated binder devices. - * @proc_log_dir: Pointer to the directory dentry containing process-specific - * logs. - */ -struct binderfs_info { - struct ipc_namespace *ipc_ns; - struct dentry *control_dentry; - kuid_t root_uid; - kgid_t root_gid; - struct binderfs_mount_opts mount_opts; - int device_count; - struct dentry *proc_log_dir; -}; - -extern const struct file_operations binder_fops; - -extern char *binder_devices_param; - -#ifdef CONFIG_ANDROID_BINDERFS -extern bool is_binderfs_device(const struct inode *inode); -extern struct dentry *binderfs_create_file(struct dentry *dir, const char *name, - const struct file_operations *fops, - void *data); -#else -static inline bool is_binderfs_device(const struct inode *inode) -{ - return false; -} -static inline struct dentry *binderfs_create_file(struct dentry *dir, - const char *name, - const struct file_operations *fops, - void *data) -{ - return NULL; -} -#endif - -#ifdef CONFIG_ANDROID_BINDERFS -extern int __init init_binderfs(void); -#else -static inline int __init init_binderfs(void) -{ - return 0; -} -#endif - -struct binder_debugfs_entry { - const char *name; - umode_t mode; - const struct file_operations *fops; - void *data; -}; - -extern const struct binder_debugfs_entry binder_debugfs_entries[]; - -#define binder_for_each_debugfs_entry(entry) \ - for ((entry) = binder_debugfs_entries; \ - (entry)->name; \ - (entry)++) - -enum binder_stat_types { - BINDER_STAT_PROC, - BINDER_STAT_THREAD, - BINDER_STAT_NODE, - BINDER_STAT_REF, - BINDER_STAT_DEATH, - BINDER_STAT_TRANSACTION, - BINDER_STAT_TRANSACTION_COMPLETE, - BINDER_STAT_FREEZE, - BINDER_STAT_COUNT -}; - -struct binder_stats { - atomic_t br[_IOC_NR(BR_CLEAR_FREEZE_NOTIFICATION_DONE) + 1]; - atomic_t bc[_IOC_NR(BC_FREEZE_NOTIFICATION_DONE) + 1]; - atomic_t obj_created[BINDER_STAT_COUNT]; - atomic_t obj_deleted[BINDER_STAT_COUNT]; -}; - -/** - * struct binder_work - work enqueued on a worklist - * @entry: node enqueued on list - * @type: type of work to be performed - * - * There are separate work lists for proc, thread, and node (async). - */ -struct binder_work { - struct list_head entry; - - enum binder_work_type { - BINDER_WORK_TRANSACTION = 1, - BINDER_WORK_TRANSACTION_COMPLETE, - BINDER_WORK_TRANSACTION_PENDING, - BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT, - BINDER_WORK_RETURN_ERROR, - BINDER_WORK_NODE, - BINDER_WORK_DEAD_BINDER, - BINDER_WORK_DEAD_BINDER_AND_CLEAR, - BINDER_WORK_CLEAR_DEATH_NOTIFICATION, - BINDER_WORK_FROZEN_BINDER, - BINDER_WORK_CLEAR_FREEZE_NOTIFICATION, - } type; -}; - -struct binder_error { - struct binder_work work; - uint32_t cmd; -}; - -/** - * struct binder_node - binder node bookkeeping - * @debug_id: unique ID for debugging - * (invariant after initialized) - * @lock: lock for node fields - * @work: worklist element for node work - * (protected by @proc->inner_lock) - * @rb_node: element for proc->nodes tree - * (protected by @proc->inner_lock) - * @dead_node: element for binder_dead_nodes list - * (protected by binder_dead_nodes_lock) - * @proc: binder_proc that owns this node - * (invariant after initialized) - * @refs: list of references on this node - * (protected by @lock) - * @internal_strong_refs: used to take strong references when - * initiating a transaction - * (protected by @proc->inner_lock if @proc - * and by @lock) - * @local_weak_refs: weak user refs from local process - * (protected by @proc->inner_lock if @proc - * and by @lock) - * @local_strong_refs: strong user refs from local process - * (protected by @proc->inner_lock if @proc - * and by @lock) - * @tmp_refs: temporary kernel refs - * (protected by @proc->inner_lock while @proc - * is valid, and by binder_dead_nodes_lock - * if @proc is NULL. During inc/dec and node release - * it is also protected by @lock to provide safety - * as the node dies and @proc becomes NULL) - * @ptr: userspace pointer for node - * (invariant, no lock needed) - * @cookie: userspace cookie for node - * (invariant, no lock needed) - * @has_strong_ref: userspace notified of strong ref - * (protected by @proc->inner_lock if @proc - * and by @lock) - * @pending_strong_ref: userspace has acked notification of strong ref - * (protected by @proc->inner_lock if @proc - * and by @lock) - * @has_weak_ref: userspace notified of weak ref - * (protected by @proc->inner_lock if @proc - * and by @lock) - * @pending_weak_ref: userspace has acked notification of weak ref - * (protected by @proc->inner_lock if @proc - * and by @lock) - * @has_async_transaction: async transaction to node in progress - * (protected by @lock) - * @accept_fds: file descriptor operations supported for node - * (invariant after initialized) - * @min_priority: minimum scheduling priority - * (invariant after initialized) - * @txn_security_ctx: require sender's security context - * (invariant after initialized) - * @async_todo: list of async work items - * (protected by @proc->inner_lock) - * - * Bookkeeping structure for binder nodes. - */ -struct binder_node { - int debug_id; - spinlock_t lock; - struct binder_work work; - union { - struct rb_node rb_node; - struct hlist_node dead_node; - }; - struct binder_proc *proc; - struct hlist_head refs; - int internal_strong_refs; - int local_weak_refs; - int local_strong_refs; - int tmp_refs; - binder_uintptr_t ptr; - binder_uintptr_t cookie; - struct { - /* - * bitfield elements protected by - * proc inner_lock - */ - u8 has_strong_ref:1; - u8 pending_strong_ref:1; - u8 has_weak_ref:1; - u8 pending_weak_ref:1; - }; - struct { - /* - * invariant after initialization - */ - u8 accept_fds:1; - u8 txn_security_ctx:1; - u8 min_priority; - }; - bool has_async_transaction; - struct list_head async_todo; -}; - -struct binder_ref_death { - /** - * @work: worklist element for death notifications - * (protected by inner_lock of the proc that - * this ref belongs to) - */ - struct binder_work work; - binder_uintptr_t cookie; -}; - -struct binder_ref_freeze { - struct binder_work work; - binder_uintptr_t cookie; - bool is_frozen:1; - bool sent:1; - bool resend:1; -}; - -/** - * struct binder_ref_data - binder_ref counts and id - * @debug_id: unique ID for the ref - * @desc: unique userspace handle for ref - * @strong: strong ref count (debugging only if not locked) - * @weak: weak ref count (debugging only if not locked) - * - * Structure to hold ref count and ref id information. Since - * the actual ref can only be accessed with a lock, this structure - * is used to return information about the ref to callers of - * ref inc/dec functions. - */ -struct binder_ref_data { - int debug_id; - uint32_t desc; - int strong; - int weak; -}; - -/** - * struct binder_ref - struct to track references on nodes - * @data: binder_ref_data containing id, handle, and current refcounts - * @rb_node_desc: node for lookup by @data.desc in proc's rb_tree - * @rb_node_node: node for lookup by @node in proc's rb_tree - * @node_entry: list entry for node->refs list in target node - * (protected by @node->lock) - * @proc: binder_proc containing ref - * @node: binder_node of target node. When cleaning up a - * ref for deletion in binder_cleanup_ref, a non-NULL - * @node indicates the node must be freed - * @death: pointer to death notification (ref_death) if requested - * (protected by @node->lock) - * @freeze: pointer to freeze notification (ref_freeze) if requested - * (protected by @node->lock) - * - * Structure to track references from procA to target node (on procB). This - * structure is unsafe to access without holding @proc->outer_lock. - */ -struct binder_ref { - /* Lookups needed: */ - /* node + proc => ref (transaction) */ - /* desc + proc => ref (transaction, inc/dec ref) */ - /* node => refs + procs (proc exit) */ - struct binder_ref_data data; - struct rb_node rb_node_desc; - struct rb_node rb_node_node; - struct hlist_node node_entry; - struct binder_proc *proc; - struct binder_node *node; - struct binder_ref_death *death; - struct binder_ref_freeze *freeze; -}; - -/** - * struct binder_proc - binder process bookkeeping - * @proc_node: element for binder_procs list - * @threads: rbtree of binder_threads in this proc - * (protected by @inner_lock) - * @nodes: rbtree of binder nodes associated with - * this proc ordered by node->ptr - * (protected by @inner_lock) - * @refs_by_desc: rbtree of refs ordered by ref->desc - * (protected by @outer_lock) - * @refs_by_node: rbtree of refs ordered by ref->node - * (protected by @outer_lock) - * @waiting_threads: threads currently waiting for proc work - * (protected by @inner_lock) - * @pid PID of group_leader of process - * (invariant after initialized) - * @tsk task_struct for group_leader of process - * (invariant after initialized) - * @cred struct cred associated with the `struct file` - * in binder_open() - * (invariant after initialized) - * @deferred_work_node: element for binder_deferred_list - * (protected by binder_deferred_lock) - * @deferred_work: bitmap of deferred work to perform - * (protected by binder_deferred_lock) - * @outstanding_txns: number of transactions to be transmitted before - * processes in freeze_wait are woken up - * (protected by @inner_lock) - * @is_dead: process is dead and awaiting free - * when outstanding transactions are cleaned up - * (protected by @inner_lock) - * @is_frozen: process is frozen and unable to service - * binder transactions - * (protected by @inner_lock) - * @sync_recv: process received sync transactions since last frozen - * bit 0: received sync transaction after being frozen - * bit 1: new pending sync transaction during freezing - * (protected by @inner_lock) - * @async_recv: process received async transactions since last frozen - * (protected by @inner_lock) - * @freeze_wait: waitqueue of processes waiting for all outstanding - * transactions to be processed - * (protected by @inner_lock) - * @dmap dbitmap to manage available reference descriptors - * (protected by @outer_lock) - * @todo: list of work for this process - * (protected by @inner_lock) - * @stats: per-process binder statistics - * (atomics, no lock needed) - * @delivered_death: list of delivered death notification - * (protected by @inner_lock) - * @delivered_freeze: list of delivered freeze notification - * (protected by @inner_lock) - * @max_threads: cap on number of binder threads - * (protected by @inner_lock) - * @requested_threads: number of binder threads requested but not - * yet started. In current implementation, can - * only be 0 or 1. - * (protected by @inner_lock) - * @requested_threads_started: number binder threads started - * (protected by @inner_lock) - * @tmp_ref: temporary reference to indicate proc is in use - * (protected by @inner_lock) - * @default_priority: default scheduler priority - * (invariant after initialized) - * @debugfs_entry: debugfs node - * @alloc: binder allocator bookkeeping - * @context: binder_context for this proc - * (invariant after initialized) - * @inner_lock: can nest under outer_lock and/or node lock - * @outer_lock: no nesting under innor or node lock - * Lock order: 1) outer, 2) node, 3) inner - * @binderfs_entry: process-specific binderfs log file - * @oneway_spam_detection_enabled: process enabled oneway spam detection - * or not - * - * Bookkeeping structure for binder processes - */ -struct binder_proc { - struct hlist_node proc_node; - struct rb_root threads; - struct rb_root nodes; - struct rb_root refs_by_desc; - struct rb_root refs_by_node; - struct list_head waiting_threads; - int pid; - struct task_struct *tsk; - const struct cred *cred; - struct hlist_node deferred_work_node; - int deferred_work; - int outstanding_txns; - bool is_dead; - bool is_frozen; - bool sync_recv; - bool async_recv; - wait_queue_head_t freeze_wait; - struct dbitmap dmap; - struct list_head todo; - struct binder_stats stats; - struct list_head delivered_death; - struct list_head delivered_freeze; - u32 max_threads; - int requested_threads; - int requested_threads_started; - int tmp_ref; - long default_priority; - struct dentry *debugfs_entry; - struct binder_alloc alloc; - struct binder_context *context; - spinlock_t inner_lock; - spinlock_t outer_lock; - struct dentry *binderfs_entry; - bool oneway_spam_detection_enabled; -}; - -/** - * struct binder_thread - binder thread bookkeeping - * @proc: binder process for this thread - * (invariant after initialization) - * @rb_node: element for proc->threads rbtree - * (protected by @proc->inner_lock) - * @waiting_thread_node: element for @proc->waiting_threads list - * (protected by @proc->inner_lock) - * @pid: PID for this thread - * (invariant after initialization) - * @looper: bitmap of looping state - * (only accessed by this thread) - * @looper_needs_return: looping thread needs to exit driver - * (no lock needed) - * @transaction_stack: stack of in-progress transactions for this thread - * (protected by @proc->inner_lock) - * @todo: list of work to do for this thread - * (protected by @proc->inner_lock) - * @process_todo: whether work in @todo should be processed - * (protected by @proc->inner_lock) - * @return_error: transaction errors reported by this thread - * (only accessed by this thread) - * @reply_error: transaction errors reported by target thread - * (protected by @proc->inner_lock) - * @ee: extended error information from this thread - * (protected by @proc->inner_lock) - * @wait: wait queue for thread work - * @stats: per-thread statistics - * (atomics, no lock needed) - * @tmp_ref: temporary reference to indicate thread is in use - * (atomic since @proc->inner_lock cannot - * always be acquired) - * @is_dead: thread is dead and awaiting free - * when outstanding transactions are cleaned up - * (protected by @proc->inner_lock) - * - * Bookkeeping structure for binder threads. - */ -struct binder_thread { - struct binder_proc *proc; - struct rb_node rb_node; - struct list_head waiting_thread_node; - int pid; - int looper; /* only modified by this thread */ - bool looper_need_return; /* can be written by other thread */ - struct binder_transaction *transaction_stack; - struct list_head todo; - bool process_todo; - struct binder_error return_error; - struct binder_error reply_error; - struct binder_extended_error ee; - wait_queue_head_t wait; - struct binder_stats stats; - atomic_t tmp_ref; - bool is_dead; -}; - -/** - * struct binder_txn_fd_fixup - transaction fd fixup list element - * @fixup_entry: list entry - * @file: struct file to be associated with new fd - * @offset: offset in buffer data to this fixup - * @target_fd: fd to use by the target to install @file - * - * List element for fd fixups in a transaction. Since file - * descriptors need to be allocated in the context of the - * target process, we pass each fd to be processed in this - * struct. - */ -struct binder_txn_fd_fixup { - struct list_head fixup_entry; - struct file *file; - size_t offset; - int target_fd; -}; - -struct binder_transaction { - int debug_id; - struct binder_work work; - struct binder_thread *from; - pid_t from_pid; - pid_t from_tid; - struct binder_transaction *from_parent; - struct binder_proc *to_proc; - struct binder_thread *to_thread; - struct binder_transaction *to_parent; - unsigned is_async:1; - unsigned is_reply:1; - - struct binder_buffer *buffer; - unsigned int code; - unsigned int flags; - long priority; - long saved_priority; - kuid_t sender_euid; - ktime_t start_time; - struct list_head fd_fixups; - binder_uintptr_t security_ctx; - /** - * @lock: protects @from, @to_proc, and @to_thread - * - * @from, @to_proc, and @to_thread can be set to NULL - * during thread teardown - */ - spinlock_t lock; -}; - -/** - * struct binder_object - union of flat binder object types - * @hdr: generic object header - * @fbo: binder object (nodes and refs) - * @fdo: file descriptor object - * @bbo: binder buffer pointer - * @fdao: file descriptor array - * - * Used for type-independent object copies - */ -struct binder_object { - union { - struct binder_object_header hdr; - struct flat_binder_object fbo; - struct binder_fd_object fdo; - struct binder_buffer_object bbo; - struct binder_fd_array_object fdao; - }; -}; - -/** - * Add a binder device to binder_devices - * @device: the new binder device to add to the global list - */ -void binder_add_device(struct binder_device *device); - -/** - * Remove a binder device to binder_devices - * @device: the binder device to remove from the global list - */ -void binder_remove_device(struct binder_device *device); - -#if IS_ENABLED(CONFIG_KUNIT) -vm_fault_t binder_vm_fault(struct vm_fault *vmf); -#endif - -#endif /* _LINUX_BINDER_INTERNAL_H */ diff --git a/drivers/android/binder_netlink.c b/drivers/android/binder_netlink.c deleted file mode 100644 index 81e8432b5904..000000000000 --- a/drivers/android/binder_netlink.c +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) -/* Do not edit directly, auto-generated from: */ -/* Documentation/netlink/specs/binder.yaml */ -/* YNL-GEN kernel source */ -/* To regenerate run: tools/net/ynl/ynl-regen.sh */ - -#include <net/netlink.h> -#include <net/genetlink.h> - -#include "binder_netlink.h" - -#include <uapi/linux/android/binder_netlink.h> - -/* Ops table for binder */ -static const struct genl_split_ops binder_nl_ops[] = { -}; - -static const struct genl_multicast_group binder_nl_mcgrps[] = { - [BINDER_NLGRP_REPORT] = { "report", }, -}; - -struct genl_family binder_nl_family __ro_after_init = { - .name = BINDER_FAMILY_NAME, - .version = BINDER_FAMILY_VERSION, - .netnsok = true, - .parallel_ops = true, - .module = THIS_MODULE, - .split_ops = binder_nl_ops, - .n_split_ops = ARRAY_SIZE(binder_nl_ops), - .mcgrps = binder_nl_mcgrps, - .n_mcgrps = ARRAY_SIZE(binder_nl_mcgrps), -}; diff --git a/drivers/android/binder_netlink.h b/drivers/android/binder_netlink.h deleted file mode 100644 index 57399942a5e3..000000000000 --- a/drivers/android/binder_netlink.h +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ -/* Do not edit directly, auto-generated from: */ -/* Documentation/netlink/specs/binder.yaml */ -/* YNL-GEN kernel header */ -/* To regenerate run: tools/net/ynl/ynl-regen.sh */ - -#ifndef _LINUX_BINDER_GEN_H -#define _LINUX_BINDER_GEN_H - -#include <net/netlink.h> -#include <net/genetlink.h> - -#include <uapi/linux/android/binder_netlink.h> - -enum { - BINDER_NLGRP_REPORT, -}; - -extern struct genl_family binder_nl_family; - -#endif /* _LINUX_BINDER_GEN_H */ diff --git a/drivers/android/binder_trace.h b/drivers/android/binder_trace.h deleted file mode 100644 index fa5eb61cf580..000000000000 --- a/drivers/android/binder_trace.h +++ /dev/null @@ -1,448 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2012 Google, Inc. - */ - -#undef TRACE_SYSTEM -#define TRACE_SYSTEM binder - -#if !defined(_BINDER_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) -#define _BINDER_TRACE_H - -#include <linux/tracepoint.h> - -struct binder_buffer; -struct binder_node; -struct binder_proc; -struct binder_alloc; -struct binder_ref_data; -struct binder_thread; -struct binder_transaction; - -TRACE_EVENT(binder_ioctl, - TP_PROTO(unsigned int cmd, unsigned long arg), - TP_ARGS(cmd, arg), - - TP_STRUCT__entry( - __field(unsigned int, cmd) - __field(unsigned long, arg) - ), - TP_fast_assign( - __entry->cmd = cmd; - __entry->arg = arg; - ), - TP_printk("cmd=0x%x arg=0x%lx", __entry->cmd, __entry->arg) -); - -DECLARE_EVENT_CLASS(binder_function_return_class, - TP_PROTO(int ret), - TP_ARGS(ret), - TP_STRUCT__entry( - __field(int, ret) - ), - TP_fast_assign( - __entry->ret = ret; - ), - TP_printk("ret=%d", __entry->ret) -); - -#define DEFINE_BINDER_FUNCTION_RETURN_EVENT(name) \ -DEFINE_EVENT(binder_function_return_class, name, \ - TP_PROTO(int ret), \ - TP_ARGS(ret)) - -DEFINE_BINDER_FUNCTION_RETURN_EVENT(binder_ioctl_done); -DEFINE_BINDER_FUNCTION_RETURN_EVENT(binder_write_done); -DEFINE_BINDER_FUNCTION_RETURN_EVENT(binder_read_done); - -TRACE_EVENT(binder_wait_for_work, - TP_PROTO(bool proc_work, bool transaction_stack, bool thread_todo), - TP_ARGS(proc_work, transaction_stack, thread_todo), - - TP_STRUCT__entry( - __field(bool, proc_work) - __field(bool, transaction_stack) - __field(bool, thread_todo) - ), - TP_fast_assign( - __entry->proc_work = proc_work; - __entry->transaction_stack = transaction_stack; - __entry->thread_todo = thread_todo; - ), - TP_printk("proc_work=%d transaction_stack=%d thread_todo=%d", - __entry->proc_work, __entry->transaction_stack, - __entry->thread_todo) -); - -TRACE_EVENT(binder_txn_latency_free, - TP_PROTO(struct binder_transaction *t, - int from_proc, int from_thread, - int to_proc, int to_thread), - TP_ARGS(t, from_proc, from_thread, to_proc, to_thread), - TP_STRUCT__entry( - __field(int, debug_id) - __field(int, from_proc) - __field(int, from_thread) - __field(int, to_proc) - __field(int, to_thread) - __field(unsigned int, code) - __field(unsigned int, flags) - ), - TP_fast_assign( - __entry->debug_id = t->debug_id; - __entry->from_proc = from_proc; - __entry->from_thread = from_thread; - __entry->to_proc = to_proc; - __entry->to_thread = to_thread; - __entry->code = t->code; - __entry->flags = t->flags; - ), - TP_printk("transaction=%d from %d:%d to %d:%d flags=0x%x code=0x%x", - __entry->debug_id, __entry->from_proc, __entry->from_thread, - __entry->to_proc, __entry->to_thread, __entry->code, - __entry->flags) -); - -TRACE_EVENT(binder_transaction, - TP_PROTO(bool reply, struct binder_transaction *t, - struct binder_node *target_node), - TP_ARGS(reply, t, target_node), - TP_STRUCT__entry( - __field(int, debug_id) - __field(int, target_node) - __field(int, to_proc) - __field(int, to_thread) - __field(int, reply) - __field(unsigned int, code) - __field(unsigned int, flags) - ), - TP_fast_assign( - __entry->debug_id = t->debug_id; - __entry->target_node = target_node ? target_node->debug_id : 0; - __entry->to_proc = t->to_proc->pid; - __entry->to_thread = t->to_thread ? t->to_thread->pid : 0; - __entry->reply = reply; - __entry->code = t->code; - __entry->flags = t->flags; - ), - TP_printk("transaction=%d dest_node=%d dest_proc=%d dest_thread=%d reply=%d flags=0x%x code=0x%x", - __entry->debug_id, __entry->target_node, - __entry->to_proc, __entry->to_thread, - __entry->reply, __entry->flags, __entry->code) -); - -TRACE_EVENT(binder_transaction_received, - TP_PROTO(struct binder_transaction *t), - TP_ARGS(t), - - TP_STRUCT__entry( - __field(int, debug_id) - ), - TP_fast_assign( - __entry->debug_id = t->debug_id; - ), - TP_printk("transaction=%d", __entry->debug_id) -); - -TRACE_EVENT(binder_transaction_node_to_ref, - TP_PROTO(struct binder_transaction *t, struct binder_node *node, - struct binder_ref_data *rdata), - TP_ARGS(t, node, rdata), - - TP_STRUCT__entry( - __field(int, debug_id) - __field(int, node_debug_id) - __field(binder_uintptr_t, node_ptr) - __field(int, ref_debug_id) - __field(uint32_t, ref_desc) - ), - TP_fast_assign( - __entry->debug_id = t->debug_id; - __entry->node_debug_id = node->debug_id; - __entry->node_ptr = node->ptr; - __entry->ref_debug_id = rdata->debug_id; - __entry->ref_desc = rdata->desc; - ), - TP_printk("transaction=%d node=%d src_ptr=0x%016llx ==> dest_ref=%d dest_desc=%d", - __entry->debug_id, __entry->node_debug_id, - (u64)__entry->node_ptr, - __entry->ref_debug_id, __entry->ref_desc) -); - -TRACE_EVENT(binder_transaction_ref_to_node, - TP_PROTO(struct binder_transaction *t, struct binder_node *node, - struct binder_ref_data *rdata), - TP_ARGS(t, node, rdata), - - TP_STRUCT__entry( - __field(int, debug_id) - __field(int, ref_debug_id) - __field(uint32_t, ref_desc) - __field(int, node_debug_id) - __field(binder_uintptr_t, node_ptr) - ), - TP_fast_assign( - __entry->debug_id = t->debug_id; - __entry->ref_debug_id = rdata->debug_id; - __entry->ref_desc = rdata->desc; - __entry->node_debug_id = node->debug_id; - __entry->node_ptr = node->ptr; - ), - TP_printk("transaction=%d node=%d src_ref=%d src_desc=%d ==> dest_ptr=0x%016llx", - __entry->debug_id, __entry->node_debug_id, - __entry->ref_debug_id, __entry->ref_desc, - (u64)__entry->node_ptr) -); - -TRACE_EVENT(binder_transaction_ref_to_ref, - TP_PROTO(struct binder_transaction *t, struct binder_node *node, - struct binder_ref_data *src_ref, - struct binder_ref_data *dest_ref), - TP_ARGS(t, node, src_ref, dest_ref), - - TP_STRUCT__entry( - __field(int, debug_id) - __field(int, node_debug_id) - __field(int, src_ref_debug_id) - __field(uint32_t, src_ref_desc) - __field(int, dest_ref_debug_id) - __field(uint32_t, dest_ref_desc) - ), - TP_fast_assign( - __entry->debug_id = t->debug_id; - __entry->node_debug_id = node->debug_id; - __entry->src_ref_debug_id = src_ref->debug_id; - __entry->src_ref_desc = src_ref->desc; - __entry->dest_ref_debug_id = dest_ref->debug_id; - __entry->dest_ref_desc = dest_ref->desc; - ), - TP_printk("transaction=%d node=%d src_ref=%d src_desc=%d ==> dest_ref=%d dest_desc=%d", - __entry->debug_id, __entry->node_debug_id, - __entry->src_ref_debug_id, __entry->src_ref_desc, - __entry->dest_ref_debug_id, __entry->dest_ref_desc) -); - -TRACE_EVENT(binder_transaction_fd_send, - TP_PROTO(struct binder_transaction *t, int fd, size_t offset), - TP_ARGS(t, fd, offset), - - TP_STRUCT__entry( - __field(int, debug_id) - __field(int, fd) - __field(size_t, offset) - ), - TP_fast_assign( - __entry->debug_id = t->debug_id; - __entry->fd = fd; - __entry->offset = offset; - ), - TP_printk("transaction=%d src_fd=%d offset=%zu", - __entry->debug_id, __entry->fd, __entry->offset) -); - -TRACE_EVENT(binder_transaction_fd_recv, - TP_PROTO(struct binder_transaction *t, int fd, size_t offset), - TP_ARGS(t, fd, offset), - - TP_STRUCT__entry( - __field(int, debug_id) - __field(int, fd) - __field(size_t, offset) - ), - TP_fast_assign( - __entry->debug_id = t->debug_id; - __entry->fd = fd; - __entry->offset = offset; - ), - TP_printk("transaction=%d dest_fd=%d offset=%zu", - __entry->debug_id, __entry->fd, __entry->offset) -); - -DECLARE_EVENT_CLASS(binder_buffer_class, - TP_PROTO(struct binder_buffer *buf), - TP_ARGS(buf), - TP_STRUCT__entry( - __field(int, debug_id) - __field(size_t, data_size) - __field(size_t, offsets_size) - __field(size_t, extra_buffers_size) - ), - TP_fast_assign( - __entry->debug_id = buf->debug_id; - __entry->data_size = buf->data_size; - __entry->offsets_size = buf->offsets_size; - __entry->extra_buffers_size = buf->extra_buffers_size; - ), - TP_printk("transaction=%d data_size=%zd offsets_size=%zd extra_buffers_size=%zd", - __entry->debug_id, __entry->data_size, __entry->offsets_size, - __entry->extra_buffers_size) -); - -DEFINE_EVENT(binder_buffer_class, binder_transaction_alloc_buf, - TP_PROTO(struct binder_buffer *buffer), - TP_ARGS(buffer)); - -DEFINE_EVENT(binder_buffer_class, binder_transaction_buffer_release, - TP_PROTO(struct binder_buffer *buffer), - TP_ARGS(buffer)); - -DEFINE_EVENT(binder_buffer_class, binder_transaction_failed_buffer_release, - TP_PROTO(struct binder_buffer *buffer), - TP_ARGS(buffer)); - -DEFINE_EVENT(binder_buffer_class, binder_transaction_update_buffer_release, - TP_PROTO(struct binder_buffer *buffer), - TP_ARGS(buffer)); - -TRACE_EVENT(binder_update_page_range, - TP_PROTO(struct binder_alloc *alloc, bool allocate, - unsigned long start, unsigned long end), - TP_ARGS(alloc, allocate, start, end), - TP_STRUCT__entry( - __field(int, proc) - __field(bool, allocate) - __field(size_t, offset) - __field(size_t, size) - ), - TP_fast_assign( - __entry->proc = alloc->pid; - __entry->allocate = allocate; - __entry->offset = start - alloc->vm_start; - __entry->size = end - start; - ), - TP_printk("proc=%d allocate=%d offset=%zu size=%zu", - __entry->proc, __entry->allocate, - __entry->offset, __entry->size) -); - -DECLARE_EVENT_CLASS(binder_lru_page_class, - TP_PROTO(const struct binder_alloc *alloc, size_t page_index), - TP_ARGS(alloc, page_index), - TP_STRUCT__entry( - __field(int, proc) - __field(size_t, page_index) - ), - TP_fast_assign( - __entry->proc = alloc->pid; - __entry->page_index = page_index; - ), - TP_printk("proc=%d page_index=%zu", - __entry->proc, __entry->page_index) -); - -DEFINE_EVENT(binder_lru_page_class, binder_alloc_lru_start, - TP_PROTO(const struct binder_alloc *alloc, size_t page_index), - TP_ARGS(alloc, page_index)); - -DEFINE_EVENT(binder_lru_page_class, binder_alloc_lru_end, - TP_PROTO(const struct binder_alloc *alloc, size_t page_index), - TP_ARGS(alloc, page_index)); - -DEFINE_EVENT(binder_lru_page_class, binder_free_lru_start, - TP_PROTO(const struct binder_alloc *alloc, size_t page_index), - TP_ARGS(alloc, page_index)); - -DEFINE_EVENT(binder_lru_page_class, binder_free_lru_end, - TP_PROTO(const struct binder_alloc *alloc, size_t page_index), - TP_ARGS(alloc, page_index)); - -DEFINE_EVENT(binder_lru_page_class, binder_alloc_page_start, - TP_PROTO(const struct binder_alloc *alloc, size_t page_index), - TP_ARGS(alloc, page_index)); - -DEFINE_EVENT(binder_lru_page_class, binder_alloc_page_end, - TP_PROTO(const struct binder_alloc *alloc, size_t page_index), - TP_ARGS(alloc, page_index)); - -DEFINE_EVENT(binder_lru_page_class, binder_unmap_user_start, - TP_PROTO(const struct binder_alloc *alloc, size_t page_index), - TP_ARGS(alloc, page_index)); - -DEFINE_EVENT(binder_lru_page_class, binder_unmap_user_end, - TP_PROTO(const struct binder_alloc *alloc, size_t page_index), - TP_ARGS(alloc, page_index)); - -DEFINE_EVENT(binder_lru_page_class, binder_unmap_kernel_start, - TP_PROTO(const struct binder_alloc *alloc, size_t page_index), - TP_ARGS(alloc, page_index)); - -DEFINE_EVENT(binder_lru_page_class, binder_unmap_kernel_end, - TP_PROTO(const struct binder_alloc *alloc, size_t page_index), - TP_ARGS(alloc, page_index)); - -TRACE_EVENT(binder_command, - TP_PROTO(uint32_t cmd), - TP_ARGS(cmd), - TP_STRUCT__entry( - __field(uint32_t, cmd) - ), - TP_fast_assign( - __entry->cmd = cmd; - ), - TP_printk("cmd=0x%x %s", - __entry->cmd, - _IOC_NR(__entry->cmd) < ARRAY_SIZE(binder_command_strings) ? - binder_command_strings[_IOC_NR(__entry->cmd)] : - "unknown") -); - -TRACE_EVENT(binder_return, - TP_PROTO(uint32_t cmd), - TP_ARGS(cmd), - TP_STRUCT__entry( - __field(uint32_t, cmd) - ), - TP_fast_assign( - __entry->cmd = cmd; - ), - TP_printk("cmd=0x%x %s", - __entry->cmd, - _IOC_NR(__entry->cmd) < ARRAY_SIZE(binder_return_strings) ? - binder_return_strings[_IOC_NR(__entry->cmd)] : - "unknown") -); - -TRACE_EVENT(binder_netlink_report, - TP_PROTO(const char *context, - struct binder_transaction *t, - u32 data_size, - u32 error), - TP_ARGS(context, t, data_size, error), - TP_STRUCT__entry( - __field(const char *, context) - __field(u32, error) - __field(int, from_pid) - __field(int, from_tid) - __field(int, to_pid) - __field(int, to_tid) - __field(bool, is_reply) - __field(unsigned int, flags) - __field(unsigned int, code) - __field(size_t, data_size) - ), - TP_fast_assign( - __entry->context = context; - __entry->error = error; - __entry->from_pid = t->from_pid; - __entry->from_tid = t->from_tid; - __entry->to_pid = t->to_proc ? t->to_proc->pid : 0; - __entry->to_tid = t->to_thread ? t->to_thread->pid : 0; - __entry->is_reply = t->is_reply; - __entry->flags = t->flags; - __entry->code = t->code; - __entry->data_size = data_size; - ), - TP_printk("from %d:%d to %d:%d context=%s error=%d is_reply=%d flags=0x%x code=0x%x size=%zu", - __entry->from_pid, __entry->from_tid, - __entry->to_pid, __entry->to_tid, - __entry->context, __entry->error, __entry->is_reply, - __entry->flags, __entry->code, __entry->data_size) -); - -#endif /* _BINDER_TRACE_H */ - -#undef TRACE_INCLUDE_PATH -#undef TRACE_INCLUDE_FILE -#define TRACE_INCLUDE_PATH . -#define TRACE_INCLUDE_FILE binder_trace -#include <trace/define_trace.h> diff --git a/drivers/android/binderfs.c b/drivers/android/binderfs.c deleted file mode 100644 index 1f0538767f95..000000000000 --- a/drivers/android/binderfs.c +++ /dev/null @@ -1,785 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#include <linux/compiler_types.h> -#include <linux/errno.h> -#include <linux/fs.h> -#include <linux/fsnotify.h> -#include <linux/gfp.h> -#include <linux/idr.h> -#include <linux/init.h> -#include <linux/ipc_namespace.h> -#include <linux/kdev_t.h> -#include <linux/kernel.h> -#include <linux/list.h> -#include <linux/namei.h> -#include <linux/magic.h> -#include <linux/major.h> -#include <linux/miscdevice.h> -#include <linux/module.h> -#include <linux/mutex.h> -#include <linux/mount.h> -#include <linux/fs_parser.h> -#include <linux/sched.h> -#include <linux/seq_file.h> -#include <linux/slab.h> -#include <linux/spinlock_types.h> -#include <linux/stddef.h> -#include <linux/string.h> -#include <linux/types.h> -#include <linux/uaccess.h> -#include <linux/user_namespace.h> -#include <linux/xarray.h> -#include <uapi/linux/android/binder.h> -#include <uapi/linux/android/binderfs.h> - -#include "binder_internal.h" - -#define FIRST_INODE 1 -#define SECOND_INODE 2 -#define INODE_OFFSET 3 -#define BINDERFS_MAX_MINOR (1U << MINORBITS) -/* Ensure that the initial ipc namespace always has devices available. */ -#define BINDERFS_MAX_MINOR_CAPPED (BINDERFS_MAX_MINOR - 4) - -static dev_t binderfs_dev; -static DEFINE_MUTEX(binderfs_minors_mutex); -static DEFINE_IDA(binderfs_minors); - -enum binderfs_param { - Opt_max, - Opt_stats_mode, -}; - -enum binderfs_stats_mode { - binderfs_stats_mode_unset, - binderfs_stats_mode_global, -}; - -struct binder_features { - bool oneway_spam_detection; - bool extended_error; - bool freeze_notification; - bool transaction_report; -}; - -static const struct constant_table binderfs_param_stats[] = { - { "global", binderfs_stats_mode_global }, - {} -}; - -static const struct fs_parameter_spec binderfs_fs_parameters[] = { - fsparam_u32("max", Opt_max), - fsparam_enum("stats", Opt_stats_mode, binderfs_param_stats), - {} -}; - -static struct binder_features binder_features = { - .oneway_spam_detection = true, - .extended_error = true, - .freeze_notification = true, - .transaction_report = true, -}; - -static inline struct binderfs_info *BINDERFS_SB(const struct super_block *sb) -{ - return sb->s_fs_info; -} - -bool is_binderfs_device(const struct inode *inode) -{ - if (inode->i_sb->s_magic == BINDERFS_SUPER_MAGIC) - return true; - - return false; -} - -/** - * binderfs_binder_device_create - allocate inode from super block of a - * binderfs mount - * @ref_inode: inode from which the super block will be taken - * @userp: buffer to copy information about new device for userspace to - * @req: struct binderfs_device as copied from userspace - * - * This function allocates a new binder_device and reserves a new minor - * number for it. - * Minor numbers are limited and tracked globally in binderfs_minors. The - * function will stash a struct binder_device for the specific binder - * device in i_private of the inode. - * It will go on to allocate a new inode from the super block of the - * filesystem mount, stash a struct binder_device in its i_private field - * and attach a dentry to that inode. - * - * Return: 0 on success, negative errno on failure - */ -static int binderfs_binder_device_create(struct inode *ref_inode, - struct binderfs_device __user *userp, - struct binderfs_device *req) -{ - int minor, ret; - struct dentry *dentry, *root; - struct binder_device *device; - char *name = NULL; - struct inode *inode = NULL; - struct super_block *sb = ref_inode->i_sb; - struct binderfs_info *info = sb->s_fs_info; -#if defined(CONFIG_IPC_NS) - bool use_reserve = (info->ipc_ns == &init_ipc_ns); -#else - bool use_reserve = true; -#endif - - /* Reserve new minor number for the new device. */ - mutex_lock(&binderfs_minors_mutex); - if (++info->device_count <= info->mount_opts.max) - minor = ida_alloc_max(&binderfs_minors, - use_reserve ? BINDERFS_MAX_MINOR - 1 : - BINDERFS_MAX_MINOR_CAPPED - 1, - GFP_KERNEL); - else - minor = -ENOSPC; - if (minor < 0) { - --info->device_count; - mutex_unlock(&binderfs_minors_mutex); - return minor; - } - mutex_unlock(&binderfs_minors_mutex); - - ret = -ENOMEM; - device = kzalloc_obj(*device); - if (!device) - goto err; - - inode = new_inode(sb); - if (!inode) - goto err; - - inode->i_ino = minor + INODE_OFFSET; - simple_inode_init_ts(inode); - init_special_inode(inode, S_IFCHR | 0600, - MKDEV(MAJOR(binderfs_dev), minor)); - inode->i_fop = &binder_fops; - inode->i_uid = info->root_uid; - inode->i_gid = info->root_gid; - - req->name[BINDERFS_MAX_NAME] = '\0'; /* NUL-terminate */ - name = kstrdup(req->name, GFP_KERNEL); - if (!name) - goto err; - - refcount_set(&device->ref, 1); - device->binderfs_inode = inode; - device->context.binder_context_mgr_uid = INVALID_UID; - device->context.name = name; - device->miscdev.name = name; - device->miscdev.minor = minor; - mutex_init(&device->context.context_mgr_node_lock); - - req->major = MAJOR(binderfs_dev); - req->minor = minor; - - if (userp && copy_to_user(userp, req, sizeof(*req))) { - ret = -EFAULT; - goto err; - } - - root = sb->s_root; - dentry = simple_start_creating(root, name); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto err; - } - inode->i_private = device; - binder_add_device(device); - d_make_persistent(dentry, inode); - fsnotify_create(root->d_inode, dentry); - simple_done_creating(dentry); - - return 0; - -err: - kfree(name); - kfree(device); - mutex_lock(&binderfs_minors_mutex); - --info->device_count; - ida_free(&binderfs_minors, minor); - mutex_unlock(&binderfs_minors_mutex); - iput(inode); - - return ret; -} - -/** - * binder_ctl_ioctl - handle binder device node allocation requests - * @file: The file pointer for the binder-control device node. - * @cmd: The ioctl command. - * @arg: The ioctl argument. - * - * The request handler for the binder-control device. All requests operate on - * the binderfs mount the binder-control device resides in: - * - BINDER_CTL_ADD - * Allocate a new binder device. - * - * Return: %0 on success, negative errno on failure. - */ -static long binder_ctl_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - int ret = -EINVAL; - struct inode *inode = file_inode(file); - struct binderfs_device __user *device = (struct binderfs_device __user *)arg; - struct binderfs_device device_req; - - switch (cmd) { - case BINDER_CTL_ADD: - ret = copy_from_user(&device_req, device, sizeof(device_req)); - if (ret) { - ret = -EFAULT; - break; - } - - ret = binderfs_binder_device_create(inode, device, &device_req); - break; - default: - break; - } - - return ret; -} - -static void binderfs_evict_inode(struct inode *inode) -{ - struct binder_device *device = inode->i_private; - struct binderfs_info *info = BINDERFS_SB(inode->i_sb); - - clear_inode(inode); - - if (!S_ISCHR(inode->i_mode) || !device) - return; - - mutex_lock(&binderfs_minors_mutex); - --info->device_count; - ida_free(&binderfs_minors, device->miscdev.minor); - mutex_unlock(&binderfs_minors_mutex); - - if (refcount_dec_and_test(&device->ref)) { - binder_remove_device(device); - kfree(device->context.name); - kfree(device); - } -} - -static int binderfs_fs_context_parse_param(struct fs_context *fc, - struct fs_parameter *param) -{ - int opt; - struct binderfs_mount_opts *ctx = fc->fs_private; - struct fs_parse_result result; - - opt = fs_parse(fc, binderfs_fs_parameters, param, &result); - if (opt < 0) - return opt; - - switch (opt) { - case Opt_max: - if (result.uint_32 > BINDERFS_MAX_MINOR) - return invalfc(fc, "Bad value for '%s'", param->key); - - ctx->max = result.uint_32; - break; - case Opt_stats_mode: - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - ctx->stats_mode = result.uint_32; - break; - default: - return invalfc(fc, "Unsupported parameter '%s'", param->key); - } - - return 0; -} - -static int binderfs_fs_context_reconfigure(struct fs_context *fc) -{ - struct binderfs_mount_opts *ctx = fc->fs_private; - struct binderfs_info *info = BINDERFS_SB(fc->root->d_sb); - - if (info->mount_opts.stats_mode != ctx->stats_mode) - return invalfc(fc, "Binderfs stats mode cannot be changed during a remount"); - - info->mount_opts.stats_mode = ctx->stats_mode; - info->mount_opts.max = ctx->max; - return 0; -} - -static int binderfs_show_options(struct seq_file *seq, struct dentry *root) -{ - struct binderfs_info *info = BINDERFS_SB(root->d_sb); - - if (info->mount_opts.max <= BINDERFS_MAX_MINOR) - seq_printf(seq, ",max=%d", info->mount_opts.max); - - switch (info->mount_opts.stats_mode) { - case binderfs_stats_mode_unset: - break; - case binderfs_stats_mode_global: - seq_printf(seq, ",stats=global"); - break; - } - - return 0; -} - -static const struct super_operations binderfs_super_ops = { - .evict_inode = binderfs_evict_inode, - .show_options = binderfs_show_options, - .statfs = simple_statfs, -}; - -static inline bool is_binderfs_control_device(const struct dentry *dentry) -{ - struct binderfs_info *info = dentry->d_sb->s_fs_info; - - return info->control_dentry == dentry; -} - -static int binderfs_rename(const struct mnt_idmap *idmap, - struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry, - unsigned int flags) -{ - if (is_binderfs_control_device(old_dentry) || - is_binderfs_control_device(new_dentry)) - return -EPERM; - - return simple_rename(idmap, old_dir, old_dentry, new_dir, - new_dentry, flags); -} - -static int binderfs_unlink(struct inode *dir, struct dentry *dentry) -{ - if (is_binderfs_control_device(dentry)) - return -EPERM; - - return simple_unlink(dir, dentry); -} - -static const struct file_operations binder_ctl_fops = { - .owner = THIS_MODULE, - .open = nonseekable_open, - .unlocked_ioctl = binder_ctl_ioctl, - .compat_ioctl = binder_ctl_ioctl, - .llseek = noop_llseek, -}; - -/** - * binderfs_binder_ctl_create - create a new binder-control device - * @sb: super block of the binderfs mount - * - * This function creates a new binder-control device node in the binderfs mount - * referred to by @sb. - * - * Return: 0 on success, negative errno on failure - */ -static int binderfs_binder_ctl_create(struct super_block *sb) -{ - int minor, ret; - struct dentry *dentry; - struct binder_device *device; - struct inode *inode = NULL; - struct dentry *root = sb->s_root; - struct binderfs_info *info = sb->s_fs_info; -#if defined(CONFIG_IPC_NS) - bool use_reserve = (info->ipc_ns == &init_ipc_ns); -#else - bool use_reserve = true; -#endif - - device = kzalloc_obj(*device); - if (!device) - return -ENOMEM; - - ret = -ENOMEM; - inode = new_inode(sb); - if (!inode) - goto out; - - /* Reserve a new minor number for the new device. */ - mutex_lock(&binderfs_minors_mutex); - minor = ida_alloc_max(&binderfs_minors, - use_reserve ? BINDERFS_MAX_MINOR - 1 : - BINDERFS_MAX_MINOR_CAPPED - 1, - GFP_KERNEL); - mutex_unlock(&binderfs_minors_mutex); - if (minor < 0) { - ret = minor; - goto out; - } - - inode->i_ino = SECOND_INODE; - simple_inode_init_ts(inode); - init_special_inode(inode, S_IFCHR | 0600, - MKDEV(MAJOR(binderfs_dev), minor)); - inode->i_fop = &binder_ctl_fops; - inode->i_uid = info->root_uid; - inode->i_gid = info->root_gid; - - refcount_set(&device->ref, 1); - device->binderfs_inode = inode; - device->miscdev.minor = minor; - - dentry = d_alloc_name(root, "binder-control"); - if (!dentry) - goto out; - - inode->i_private = device; - info->control_dentry = dentry; - d_make_persistent(dentry, inode); - dput(dentry); - - return 0; - -out: - kfree(device); - iput(inode); - - return ret; -} - -static const struct inode_operations binderfs_dir_inode_operations = { - .lookup = simple_lookup, - .rename = binderfs_rename, - .unlink = binderfs_unlink, -}; - -static struct inode *binderfs_make_inode(struct super_block *sb, int mode) -{ - struct inode *ret; - - ret = new_inode(sb); - if (ret) { - ret->i_ino = iunique(sb, BINDERFS_MAX_MINOR + INODE_OFFSET); - ret->i_mode = mode; - simple_inode_init_ts(ret); - } - return ret; -} - -struct dentry *binderfs_create_file(struct dentry *parent, const char *name, - const struct file_operations *fops, - void *data) -{ - struct dentry *dentry; - struct inode *new_inode, *parent_inode; - struct super_block *sb; - - parent_inode = d_inode(parent); - - dentry = simple_start_creating(parent, name); - if (IS_ERR(dentry)) - return dentry; - - sb = parent_inode->i_sb; - new_inode = binderfs_make_inode(sb, S_IFREG | 0444); - if (!new_inode) { - simple_done_creating(dentry); - return ERR_PTR(-ENOMEM); - } - - new_inode->i_fop = fops; - new_inode->i_private = data; - d_make_persistent(dentry, new_inode); - fsnotify_create(parent_inode, dentry); - simple_done_creating(dentry); - return dentry; // borrowed -} - -static struct dentry *binderfs_create_dir(struct dentry *parent, - const char *name) -{ - struct dentry *dentry; - struct inode *new_inode, *parent_inode; - struct super_block *sb; - - parent_inode = d_inode(parent); - - dentry = simple_start_creating(parent, name); - if (IS_ERR(dentry)) - return dentry; - - sb = parent_inode->i_sb; - new_inode = binderfs_make_inode(sb, S_IFDIR | 0755); - if (!new_inode) { - simple_done_creating(dentry); - return ERR_PTR(-ENOMEM); - } - - new_inode->i_fop = &simple_dir_operations; - new_inode->i_op = &simple_dir_inode_operations; - - set_nlink(new_inode, 2); - d_make_persistent(dentry, new_inode); - inc_nlink(parent_inode); - fsnotify_mkdir(parent_inode, dentry); - simple_done_creating(dentry); - return dentry; -} - -static int binder_features_show(struct seq_file *m, void *unused) -{ - bool *feature = m->private; - - seq_printf(m, "%d\n", *feature); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(binder_features); - -static int init_binder_features(struct super_block *sb) -{ - struct dentry *dentry, *dir; - - dir = binderfs_create_dir(sb->s_root, "features"); - if (IS_ERR(dir)) - return PTR_ERR(dir); - - dentry = binderfs_create_file(dir, "oneway_spam_detection", - &binder_features_fops, - &binder_features.oneway_spam_detection); - if (IS_ERR(dentry)) - return PTR_ERR(dentry); - - dentry = binderfs_create_file(dir, "extended_error", - &binder_features_fops, - &binder_features.extended_error); - if (IS_ERR(dentry)) - return PTR_ERR(dentry); - - dentry = binderfs_create_file(dir, "freeze_notification", - &binder_features_fops, - &binder_features.freeze_notification); - if (IS_ERR(dentry)) - return PTR_ERR(dentry); - - dentry = binderfs_create_file(dir, "transaction_report", - &binder_features_fops, - &binder_features.transaction_report); - if (IS_ERR(dentry)) - return PTR_ERR(dentry); - - return 0; -} - -static int init_binder_logs(struct super_block *sb) -{ - struct dentry *binder_logs_root_dir, *dentry, *proc_log_dir; - const struct binder_debugfs_entry *db_entry; - struct binderfs_info *info; - int ret = 0; - - binder_logs_root_dir = binderfs_create_dir(sb->s_root, - "binder_logs"); - if (IS_ERR(binder_logs_root_dir)) { - ret = PTR_ERR(binder_logs_root_dir); - goto out; - } - - binder_for_each_debugfs_entry(db_entry) { - dentry = binderfs_create_file(binder_logs_root_dir, - db_entry->name, - db_entry->fops, - db_entry->data); - if (IS_ERR(dentry)) { - ret = PTR_ERR(dentry); - goto out; - } - } - - proc_log_dir = binderfs_create_dir(binder_logs_root_dir, "proc"); - if (IS_ERR(proc_log_dir)) { - ret = PTR_ERR(proc_log_dir); - goto out; - } - info = sb->s_fs_info; - info->proc_log_dir = proc_log_dir; - -out: - return ret; -} - -static int binderfs_fill_super(struct super_block *sb, struct fs_context *fc) -{ - int ret; - struct binderfs_info *info; - struct binderfs_mount_opts *ctx = fc->fs_private; - struct inode *inode = NULL; - struct binderfs_device device_info = {}; - const char *name; - size_t len; - - sb->s_blocksize = PAGE_SIZE; - sb->s_blocksize_bits = PAGE_SHIFT; - - /* - * The binderfs filesystem can be mounted by userns root in a - * non-initial userns. By default such mounts have the SB_I_NODEV flag - * set in s_iflags to prevent security issues where userns root can - * just create random device nodes via mknod() since it owns the - * filesystem mount. But binderfs does not allow to create any files - * including devices nodes. The only way to create binder devices nodes - * is through the binder-control device which userns root is explicitly - * allowed to do. So removing the SB_I_NODEV flag from s_iflags is both - * necessary and safe. - */ - sb->s_iflags &= ~SB_I_NODEV; - sb->s_iflags |= SB_I_NOEXEC; - sb->s_magic = BINDERFS_SUPER_MAGIC; - sb->s_op = &binderfs_super_ops; - sb->s_time_gran = 1; - - sb->s_fs_info = kzalloc_obj(struct binderfs_info); - if (!sb->s_fs_info) - return -ENOMEM; - info = sb->s_fs_info; - - info->ipc_ns = get_ipc_ns(current->nsproxy->ipc_ns); - - info->root_gid = make_kgid(sb->s_user_ns, 0); - if (!gid_valid(info->root_gid)) - info->root_gid = GLOBAL_ROOT_GID; - info->root_uid = make_kuid(sb->s_user_ns, 0); - if (!uid_valid(info->root_uid)) - info->root_uid = GLOBAL_ROOT_UID; - info->mount_opts.max = ctx->max; - info->mount_opts.stats_mode = ctx->stats_mode; - - inode = new_inode(sb); - if (!inode) - return -ENOMEM; - - inode->i_ino = FIRST_INODE; - inode->i_fop = &simple_dir_operations; - inode->i_mode = S_IFDIR | 0755; - simple_inode_init_ts(inode); - inode->i_op = &binderfs_dir_inode_operations; - set_nlink(inode, 2); - - sb->s_root = d_make_root(inode); - if (!sb->s_root) - return -ENOMEM; - - ret = binderfs_binder_ctl_create(sb); - if (ret) - return ret; - - name = binder_devices_param; - for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) { - strscpy(device_info.name, name, len + 1); - ret = binderfs_binder_device_create(inode, NULL, &device_info); - if (ret) - return ret; - name += len; - if (*name == ',') - name++; - } - - ret = init_binder_features(sb); - if (ret) - return ret; - - if (info->mount_opts.stats_mode == binderfs_stats_mode_global) - return init_binder_logs(sb); - - return 0; -} - -static int binderfs_fs_context_get_tree(struct fs_context *fc) -{ - return get_tree_nodev(fc, binderfs_fill_super); -} - -static void binderfs_fs_context_free(struct fs_context *fc) -{ - struct binderfs_mount_opts *ctx = fc->fs_private; - - kfree(ctx); -} - -static const struct fs_context_operations binderfs_fs_context_ops = { - .free = binderfs_fs_context_free, - .get_tree = binderfs_fs_context_get_tree, - .parse_param = binderfs_fs_context_parse_param, - .reconfigure = binderfs_fs_context_reconfigure, -}; - -static int binderfs_init_fs_context(struct fs_context *fc) -{ - struct binderfs_mount_opts *ctx; - - ctx = kzalloc_obj(struct binderfs_mount_opts); - if (!ctx) - return -ENOMEM; - - ctx->max = BINDERFS_MAX_MINOR; - ctx->stats_mode = binderfs_stats_mode_unset; - - fc->fs_private = ctx; - fc->ops = &binderfs_fs_context_ops; - - return 0; -} - -static void binderfs_kill_super(struct super_block *sb) -{ - struct binderfs_info *info = sb->s_fs_info; - - /* - * During inode eviction struct binderfs_info is needed. - * So first wipe the super_block then free struct binderfs_info. - */ - kill_anon_super(sb); - - if (info && info->ipc_ns) - put_ipc_ns(info->ipc_ns); - - kfree(info); -} - -static struct file_system_type binder_fs_type = { - .name = "binder", - .init_fs_context = binderfs_init_fs_context, - .parameters = binderfs_fs_parameters, - .kill_sb = binderfs_kill_super, - .fs_flags = FS_USERNS_MOUNT, -}; - -int __init init_binderfs(void) -{ - int ret; - const char *name; - size_t len; - - /* Verify that the default binderfs device names are valid. */ - name = binder_devices_param; - for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) { - if (len > BINDERFS_MAX_NAME) - return -E2BIG; - name += len; - if (*name == ',') - name++; - } - - /* Allocate new major number for binderfs. */ - ret = alloc_chrdev_region(&binderfs_dev, 0, BINDERFS_MAX_MINOR, - "binder"); - if (ret) - return ret; - - ret = register_filesystem(&binder_fs_type); - if (ret) { - unregister_chrdev_region(binderfs_dev, BINDERFS_MAX_MINOR); - return ret; - } - - return ret; -} diff --git a/drivers/android/dbitmap.h b/drivers/android/dbitmap.h deleted file mode 100644 index c7299ce8b374..000000000000 --- a/drivers/android/dbitmap.h +++ /dev/null @@ -1,169 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright 2024 Google LLC - * - * dbitmap - dynamically sized bitmap library. - * - * Used by the binder driver to optimize the allocation of the smallest - * available descriptor ID. Each bit in the bitmap represents the state - * of an ID. - * - * A dbitmap can grow or shrink as needed. This part has been designed - * considering that users might need to briefly release their locks in - * order to allocate memory for the new bitmap. These operations then, - * are verified to determine if the grow or shrink is sill valid. - * - * This library does not provide protection against concurrent access - * by itself. Binder uses the proc->outer_lock for this purpose. - */ - -#ifndef _LINUX_DBITMAP_H -#define _LINUX_DBITMAP_H -#include <linux/bitmap.h> - -#define NBITS_MIN BITS_PER_TYPE(unsigned long) - -struct dbitmap { - unsigned int nbits; - unsigned long *map; -}; - -static inline int dbitmap_enabled(struct dbitmap *dmap) -{ - return !!dmap->nbits; -} - -static inline void dbitmap_free(struct dbitmap *dmap) -{ - dmap->nbits = 0; - kfree(dmap->map); - dmap->map = NULL; -} - -/* Returns the nbits that a dbitmap can shrink to, 0 if not possible. */ -static inline unsigned int dbitmap_shrink_nbits(struct dbitmap *dmap) -{ - unsigned int bit; - - if (dmap->nbits <= NBITS_MIN) - return 0; - - /* - * Determine if the bitmap can shrink based on the position of - * its last set bit. If the bit is within the first quarter of - * the bitmap then shrinking is possible. In this case, the - * bitmap should shrink to half its current size. - */ - bit = find_last_bit(dmap->map, dmap->nbits); - if (bit < (dmap->nbits >> 2)) - return dmap->nbits >> 1; - - /* find_last_bit() returns dmap->nbits when no bits are set. */ - if (bit == dmap->nbits) - return NBITS_MIN; - - return 0; -} - -/* Replace the internal bitmap with a new one of different size */ -static inline void -dbitmap_replace(struct dbitmap *dmap, unsigned long *new, unsigned int nbits) -{ - bitmap_copy(new, dmap->map, min(dmap->nbits, nbits)); - kfree(dmap->map); - dmap->map = new; - dmap->nbits = nbits; -} - -static inline void -dbitmap_shrink(struct dbitmap *dmap, unsigned long *new, unsigned int nbits) -{ - if (!new) - return; - - /* - * Verify that shrinking to @nbits is still possible. The @new - * bitmap might have been allocated without locks, so this call - * could now be outdated. In this case, free @new and move on. - */ - if (!dbitmap_enabled(dmap) || dbitmap_shrink_nbits(dmap) != nbits) { - kfree(new); - return; - } - - dbitmap_replace(dmap, new, nbits); -} - -/* Returns the nbits that a dbitmap can grow to. */ -static inline unsigned int dbitmap_grow_nbits(struct dbitmap *dmap) -{ - return dmap->nbits << 1; -} - -static inline void -dbitmap_grow(struct dbitmap *dmap, unsigned long *new, unsigned int nbits) -{ - /* - * Verify that growing to @nbits is still possible. The @new - * bitmap might have been allocated without locks, so this call - * could now be outdated. In this case, free @new and move on. - */ - if (!dbitmap_enabled(dmap) || nbits <= dmap->nbits) { - kfree(new); - return; - } - - /* - * Check for ENOMEM after confirming the grow operation is still - * required. This ensures we only disable the dbitmap when it's - * necessary. Once the dbitmap is disabled, binder will fallback - * to slow_desc_lookup_olocked(). - */ - if (!new) { - dbitmap_free(dmap); - return; - } - - dbitmap_replace(dmap, new, nbits); -} - -/* - * Finds and sets the next zero bit in the bitmap. Upon success @bit - * is populated with the index and 0 is returned. Otherwise, -ENOSPC - * is returned to indicate that a dbitmap_grow() is needed. - */ -static inline int -dbitmap_acquire_next_zero_bit(struct dbitmap *dmap, unsigned long offset, - unsigned long *bit) -{ - unsigned long n; - - n = find_next_zero_bit(dmap->map, dmap->nbits, offset); - if (n == dmap->nbits) - return -ENOSPC; - - *bit = n; - set_bit(n, dmap->map); - - return 0; -} - -static inline void -dbitmap_clear_bit(struct dbitmap *dmap, unsigned long bit) -{ - clear_bit(bit, dmap->map); -} - -static inline int dbitmap_init(struct dbitmap *dmap) -{ - dmap->map = bitmap_zalloc(NBITS_MIN, GFP_KERNEL); - if (!dmap->map) { - dmap->nbits = 0; - return -ENOMEM; - } - - dmap->nbits = NBITS_MIN; - - return 0; -} -#endif diff --git a/drivers/android/tests/.kunitconfig b/drivers/android/tests/.kunitconfig deleted file mode 100644 index 39b76bab9d9a..000000000000 --- a/drivers/android/tests/.kunitconfig +++ /dev/null @@ -1,7 +0,0 @@ -# -# Copyright 2025 Google LLC. -# - -CONFIG_KUNIT=y -CONFIG_ANDROID_BINDER_IPC=y -CONFIG_ANDROID_BINDER_ALLOC_KUNIT_TEST=y diff --git a/drivers/android/tests/Makefile b/drivers/android/tests/Makefile deleted file mode 100644 index 27268418eb03..000000000000 --- a/drivers/android/tests/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# Copyright 2025 Google LLC. -# - -obj-$(CONFIG_ANDROID_BINDER_ALLOC_KUNIT_TEST) += binder_alloc_kunit.o diff --git a/drivers/android/tests/binder_alloc_kunit.c b/drivers/android/tests/binder_alloc_kunit.c deleted file mode 100644 index 7f9cc003bbe3..000000000000 --- a/drivers/android/tests/binder_alloc_kunit.c +++ /dev/null @@ -1,572 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Test cases for binder allocator code. - * - * Copyright 2025 Google LLC. - * Author: Tiffany Yang <ynaffit@google.com> - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <kunit/test.h> -#include <linux/anon_inodes.h> -#include <linux/err.h> -#include <linux/file.h> -#include <linux/fs.h> -#include <linux/mm.h> -#include <linux/mman.h> -#include <linux/seq_buf.h> -#include <linux/sizes.h> - -#include "../binder_alloc.h" -#include "../binder_internal.h" - -MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); - -#define BINDER_MMAP_SIZE SZ_128K - -#define BUFFER_NUM 5 -#define BUFFER_MIN_SIZE (PAGE_SIZE / 8) - -#define FREESEQ_BUFLEN ((3 * BUFFER_NUM) + 1) - -#define ALIGN_TYPE_STRLEN (12) - -#define ALIGNMENTS_BUFLEN (((ALIGN_TYPE_STRLEN + 6) * BUFFER_NUM) + 1) - -#define PRINT_ALL_CASES (0) - -/* 5^5 alignment combinations * 2 places to share pages * 5! free sequences */ -#define TOTAL_EXHAUSTIVE_CASES (3125 * 2 * 120) - -/** - * enum buf_end_align_type - Page alignment of a buffer - * end with regard to the end of the previous buffer. - * - * In the pictures below, buf2 refers to the buffer we - * are aligning. buf1 refers to previous buffer by addr. - * Symbol [ means the start of a buffer, ] means the end - * of a buffer, and | means page boundaries. - */ -enum buf_end_align_type { - /** - * @SAME_PAGE_UNALIGNED: The end of this buffer is on - * the same page as the end of the previous buffer and - * is not page aligned. Examples: - * buf1 ][ buf2 ][ ... - * buf1 ]|[ buf2 ][ ... - */ - SAME_PAGE_UNALIGNED = 0, - /** - * @SAME_PAGE_ALIGNED: When the end of the previous buffer - * is not page aligned, the end of this buffer is on the - * same page as the end of the previous buffer and is page - * aligned. When the previous buffer is page aligned, the - * end of this buffer is aligned to the next page boundary. - * Examples: - * buf1 ][ buf2 ]| ... - * buf1 ]|[ buf2 ]| ... - */ - SAME_PAGE_ALIGNED, - /** - * @NEXT_PAGE_UNALIGNED: The end of this buffer is on - * the page next to the end of the previous buffer and - * is not page aligned. Examples: - * buf1 ][ buf2 | buf2 ][ ... - * buf1 ]|[ buf2 | buf2 ][ ... - */ - NEXT_PAGE_UNALIGNED, - /** - * @NEXT_PAGE_ALIGNED: The end of this buffer is on - * the page next to the end of the previous buffer and - * is page aligned. Examples: - * buf1 ][ buf2 | buf2 ]| ... - * buf1 ]|[ buf2 | buf2 ]| ... - */ - NEXT_PAGE_ALIGNED, - /** - * @NEXT_NEXT_UNALIGNED: The end of this buffer is on - * the page that follows the page after the end of the - * previous buffer and is not page aligned. Examples: - * buf1 ][ buf2 | buf2 | buf2 ][ ... - * buf1 ]|[ buf2 | buf2 | buf2 ][ ... - */ - NEXT_NEXT_UNALIGNED, - /** - * @LOOP_END: The number of enum values in &buf_end_align_type. - * It is used for controlling loop termination. - */ - LOOP_END, -}; - -static const char *const buf_end_align_type_strs[LOOP_END] = { - [SAME_PAGE_UNALIGNED] = "SP_UNALIGNED", - [SAME_PAGE_ALIGNED] = " SP_ALIGNED ", - [NEXT_PAGE_UNALIGNED] = "NP_UNALIGNED", - [NEXT_PAGE_ALIGNED] = " NP_ALIGNED ", - [NEXT_NEXT_UNALIGNED] = "NN_UNALIGNED", -}; - -struct binder_alloc_test_case_info { - char alignments[ALIGNMENTS_BUFLEN]; - struct seq_buf alignments_sb; - size_t *buffer_sizes; - int *free_sequence; - bool front_pages; -}; - -static void stringify_free_seq(struct kunit *test, int *seq, struct seq_buf *sb) -{ - int i; - - for (i = 0; i < BUFFER_NUM; i++) - seq_buf_printf(sb, "[%d]", seq[i]); - - KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(sb)); -} - -static void stringify_alignments(struct kunit *test, int *alignments, - struct seq_buf *sb) -{ - int i; - - for (i = 0; i < BUFFER_NUM; i++) - seq_buf_printf(sb, "[ %d:%s ]", i, - buf_end_align_type_strs[alignments[i]]); - - KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(sb)); -} - -static bool check_buffer_pages_allocated(struct kunit *test, - struct binder_alloc *alloc, - struct binder_buffer *buffer, - size_t size) -{ - unsigned long page_addr; - unsigned long end; - int page_index; - - end = PAGE_ALIGN(buffer->user_data + size); - page_addr = buffer->user_data; - for (; page_addr < end; page_addr += PAGE_SIZE) { - page_index = (page_addr - alloc->vm_start) / PAGE_SIZE; - if (!alloc->pages[page_index] || - !list_empty(page_to_lru(alloc->pages[page_index]))) { - kunit_err(test, "expect alloc but is %s at page index %d\n", - alloc->pages[page_index] ? - "lru" : "free", page_index); - return false; - } - } - return true; -} - -static unsigned long binder_alloc_test_alloc_buf(struct kunit *test, - struct binder_alloc *alloc, - struct binder_buffer *buffers[], - size_t *sizes, int *seq) -{ - unsigned long failures = 0; - int i; - - for (i = 0; i < BUFFER_NUM; i++) { - buffers[i] = binder_alloc_new_buf(alloc, sizes[i], 0, 0, 0); - if (IS_ERR(buffers[i]) || - !check_buffer_pages_allocated(test, alloc, buffers[i], sizes[i])) - failures++; - } - - return failures; -} - -static unsigned long binder_alloc_test_free_buf(struct kunit *test, - struct binder_alloc *alloc, - struct binder_buffer *buffers[], - size_t *sizes, int *seq, size_t end) -{ - unsigned long failures = 0; - int i; - - for (i = 0; i < BUFFER_NUM; i++) - binder_alloc_free_buf(alloc, buffers[seq[i]]); - - for (i = 0; i <= (end - 1) / PAGE_SIZE; i++) { - if (list_empty(page_to_lru(alloc->pages[i]))) { - kunit_err(test, "expect lru but is %s at page index %d\n", - alloc->pages[i] ? "alloc" : "free", i); - failures++; - } - } - - return failures; -} - -static unsigned long binder_alloc_test_free_page(struct kunit *test, - struct binder_alloc *alloc) -{ - unsigned long failures = 0; - unsigned long count; - int i; - - while ((count = list_lru_count(alloc->freelist))) { - list_lru_walk(alloc->freelist, binder_alloc_free_page, - NULL, count); - } - - for (i = 0; i < (alloc->buffer_size / PAGE_SIZE); i++) { - if (alloc->pages[i]) { - kunit_err(test, "expect free but is %s at page index %d\n", - list_empty(page_to_lru(alloc->pages[i])) ? - "alloc" : "lru", i); - failures++; - } - } - - return failures; -} - -/* Executes one full test run for the given test case. */ -static bool binder_alloc_test_alloc_free(struct kunit *test, - struct binder_alloc *alloc, - struct binder_alloc_test_case_info *tc, - size_t end) -{ - unsigned long pages = PAGE_ALIGN(end) / PAGE_SIZE; - struct binder_buffer *buffers[BUFFER_NUM]; - unsigned long failures; - bool failed = false; - - failures = binder_alloc_test_alloc_buf(test, alloc, buffers, - tc->buffer_sizes, - tc->free_sequence); - failed = failed || failures; - KUNIT_EXPECT_EQ_MSG(test, failures, 0, - "Initial allocation failed: %lu/%u buffers with errors", - failures, BUFFER_NUM); - - failures = binder_alloc_test_free_buf(test, alloc, buffers, - tc->buffer_sizes, - tc->free_sequence, end); - failed = failed || failures; - KUNIT_EXPECT_EQ_MSG(test, failures, 0, - "Initial buffers not freed correctly: %lu/%lu pages not on lru list", - failures, pages); - - /* Allocate from lru. */ - failures = binder_alloc_test_alloc_buf(test, alloc, buffers, - tc->buffer_sizes, - tc->free_sequence); - failed = failed || failures; - KUNIT_EXPECT_EQ_MSG(test, failures, 0, - "Reallocation failed: %lu/%u buffers with errors", - failures, BUFFER_NUM); - - failures = list_lru_count(alloc->freelist); - failed = failed || failures; - KUNIT_EXPECT_EQ_MSG(test, failures, 0, - "lru list should be empty after reallocation but still has %lu pages", - failures); - - failures = binder_alloc_test_free_buf(test, alloc, buffers, - tc->buffer_sizes, - tc->free_sequence, end); - failed = failed || failures; - KUNIT_EXPECT_EQ_MSG(test, failures, 0, - "Reallocated buffers not freed correctly: %lu/%lu pages not on lru list", - failures, pages); - - failures = binder_alloc_test_free_page(test, alloc); - failed = failed || failures; - KUNIT_EXPECT_EQ_MSG(test, failures, 0, - "Failed to clean up allocated pages: %lu/%lu pages still installed", - failures, (alloc->buffer_size / PAGE_SIZE)); - - return failed; -} - -static bool is_dup(int *seq, int index, int val) -{ - int i; - - for (i = 0; i < index; i++) { - if (seq[i] == val) - return true; - } - return false; -} - -/* Generate BUFFER_NUM factorial free orders. */ -static void permute_frees(struct kunit *test, struct binder_alloc *alloc, - struct binder_alloc_test_case_info *tc, - unsigned long *runs, unsigned long *failures, - int index, size_t end) -{ - bool case_failed; - int i; - - if (index == BUFFER_NUM) { - DECLARE_SEQ_BUF(freeseq_sb, FREESEQ_BUFLEN); - - case_failed = binder_alloc_test_alloc_free(test, alloc, tc, end); - *runs += 1; - *failures += case_failed; - - if (case_failed || PRINT_ALL_CASES) { - stringify_free_seq(test, tc->free_sequence, - &freeseq_sb); - kunit_err(test, "case %lu: [%s] | %s - %s - %s", *runs, - case_failed ? "FAILED" : "PASSED", - tc->front_pages ? "front" : "back ", - seq_buf_str(&tc->alignments_sb), - seq_buf_str(&freeseq_sb)); - } - - return; - } - for (i = 0; i < BUFFER_NUM; i++) { - if (is_dup(tc->free_sequence, index, i)) - continue; - tc->free_sequence[index] = i; - permute_frees(test, alloc, tc, runs, failures, index + 1, end); - } -} - -static void gen_buf_sizes(struct kunit *test, - struct binder_alloc *alloc, - struct binder_alloc_test_case_info *tc, - size_t *end_offset, unsigned long *runs, - unsigned long *failures) -{ - size_t last_offset, offset = 0; - size_t front_sizes[BUFFER_NUM]; - size_t back_sizes[BUFFER_NUM]; - int seq[BUFFER_NUM] = {0}; - int i; - - tc->free_sequence = seq; - for (i = 0; i < BUFFER_NUM; i++) { - last_offset = offset; - offset = end_offset[i]; - front_sizes[i] = offset - last_offset; - back_sizes[BUFFER_NUM - i - 1] = front_sizes[i]; - } - back_sizes[0] += alloc->buffer_size - end_offset[BUFFER_NUM - 1]; - - /* - * Buffers share the first or last few pages. - * Only BUFFER_NUM - 1 buffer sizes are adjustable since - * we need one giant buffer before getting to the last page. - */ - tc->front_pages = true; - tc->buffer_sizes = front_sizes; - permute_frees(test, alloc, tc, runs, failures, 0, - end_offset[BUFFER_NUM - 1]); - - tc->front_pages = false; - tc->buffer_sizes = back_sizes; - permute_frees(test, alloc, tc, runs, failures, 0, alloc->buffer_size); -} - -static void gen_buf_offsets(struct kunit *test, struct binder_alloc *alloc, - size_t *end_offset, int *alignments, - unsigned long *runs, unsigned long *failures, - int index) -{ - size_t end, prev; - int align; - - if (index == BUFFER_NUM) { - struct binder_alloc_test_case_info tc = {0}; - - seq_buf_init(&tc.alignments_sb, tc.alignments, - ALIGNMENTS_BUFLEN); - stringify_alignments(test, alignments, &tc.alignments_sb); - - gen_buf_sizes(test, alloc, &tc, end_offset, runs, failures); - return; - } - prev = index == 0 ? 0 : end_offset[index - 1]; - end = prev; - - BUILD_BUG_ON(BUFFER_MIN_SIZE * BUFFER_NUM >= PAGE_SIZE); - - for (align = SAME_PAGE_UNALIGNED; align < LOOP_END; align++) { - if (align % 2) - end = ALIGN(end, PAGE_SIZE); - else - end += BUFFER_MIN_SIZE; - end_offset[index] = end; - alignments[index] = align; - gen_buf_offsets(test, alloc, end_offset, alignments, runs, - failures, index + 1); - } -} - -struct binder_alloc_test { - struct binder_alloc alloc; - struct list_lru binder_test_freelist; - struct file *filp; - unsigned long mmap_uaddr; -}; - -static void binder_alloc_test_init_freelist(struct kunit *test) -{ - struct binder_alloc_test *priv = test->priv; - - KUNIT_EXPECT_PTR_EQ(test, priv->alloc.freelist, - &priv->binder_test_freelist); -} - -static void binder_alloc_test_mmap(struct kunit *test) -{ - struct binder_alloc_test *priv = test->priv; - struct binder_alloc *alloc = &priv->alloc; - struct binder_buffer *buf; - struct rb_node *n; - - KUNIT_EXPECT_EQ(test, alloc->mapped, true); - KUNIT_EXPECT_EQ(test, alloc->buffer_size, BINDER_MMAP_SIZE); - - n = rb_first(&alloc->allocated_buffers); - KUNIT_EXPECT_PTR_EQ(test, n, NULL); - - n = rb_first(&alloc->free_buffers); - buf = rb_entry(n, struct binder_buffer, rb_node); - KUNIT_EXPECT_EQ(test, binder_alloc_buffer_size(alloc, buf), - BINDER_MMAP_SIZE); - KUNIT_EXPECT_TRUE(test, list_is_last(&buf->entry, &alloc->buffers)); -} - -/** - * binder_alloc_exhaustive_test() - Exhaustively test alloc and free of buffer pages. - * @test: The test context object. - * - * Allocate BUFFER_NUM buffers to cover all page alignment cases, - * then free them in all orders possible. Check that pages are - * correctly allocated, put onto lru when buffers are freed, and - * are freed when binder_alloc_free_page() is called. - */ -static void binder_alloc_exhaustive_test(struct kunit *test) -{ - struct binder_alloc_test *priv = test->priv; - size_t end_offset[BUFFER_NUM]; - int alignments[BUFFER_NUM]; - unsigned long failures = 0; - unsigned long runs = 0; - - gen_buf_offsets(test, &priv->alloc, end_offset, alignments, &runs, - &failures, 0); - - KUNIT_EXPECT_EQ(test, runs, TOTAL_EXHAUSTIVE_CASES); - KUNIT_EXPECT_EQ(test, failures, 0); -} - -/* ===== End test cases ===== */ - -static void binder_alloc_test_vma_close(struct vm_area_struct *vma) -{ - struct binder_alloc *alloc = vma->vm_private_data; - - binder_alloc_vma_close(alloc); -} - -static const struct vm_operations_struct binder_alloc_test_vm_ops = { - .close = binder_alloc_test_vma_close, - .fault = binder_vm_fault, -}; - -static int binder_alloc_test_mmap_handler(struct file *filp, - struct vm_area_struct *vma) -{ - struct binder_alloc *alloc = filp->private_data; - - vm_flags_mod(vma, VM_DONTCOPY | VM_MIXEDMAP, VM_MAYWRITE); - - vma->vm_ops = &binder_alloc_test_vm_ops; - vma->vm_private_data = alloc; - - return binder_alloc_mmap_handler(alloc, vma); -} - -static const struct file_operations binder_alloc_test_fops = { - .mmap = binder_alloc_test_mmap_handler, -}; - -static int binder_alloc_test_init(struct kunit *test) -{ - struct binder_alloc_test *priv; - int ret; - - priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - test->priv = priv; - - ret = list_lru_init(&priv->binder_test_freelist); - if (ret) { - kunit_err(test, "Failed to initialize test freelist\n"); - return ret; - } - - /* __binder_alloc_init requires mm to be attached */ - ret = kunit_attach_mm(); - if (ret) { - kunit_err(test, "Failed to attach mm\n"); - return ret; - } - __binder_alloc_init(&priv->alloc, &priv->binder_test_freelist); - - priv->filp = anon_inode_getfile("binder_alloc_kunit", - &binder_alloc_test_fops, &priv->alloc, - O_RDWR | O_CLOEXEC); - if (IS_ERR_OR_NULL(priv->filp)) { - kunit_err(test, "Failed to open binder alloc test driver file\n"); - return priv->filp ? PTR_ERR(priv->filp) : -ENOMEM; - } - - priv->mmap_uaddr = kunit_vm_mmap(test, priv->filp, 0, BINDER_MMAP_SIZE, - PROT_READ, MAP_PRIVATE | MAP_NORESERVE, - 0); - if (!priv->mmap_uaddr) { - kunit_err(test, "Could not map the test's transaction memory\n"); - return -ENOMEM; - } - - return 0; -} - -static void binder_alloc_test_exit(struct kunit *test) -{ - struct binder_alloc_test *priv = test->priv; - - /* Close the backing file to make sure binder_alloc_vma_close runs */ - if (!IS_ERR_OR_NULL(priv->filp)) - fput(priv->filp); - - if (priv->alloc.mm) - binder_alloc_deferred_release(&priv->alloc); - - /* Make sure freelist is empty */ - KUNIT_EXPECT_EQ(test, list_lru_count(&priv->binder_test_freelist), 0); - list_lru_destroy(&priv->binder_test_freelist); -} - -static struct kunit_case binder_alloc_test_cases[] = { - KUNIT_CASE(binder_alloc_test_init_freelist), - KUNIT_CASE(binder_alloc_test_mmap), - KUNIT_CASE_SLOW(binder_alloc_exhaustive_test), - {} -}; - -static struct kunit_suite binder_alloc_test_suite = { - .name = "binder_alloc", - .test_cases = binder_alloc_test_cases, - .init = binder_alloc_test_init, - .exit = binder_alloc_test_exit, -}; - -kunit_test_suite(binder_alloc_test_suite); - -MODULE_AUTHOR("Tiffany Yang <ynaffit@google.com>"); -MODULE_DESCRIPTION("Binder Alloc KUnit tests"); -MODULE_LICENSE("GPL"); diff --git a/drivers/bus/mhi/ep/Kconfig b/drivers/bus/mhi/ep/Kconfig index 90ab3b040672..9edb81b39890 100644 --- a/drivers/bus/mhi/ep/Kconfig +++ b/drivers/bus/mhi/ep/Kconfig @@ -8,3 +8,5 @@ config MHI_BUS_EP MHI_BUS_EP implements the MHI protocol for the endpoint devices, such as SDX55 modem connected to the host machine over PCIe. + +source "drivers/bus/mhi/ep/clients/Kconfig" diff --git a/drivers/bus/mhi/ep/Makefile b/drivers/bus/mhi/ep/Makefile index aad85f180b70..ab36ef2a40ab 100644 --- a/drivers/bus/mhi/ep/Makefile +++ b/drivers/bus/mhi/ep/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_MHI_BUS_EP) += mhi_ep.o mhi_ep-y := main.o mmio.o ring.o sm.o +obj-y += clients/ diff --git a/drivers/bus/mhi/ep/clients/Kconfig b/drivers/bus/mhi/ep/clients/Kconfig new file mode 100644 index 000000000000..4cf27184058c --- /dev/null +++ b/drivers/bus/mhi/ep/clients/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 + +config MHI_BUS_EP_LOOPBACK + tristate "MHI Endpoint LOOPBACK client driver" + depends on MHI_BUS_EP + help + MHI Endpoint LOOPBACK client driver that binds to the MHI LOOPBACK + channel as defined in the MHI specification. The LOOPBACK channel is + implemented by MHI-based endpoint devices (modems, WLAN) in the field, + where the endpoint firmware echoes back whatever the host sends. + + This driver receives data on the uplink channel and echoes it back on + the downlink channel for testing the MHI endpoint data path. + + To compile this driver as a module, choose M here. The module + will be called mhi_ep_loopback. diff --git a/drivers/bus/mhi/ep/clients/Makefile b/drivers/bus/mhi/ep/clients/Makefile new file mode 100644 index 000000000000..71dc91cc63b0 --- /dev/null +++ b/drivers/bus/mhi/ep/clients/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_MHI_BUS_EP_LOOPBACK) += mhi_ep_loopback.o +mhi_ep_loopback-y += loopback.o diff --git a/drivers/bus/mhi/ep/clients/loopback.c b/drivers/bus/mhi/ep/clients/loopback.c new file mode 100644 index 000000000000..c7452428d0b7 --- /dev/null +++ b/drivers/bus/mhi/ep/clients/loopback.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/mhi_ep.h> +#include <linux/module.h> +#include <linux/string.h> + +struct mhi_ep_loopback { + struct workqueue_struct *wq; + struct mhi_ep_device *mdev; +}; + +struct mhi_ep_loopback_work { + struct mhi_ep_device *mdev; + struct work_struct work; + void *buf; + size_t len; +}; + +static void mhi_ep_loopback_work_handler(struct work_struct *work) +{ + struct mhi_ep_loopback_work *mhi_ep_lb_work = container_of(work, + struct mhi_ep_loopback_work, work); + int ret; + + ret = mhi_ep_queue_buf(mhi_ep_lb_work->mdev, mhi_ep_lb_work->buf, + mhi_ep_lb_work->len); + if (ret) { + dev_err(&mhi_ep_lb_work->mdev->dev, "Failed to queue buffer: %d\n", ret); + kfree(mhi_ep_lb_work->buf); + } + + kfree(mhi_ep_lb_work); +} + +static void mhi_ep_loopback_ul_callback(struct mhi_ep_device *mhi_dev, + struct mhi_result *mhi_res) +{ + struct mhi_ep_loopback *mhi_ep_lb = dev_get_drvdata(&mhi_dev->dev); + struct mhi_ep_loopback_work *mhi_ep_lb_work; + void *buf; + + if (!mhi_ep_lb) + return; + + if (!mhi_res->transaction_status) { + if (!mhi_res->bytes_xferd) + return; + + buf = kmemdup(mhi_res->buf_addr, mhi_res->bytes_xferd, GFP_KERNEL); + if (!buf) + return; + + mhi_ep_lb_work = kmalloc_obj(*mhi_ep_lb_work, GFP_KERNEL); + if (!mhi_ep_lb_work) { + kfree(buf); + return; + } + + INIT_WORK(&mhi_ep_lb_work->work, mhi_ep_loopback_work_handler); + mhi_ep_lb_work->mdev = mhi_dev; + mhi_ep_lb_work->buf = buf; + mhi_ep_lb_work->len = mhi_res->bytes_xferd; + + queue_work(mhi_ep_lb->wq, &mhi_ep_lb_work->work); + } +} + +static void mhi_ep_loopback_dl_callback(struct mhi_ep_device *mhi_dev, + struct mhi_result *mhi_res) +{ + kfree(mhi_res->buf_addr); +} + +static int mhi_ep_loopback_probe(struct mhi_ep_device *mhi_dev, const struct mhi_device_id *id) +{ + struct mhi_ep_loopback *mhi_ep_lb; + + mhi_ep_lb = devm_kzalloc(&mhi_dev->dev, sizeof(*mhi_ep_lb), GFP_KERNEL); + if (!mhi_ep_lb) + return -ENOMEM; + + mhi_ep_lb->wq = alloc_ordered_workqueue("mhi_ep_loopback", WQ_MEM_RECLAIM); + if (!mhi_ep_lb->wq) { + dev_err(&mhi_dev->dev, "Failed to create workqueue\n"); + return -ENOMEM; + } + + mhi_ep_lb->mdev = mhi_dev; + dev_set_drvdata(&mhi_dev->dev, mhi_ep_lb); + + return 0; +} + +static void mhi_ep_loopback_remove(struct mhi_ep_device *mhi_dev) +{ + struct mhi_ep_loopback *mhi_ep_lb = dev_get_drvdata(&mhi_dev->dev); + + destroy_workqueue(mhi_ep_lb->wq); +} + +static const struct mhi_device_id mhi_ep_loopback_id_table[] = { + { .chan = "LOOPBACK" }, + { } +}; +MODULE_DEVICE_TABLE(mhi, mhi_ep_loopback_id_table); + +static struct mhi_ep_driver mhi_ep_loopback_driver = { + .probe = mhi_ep_loopback_probe, + .remove = mhi_ep_loopback_remove, + .dl_xfer_cb = mhi_ep_loopback_dl_callback, + .ul_xfer_cb = mhi_ep_loopback_ul_callback, + .id_table = mhi_ep_loopback_id_table, + .driver = { + .name = "mhi_ep_loopback", + }, +}; + +module_mhi_ep_driver(mhi_ep_loopback_driver); + +MODULE_AUTHOR("Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>"); +MODULE_AUTHOR("Sumit Kumar <sumit.kumar@oss.qualcomm.com>"); +MODULE_DESCRIPTION("MHI Endpoint Loopback driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/bus/mhi/ep/main.c b/drivers/bus/mhi/ep/main.c index db7ee6547eef..4be20c368536 100644 --- a/drivers/bus/mhi/ep/main.c +++ b/drivers/bus/mhi/ep/main.c @@ -504,7 +504,7 @@ static int mhi_ep_process_ch_ring(struct mhi_ep_ring *ring) return 0; } -static void mhi_ep_skb_completion(struct mhi_ep_buf_info *buf_info) +static void mhi_ep_buf_completion(struct mhi_ep_buf_info *buf_info) { struct mhi_ep_device *mhi_dev = buf_info->mhi_dev; struct mhi_ep_cntrl *mhi_cntrl = mhi_dev->mhi_cntrl; @@ -534,20 +534,21 @@ static void mhi_ep_skb_completion(struct mhi_ep_buf_info *buf_info) } /* TODO: Handle partially formed TDs */ -int mhi_ep_queue_skb(struct mhi_ep_device *mhi_dev, struct sk_buff *skb) +static int mhi_ep_queue(struct mhi_ep_device *mhi_dev, void *buf, size_t len, + void *cb_buf) { struct mhi_ep_cntrl *mhi_cntrl = mhi_dev->mhi_cntrl; struct mhi_ep_chan *mhi_chan = mhi_dev->dl_chan; struct device *dev = &mhi_chan->mhi_dev->dev; struct mhi_ep_buf_info buf_info = {}; struct mhi_ring_element *el; - u32 buf_left, read_offset; + size_t buf_left, read_offset; struct mhi_ep_ring *ring; size_t tr_len; u32 tre_len; int ret; - buf_left = skb->len; + buf_left = len; ring = &mhi_cntrl->mhi_chan[mhi_chan->chan].ring; mutex_lock(&mhi_chan->lock); @@ -570,13 +571,13 @@ int mhi_ep_queue_skb(struct mhi_ep_device *mhi_dev, struct sk_buff *skb) tre_len = MHI_TRE_DATA_GET_LEN(el); tr_len = min(buf_left, tre_len); - read_offset = skb->len - buf_left; + read_offset = len - buf_left; - buf_info.dev_addr = skb->data + read_offset; + buf_info.dev_addr = buf + read_offset; buf_info.host_addr = MHI_TRE_DATA_GET_PTR(el); buf_info.size = tr_len; - buf_info.cb = mhi_ep_skb_completion; - buf_info.cb_buf = skb; + buf_info.cb = mhi_ep_buf_completion; + buf_info.cb_buf = cb_buf; buf_info.mhi_dev = mhi_dev; /* @@ -615,8 +616,19 @@ err_exit: return ret; } + +int mhi_ep_queue_skb(struct mhi_ep_device *mhi_dev, struct sk_buff *skb) +{ + return mhi_ep_queue(mhi_dev, skb->data, skb->len, skb); +} EXPORT_SYMBOL_GPL(mhi_ep_queue_skb); +int mhi_ep_queue_buf(struct mhi_ep_device *mhi_dev, void *buf, size_t len) +{ + return mhi_ep_queue(mhi_dev, buf, len, buf); +} +EXPORT_SYMBOL_GPL(mhi_ep_queue_buf); + static int mhi_ep_cache_host_cfg(struct mhi_ep_cntrl *mhi_cntrl) { size_t cmd_ctx_host_size, ch_ctx_host_size, ev_ctx_host_size; @@ -1534,8 +1546,8 @@ int mhi_ep_register_controller(struct mhi_ep_cntrl *mhi_cntrl, mhi_cntrl->index = ret; - irq_set_status_flags(mhi_cntrl->irq, IRQ_NOAUTOEN); - ret = request_irq(mhi_cntrl->irq, mhi_ep_irq, IRQF_TRIGGER_HIGH, + ret = request_irq(mhi_cntrl->irq, mhi_ep_irq, + IRQF_TRIGGER_HIGH | IRQF_NO_AUTOEN, "doorbell_irq", mhi_cntrl); if (ret) { dev_err(mhi_cntrl->cntrl_dev, "Failed to request Doorbell IRQ\n"); diff --git a/drivers/bus/mhi/host/Kconfig b/drivers/bus/mhi/host/Kconfig index da5cd0c9fc62..627c57948235 100644 --- a/drivers/bus/mhi/host/Kconfig +++ b/drivers/bus/mhi/host/Kconfig @@ -29,3 +29,4 @@ config MHI_BUS_PCI_GENERIC This driver provides MHI PCI controller driver for devices such as Qualcomm SDX55 based PCIe modems. +source "drivers/bus/mhi/host/clients/Kconfig" diff --git a/drivers/bus/mhi/host/Makefile b/drivers/bus/mhi/host/Makefile index 859c2f38451c..2a16008aeb38 100644 --- a/drivers/bus/mhi/host/Makefile +++ b/drivers/bus/mhi/host/Makefile @@ -4,3 +4,4 @@ mhi-$(CONFIG_MHI_BUS_DEBUG) += debugfs.o obj-$(CONFIG_MHI_BUS_PCI_GENERIC) += mhi_pci_generic.o mhi_pci_generic-y += pci_generic.o +obj-y += clients/ diff --git a/drivers/bus/mhi/host/clients/Kconfig b/drivers/bus/mhi/host/clients/Kconfig new file mode 100644 index 000000000000..8bb5715e61d0 --- /dev/null +++ b/drivers/bus/mhi/host/clients/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 + +config MHI_BUS_LOOPBACK + tristate "MHI LOOPBACK client driver" + depends on MHI_BUS + help + MHI LOOPBACK client driver that binds to the MHI LOOPBACK channel + as defined in the MHI specification. The LOOPBACK channel is + implemented by MHI-based devices (e.g. modems, WLAN) in the field, + where the endpoint firmware echoes back whatever the host sends. + + This driver exposes a sysfs interface for testing MHI data path + integrity between host and endpoint. Users can configure the TRE + size and count, and trigger a loopback test. + + To compile this driver as a module, choose M here. The module + will be called mhi_loopback. diff --git a/drivers/bus/mhi/host/clients/Makefile b/drivers/bus/mhi/host/clients/Makefile new file mode 100644 index 000000000000..3811b6928f42 --- /dev/null +++ b/drivers/bus/mhi/host/clients/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_MHI_BUS_LOOPBACK) += mhi_loopback.o +mhi_loopback-y += loopback.o diff --git a/drivers/bus/mhi/host/clients/loopback.c b/drivers/bus/mhi/host/clients/loopback.c new file mode 100644 index 000000000000..35312f025598 --- /dev/null +++ b/drivers/bus/mhi/host/clients/loopback.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/atomic.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/errno.h> +#include <linux/mhi.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/random.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/types.h> + +#define MHI_LOOPBACK_DEFAULT_TRE_SIZE 32 +#define MHI_LOOPBACK_DEFAULT_NUM_TRE 1 +#define MHI_LOOPBACK_TIMEOUT_MS 5000 +#define MHI_LOOPBACK_MAX_TRE_SIZE (SZ_64K - 1) + +struct mhi_loopback { + struct mhi_device *mdev; + /* Serializes the sysfs attributes against a running test */ + struct mutex lb_mutex; + struct completion comp; + /* Counts pending DL completions; comp fires when it hits zero */ + atomic_t tre_pending; + u32 num_tre; + u32 tre_size; +}; + +static ssize_t tre_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mhi_loopback *loopback = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%u\n", loopback->tre_size); +} + +static ssize_t tre_size_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mhi_loopback *loopback = dev_get_drvdata(dev); + u32 val; + + if (kstrtou32(buf, 0, &val)) + return -EINVAL; + + if (val == 0 || val > MHI_LOOPBACK_MAX_TRE_SIZE) + return -EINVAL; + + guard(mutex)(&loopback->lb_mutex); + loopback->tre_size = val; + + return count; +} +static DEVICE_ATTR_RW(tre_size); + +static ssize_t max_tre_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", MHI_LOOPBACK_MAX_TRE_SIZE); +} +static DEVICE_ATTR_RO(max_tre_size); + +static int mhi_loopback_ring_capacity(struct mhi_loopback *loopback) +{ + return min(mhi_get_free_desc_count(loopback->mdev, DMA_TO_DEVICE), + mhi_get_free_desc_count(loopback->mdev, DMA_FROM_DEVICE)); +} + +static ssize_t max_num_tre_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mhi_loopback *loopback = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", mhi_loopback_ring_capacity(loopback)); +} +static DEVICE_ATTR_RO(max_num_tre); + +static ssize_t num_tre_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mhi_loopback *loopback = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%u\n", loopback->num_tre); +} + +static ssize_t num_tre_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mhi_loopback *loopback = dev_get_drvdata(dev); + u32 val; + int el_num; + + if (kstrtou32(buf, 0, &val)) + return -EINVAL; + + if (val == 0) + return -EINVAL; + + guard(mutex)(&loopback->lb_mutex); + + el_num = mhi_loopback_ring_capacity(loopback); + if (val > el_num) { + dev_err(dev, "num_tre (%u) exceeds ring capacity (%d)\n", val, el_num); + return -EINVAL; + } + + loopback->num_tre = val; + + return count; +} +static DEVICE_ATTR_RW(num_tre); + +static void mhi_loopback_reset_chan(struct mhi_loopback *loopback) +{ + struct device *dev = &loopback->mdev->dev; + int ret; + + mhi_unprepare_from_transfer(loopback->mdev); + ret = mhi_prepare_for_transfer(loopback->mdev); + if (ret) + dev_err(dev, "Failed to re-prepare channel for transfers: %d\n", ret); +} + +static ssize_t start_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mhi_loopback *loopback = dev_get_drvdata(dev); + u32 total_size, tre_count, tre_size; + int i, ret; + + guard(mutex)(&loopback->lb_mutex); + + tre_size = loopback->tre_size; + tre_count = loopback->num_tre; + total_size = size_mul(tre_count, tre_size); + + if (total_size > KMALLOC_MAX_SIZE) + return -EINVAL; + + if (tre_count > mhi_loopback_ring_capacity(loopback)) { + dev_err(dev, "Not enough ring space for %u TREs\n", tre_count); + return -ENOSPC; + } + + void *recv_buf __free(kfree) = kzalloc(total_size, GFP_KERNEL); + if (!recv_buf) + return -ENOMEM; + + void *send_buf __free(kfree) = kzalloc(total_size, GFP_KERNEL); + if (!send_buf) + return -ENOMEM; + + get_random_bytes(send_buf, total_size); + + atomic_set(&loopback->tre_pending, tre_count); + reinit_completion(&loopback->comp); + + for (i = 0; i < tre_count; i++) { + ret = mhi_queue_buf(loopback->mdev, DMA_FROM_DEVICE, + recv_buf + (i * tre_size), tre_size, MHI_EOT); + if (ret) { + dev_err(dev, "Unable to queue read TRE %d: %d\n", i, ret); + /* + * TREs already queued for this iteration still point at + * recv_buf, which is about to be freed. Reset the channel + * to reclaim them before returning. + */ + mhi_loopback_reset_chan(loopback); + return ret; + } + } + + for (i = 0; i < tre_count - 1; i++) { + ret = mhi_queue_buf(loopback->mdev, DMA_TO_DEVICE, + send_buf + (i * tre_size), tre_size, MHI_CHAIN); + if (ret) { + dev_err(dev, "Unable to queue send TRE %d: %d\n", i, ret); + /* Reclaim the queued DL and UL TREs before recv_buf/send_buf are freed */ + mhi_loopback_reset_chan(loopback); + return ret; + } + } + + ret = mhi_queue_buf(loopback->mdev, DMA_TO_DEVICE, + send_buf + (i * tre_size), tre_size, MHI_EOT); + if (ret) { + dev_err(dev, "Unable to queue final TRE: %d\n", ret); + /* Reclaim the queued DL and UL TREs before recv_buf/send_buf are freed */ + mhi_loopback_reset_chan(loopback); + return ret; + } + + if (!wait_for_completion_timeout(&loopback->comp, + msecs_to_jiffies(MHI_LOOPBACK_TIMEOUT_MS))) { + dev_err(dev, "Loopback test timed out\n"); + mhi_loopback_reset_chan(loopback); + return -ETIMEDOUT; + } + + if (memcmp(send_buf, recv_buf, total_size)) { + dev_err(dev, "Loopback data mismatch\n"); + return -EIO; + } + + return count; +} +static DEVICE_ATTR_WO(start); + +static void mhi_loopback_dl_callback(struct mhi_device *mhi_dev, + struct mhi_result *mhi_res) +{ + struct mhi_loopback *loopback = dev_get_drvdata(&mhi_dev->dev); + + if (mhi_res->transaction_status && mhi_res->transaction_status != -ENOTCONN) + dev_err(&mhi_dev->dev, "DL callback error: status %d\n", + mhi_res->transaction_status); + + if (atomic_dec_and_test(&loopback->tre_pending)) + complete(&loopback->comp); +} + +static void mhi_loopback_ul_callback(struct mhi_device *mhi_dev, + struct mhi_result *mhi_res) +{ + if (mhi_res->transaction_status && mhi_res->transaction_status != -ENOTCONN) + dev_err(&mhi_dev->dev, "UL callback error: status %d\n", + mhi_res->transaction_status); +} + +static struct attribute *mhi_loopback_attrs[] = { + &dev_attr_tre_size.attr, + &dev_attr_max_tre_size.attr, + &dev_attr_num_tre.attr, + &dev_attr_max_num_tre.attr, + &dev_attr_start.attr, + NULL +}; + +static const struct attribute_group mhi_loopback_group = { + .attrs = mhi_loopback_attrs, +}; + +static const struct attribute_group *mhi_loopback_groups[] = { + &mhi_loopback_group, + NULL, +}; + +static int mhi_loopback_probe(struct mhi_device *mhi_dev, + const struct mhi_device_id *id) +{ + struct mhi_loopback *loopback; + int ret; + + loopback = devm_kzalloc(&mhi_dev->dev, sizeof(*loopback), GFP_KERNEL); + if (!loopback) + return -ENOMEM; + + loopback->mdev = mhi_dev; + loopback->tre_size = MHI_LOOPBACK_DEFAULT_TRE_SIZE; + loopback->num_tre = MHI_LOOPBACK_DEFAULT_NUM_TRE; + + mutex_init(&loopback->lb_mutex); + init_completion(&loopback->comp); + + dev_set_drvdata(&mhi_dev->dev, loopback); + + ret = mhi_prepare_for_transfer(mhi_dev); + if (ret) + return dev_err_probe(&mhi_dev->dev, ret, "Failed to prepare for transfers\n"); + + return 0; +} + +static void mhi_loopback_remove(struct mhi_device *mhi_dev) +{ + mhi_unprepare_from_transfer(mhi_dev); +} + +static const struct mhi_device_id mhi_loopback_id_table[] = { + { .chan = "LOOPBACK" }, + { } +}; +MODULE_DEVICE_TABLE(mhi, mhi_loopback_id_table); + +static struct mhi_driver mhi_loopback_driver = { + .probe = mhi_loopback_probe, + .remove = mhi_loopback_remove, + .dl_xfer_cb = mhi_loopback_dl_callback, + .ul_xfer_cb = mhi_loopback_ul_callback, + .id_table = mhi_loopback_id_table, + .driver = { + .name = "mhi_loopback", + .dev_groups = mhi_loopback_groups, + }, +}; + +module_mhi_driver(mhi_loopback_driver); + +MODULE_AUTHOR("Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>"); +MODULE_AUTHOR("Sumit Kumar <sumit.kumar@oss.qualcomm.com>"); +MODULE_DESCRIPTION("MHI Host Loopback Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/bus/mhi/host/debugfs.c b/drivers/bus/mhi/host/debugfs.c index 39e45748a24c..93f458a2557d 100644 --- a/drivers/bus/mhi/host/debugfs.c +++ b/drivers/bus/mhi/host/debugfs.c @@ -72,7 +72,7 @@ static int mhi_debugfs_events_show(struct seq_file *m, void *d) seq_printf(m, " rp: 0x%llx wp: 0x%llx", le64_to_cpu(er_ctxt->rp), le64_to_cpu(er_ctxt->wp)); - seq_printf(m, " local rp: 0x%pK db: 0x%pad\n", ring->rp, + seq_printf(m, " local rp: 0x%p db: 0x%pad\n", ring->rp, &mhi_event->db_cfg.db_val); } @@ -120,7 +120,7 @@ static int mhi_debugfs_channels_show(struct seq_file *m, void *d) le64_to_cpu(chan_ctxt->rbase), le64_to_cpu(chan_ctxt->rlen), le64_to_cpu(chan_ctxt->rp), le64_to_cpu(chan_ctxt->wp)); - seq_printf(m, " local rp: 0x%pK local wp: 0x%pK db: 0x%pad\n", + seq_printf(m, " local rp: 0x%p local wp: 0x%p db: 0x%pad\n", ring->rp, ring->wp, &mhi_chan->db_cfg.db_val); } diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c index b636e2c23b4d..7193a384cca4 100644 --- a/drivers/bus/mhi/host/pci_generic.c +++ b/drivers/bus/mhi/host/pci_generic.c @@ -322,6 +322,8 @@ static const struct mhi_channel_config modem_qcom_v1_mhi_channels[] = { MHI_CHANNEL_CONFIG_DL(15, "QMI", 4, 0), MHI_CHANNEL_CONFIG_UL(20, "IPCR", 8, 0), MHI_CHANNEL_CONFIG_DL(21, "IPCR", 8, 0), + MHI_CHANNEL_CONFIG_UL(32, "DUN", 32, 0), + MHI_CHANNEL_CONFIG_DL(33, "DUN", 32, 0), MHI_CHANNEL_CONFIG_UL_FP(34, "FIREHOSE", 32, 0), MHI_CHANNEL_CONFIG_DL_FP(35, "FIREHOSE", 32, 0), MHI_CHANNEL_CONFIG_UL(46, "IP_SW0", 64, 2), @@ -488,6 +490,16 @@ static const struct mhi_pci_dev_info mhi_quectel_rm5xx_info = { .sideband_wake = true, }; +static const struct mhi_pci_dev_info mhi_quectel_rm500q_info = { + .name = "quectel-rm500q", + .edl = "qcom/sdx55m/edl.mbn", + .config = &modem_quectel_em1xx_config, + .bar_num = MHI_PCI_DEFAULT_BAR_NUM, + .dma_data_width = 32, + .mru_default = 32768, + .sideband_wake = true, +}; + static const struct mhi_channel_config mhi_foxconn_sdx55_channels[] = { MHI_CHANNEL_CONFIG_UL(0, "LOOPBACK", 32, 0), MHI_CHANNEL_CONFIG_DL(1, "LOOPBACK", 32, 0), @@ -732,6 +744,8 @@ static const struct mhi_channel_config mhi_sierra_em919x_channels[] = { MHI_CHANNEL_CONFIG_DL(13, "MBIM", 128, 0), MHI_CHANNEL_CONFIG_UL(14, "QMI", 32, 0), MHI_CHANNEL_CONFIG_DL(15, "QMI", 32, 0), + MHI_CHANNEL_CONFIG_UL(18, "IP_CTRL", 32, 0), + MHI_CHANNEL_CONFIG_DL(19, "IP_CTRL", 32, 0), MHI_CHANNEL_CONFIG_UL(32, "DUN", 32, 0), MHI_CHANNEL_CONFIG_DL(33, "DUN", 32, 0), MHI_CHANNEL_CONFIG_HW_UL(100, "IP_HW0", 512, 1), @@ -976,6 +990,9 @@ static const struct pci_device_id mhi_pci_id_table[] = { /* Telit FN980 hardware revision v1 */ { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0306, 0x1C5D, 0x2000), .driver_data = (kernel_ulong_t) &mhi_telit_fn980_hw_v1_info }, + /* Quectel RM500Q-AE (sdx55), uses the same vid:pid as qcom-sdx55m */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0306, PCI_VENDOR_ID_QCOM, 0x5002), + .driver_data = (kernel_ulong_t) &mhi_quectel_rm500q_info }, { PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x0306), .driver_data = (kernel_ulong_t) &mhi_qcom_sdx55_info }, /* Telit FN990 */ @@ -1097,6 +1114,7 @@ struct mhi_pci_device { struct work_struct recovery_work; struct timer_list health_check_timer; unsigned long status; + bool no_m3; bool reset_on_remove; }; @@ -1409,6 +1427,8 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) mhi_cntrl->mru = info->mru_default; mhi_cntrl->name = info->name; + mhi_pdev->no_m3 = info->no_m3; + if (!pdev->is_virtfn) mhi_pdev->reset_on_remove = info->reset_on_remove; @@ -1497,7 +1517,7 @@ static void mhi_pci_remove(struct pci_dev *pdev) } /* balancing probe put_noidle */ - if (pci_pme_capable(pdev, PCI_D3hot)) + if (pci_pme_capable(pdev, PCI_D3hot) && !mhi_pdev->no_m3) pm_runtime_get_noresume(&pdev->dev); if (mhi_pdev->reset_on_remove) diff --git a/drivers/char/scx200_gpio.c b/drivers/char/scx200_gpio.c index 700e6affea6f..3ec17832e9c9 100644 --- a/drivers/char/scx200_gpio.c +++ b/drivers/char/scx200_gpio.c @@ -107,10 +107,16 @@ static int __init scx200_gpio_init(void) } cdev_init(&scx200_gpio_cdev, &scx200_gpio_fileops); - cdev_add(&scx200_gpio_cdev, devid, MAX_PINS); + rc = cdev_add(&scx200_gpio_cdev, devid, MAX_PINS); + if (rc) { + dev_err(&pdev->dev, "cdev_add err: %d\n", rc); + goto undo_chrdev_region; + } return 0; /* succeed */ +undo_chrdev_region: + unregister_chrdev_region(devid, MAX_PINS); undo_platform_device_add: platform_device_del(pdev); undo_malloc: diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c index 9bb5fa642fd8..bbeb9b6060dc 100644 --- a/drivers/char/xilinx_hwicap/xilinx_hwicap.c +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c @@ -382,7 +382,7 @@ hwicap_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) 4 - bytes_to_read); } else { /* Get new data from the ICAP, and return what was requested. */ - kbuf = (u32 *) get_zeroed_page(GFP_KERNEL); + kbuf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!kbuf) { status = -ENOMEM; goto error; @@ -412,13 +412,13 @@ hwicap_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) /* If we didn't read correctly, then bail out. */ if (status) { - free_page((unsigned long)kbuf); + kfree(kbuf); goto error; } /* If we fail to return the data to the user, then bail out. */ if (copy_to_user(buf, kbuf, bytes_to_read)) { - free_page((unsigned long)kbuf); + kfree(kbuf); status = -EFAULT; goto error; } @@ -426,7 +426,7 @@ hwicap_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) kbuf, bytes_remaining); drvdata->read_buffer_in_use = bytes_remaining; - free_page((unsigned long)kbuf); + kfree(kbuf); } status = bytes_to_read; error: @@ -457,7 +457,7 @@ hwicap_write(struct file *file, const char __user *buf, goto error; } - kbuf = (u32 *) __get_free_page(GFP_KERNEL); + kbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!kbuf) { status = -ENOMEM; goto error; @@ -479,13 +479,13 @@ hwicap_write(struct file *file, const char __user *buf, (((char *)kbuf) + drvdata->write_buffer_in_use), buf + written, len - (drvdata->write_buffer_in_use))) { - free_page((unsigned long)kbuf); + kfree(kbuf); status = -EFAULT; goto error; } } else { if (copy_from_user(kbuf, buf + written, len)) { - free_page((unsigned long)kbuf); + kfree(kbuf); status = -EFAULT; goto error; } @@ -495,7 +495,7 @@ hwicap_write(struct file *file, const char __user *buf, kbuf, len >> 2); if (status) { - free_page((unsigned long)kbuf); + kfree(kbuf); status = -EFAULT; goto error; } @@ -516,7 +516,7 @@ hwicap_write(struct file *file, const char __user *buf, } } - free_page((unsigned long)kbuf); + kfree(kbuf); status = written; error: mutex_unlock(&drvdata->sem); diff --git a/drivers/char/xillybus/xillybus.h b/drivers/char/xillybus/xillybus.h index 51de7cbc579e..98c7ac4dd1f9 100644 --- a/drivers/char/xillybus/xillybus.h +++ b/drivers/char/xillybus/xillybus.h @@ -94,6 +94,9 @@ struct xilly_endpoint { __iomem void *registers; int fatal_error; + bool allow_isr; + spinlock_t allow_isr_lock; + struct mutex register_mutex; wait_queue_head_t ep_wait; diff --git a/drivers/char/xillybus/xillybus_class.c b/drivers/char/xillybus/xillybus_class.c index 5e8f03b77064..f7e0da233e2a 100644 --- a/drivers/char/xillybus/xillybus_class.c +++ b/drivers/char/xillybus/xillybus_class.c @@ -57,6 +57,9 @@ int xillybus_init_chrdev(struct device *dev, size_t namelen; struct xilly_unit *unit, *u; + if (num_nodes <= 0 || num_nodes > XILLYBUS_MAX_NODES || !idt || !prefix || !dev) + return -ENODEV; + unit = kzalloc_obj(*unit); if (!unit) @@ -68,6 +71,12 @@ int xillybus_init_chrdev(struct device *dev, snprintf(unit->name, UNITNAMELEN, "%s", prefix); for (i = 0; enumerate; i++) { + if (i > 99) { + dev_err(dev, "Failed to obtain unique unit name\n"); + rc = -ENODEV; + goto fail_obtain; + } + snprintf(unit->name, UNITNAMELEN, "%s_%02d", prefix, i); @@ -215,10 +224,15 @@ EXPORT_SYMBOL(xillybus_cleanup_chrdev); int xillybus_find_inode(struct inode *inode, void **private_data, int *index) { - int minor = iminor(inode); - int major = imajor(inode); + int minor, major; struct xilly_unit *unit = NULL, *iter; + if (!inode || !private_data || !index) + return -ENODEV; + + minor = iminor(inode); + major = imajor(inode); + mutex_lock(&unit_mutex); list_for_each_entry(iter, &unit_list, list_entry) diff --git a/drivers/char/xillybus/xillybus_class.h b/drivers/char/xillybus/xillybus_class.h index 5dbfdfc95c65..4dbed9adcaf8 100644 --- a/drivers/char/xillybus/xillybus_class.h +++ b/drivers/char/xillybus/xillybus_class.h @@ -8,6 +8,9 @@ #ifndef __XILLYBUS_CLASS_H #define __XILLYBUS_CLASS_H +#define XILLYBUS_MAX_NODES 1024 +#define XILLYBUS_MAX_IDT 1048576 + #include <linux/types.h> #include <linux/device.h> #include <linux/fs.h> diff --git a/drivers/char/xillybus/xillybus_core.c b/drivers/char/xillybus/xillybus_core.c index 952ef149aba1..7189c6d97e7d 100644 --- a/drivers/char/xillybus/xillybus_core.c +++ b/drivers/char/xillybus/xillybus_core.c @@ -28,6 +28,7 @@ #include <linux/delay.h> #include <linux/slab.h> #include <linux/workqueue.h> +#include <linux/sizes.h> #include "xillybus.h" #include "xillybus_class.h" @@ -72,6 +73,8 @@ static struct workqueue_struct *xillybus_wq; * * rd_spinlock does the same with rd_*_buf_idx, rd_empty and end_offset. * + * allow_isr_lock protects allow_isr. + * * register_mutex is endpoint-specific, and is held when non-atomic * register operations are performed. wr_mutex and rd_mutex may be * held when register_mutex is taken, but none of the spinlocks. Note that @@ -84,7 +87,8 @@ static struct workqueue_struct *xillybus_wq; * Only interruptible blocking is allowed on mutexes and wait queues. * * All in all, the locking order goes (with skips allowed, of course): - * wr_mutex -> rd_mutex -> register_mutex -> wr_spinlock -> rd_spinlock + * wr_mutex -> rd_mutex -> register_mutex -> + * allow_isr_lock -> wr_spinlock -> rd_spinlock */ static void malformed_message(struct xilly_endpoint *endpoint, u32 *buf) @@ -119,6 +123,13 @@ irqreturn_t xillybus_isr(int irq, void *data) unsigned int msg_channel, msg_bufno, msg_data, msg_dir; struct xilly_channel *channel; + guard(spinlock)(&ep->allow_isr_lock); + + if (!ep->allow_isr) { + dev_err_ratelimited(ep->dev, "Unexpected interrupt! Something is wrong with the hardware.\n"); + return IRQ_NONE; + } + buf = ep->msgbuf_addr; buf_size = ep->msg_buf_size/sizeof(u32); @@ -137,6 +148,7 @@ irqreturn_t xillybus_isr(int irq, void *data) if (++ep->failed_messages > 10) { dev_err(ep->dev, "Lost sync with interrupt messages. Stopping.\n"); + return IRQ_NONE; } else { dma_sync_single_for_device(ep->dev, ep->msgbuf_dma_addr, @@ -284,6 +296,19 @@ irqreturn_t xillybus_isr(int irq, void *data) EXPORT_SYMBOL(xillybus_isr); /* + * xilly_allow_isr() is similar to enabling / disabling the interrupt, + * with the difference that if an interrupt is issued while ep->allow_isr + * is false, this is visible in the kernel log. + */ + +static void xilly_allow_isr(struct xilly_endpoint *ep, bool newstate) +{ + guard(spinlock_irqsave)(&ep->allow_isr_lock); + + ep->allow_isr = newstate; +} + +/* * A few trivial memory management functions. * NOTE: These functions are used only on probe and remove, and therefore * no locks are applied! @@ -343,13 +368,20 @@ static int xilly_map_single(struct xilly_endpoint *ep, static int xilly_get_dma_buffers(struct xilly_endpoint *ep, struct xilly_alloc_state *s, struct xilly_buffer **buffers, - int bufnum, int bytebufsize) + unsigned int bufnum, + unsigned int bytebufsize) { int i, rc; dma_addr_t dma_addr; struct device *dev = ep->dev; struct xilly_buffer *this_buffer = NULL; /* Init to silence warning */ + if (bytebufsize == 0 || bytebufsize > SZ_1G) { + dev_err(ep->dev, + "Illegal buffer size requested in IDT. Aborting.\n"); + return -ENODEV; + } + if (buffers) { /* Not the message buffer */ this_buffer = devm_kcalloc(dev, bufnum, sizeof(struct xilly_buffer), @@ -431,8 +463,8 @@ static int xilly_setupchannels(struct xilly_endpoint *ep, struct device *dev = ep->dev; int i, entry, rc; struct xilly_channel *channel; - int channelnum, bufnum, bufsize, format, is_writebuf; - int bytebufsize; + unsigned int channelnum, bufnum, bufsize, format, is_writebuf; + unsigned int bytebufsize; int synchronous, allowpartial, exclusive_open, seekable; int supports_nonempty; int msg_buf_done = 0; @@ -512,7 +544,7 @@ static int xilly_setupchannels(struct xilly_endpoint *ep, format = (chandesc[1] >> 4) & 0x03; allowpartial = (chandesc[1] >> 6) & 0x01; synchronous = (chandesc[1] >> 7) & 0x01; - bufsize = 1 << (chandesc[2] & 0x1f); + bufsize = 1U << (chandesc[2] & 0x1f); bufnum = 1 << (chandesc[3] & 0x0f); exclusive_open = (chandesc[2] >> 7) & 0x01; seekable = (chandesc[2] >> 6) & 0x01; @@ -531,8 +563,7 @@ static int xilly_setupchannels(struct xilly_endpoint *ep, channel->log2_element_size = ((format > 2) ? 2 : format); - bytebufsize = bufsize * - (1 << channel->log2_element_size); + bytebufsize = bufsize << channel->log2_element_size; buffers = devm_kcalloc(dev, bufnum, sizeof(struct xilly_buffer *), @@ -589,7 +620,7 @@ static int xilly_setupchannels(struct xilly_endpoint *ep, static int xilly_scan_idt(struct xilly_endpoint *endpoint, struct xilly_idt_handle *idt_handle) { - int count = 0; + unsigned int count = 0; unsigned char *idt = endpoint->channels[1]->wr_buffers[0]->addr; unsigned char *end_of_idt = idt + endpoint->idtlen - 4; unsigned char *scan; @@ -623,6 +654,12 @@ static int xilly_scan_idt(struct xilly_endpoint *endpoint, return -ENODEV; } + if (count == 0 || count > XILLYBUS_MAX_NODES) { + dev_err(endpoint->dev, + "Unreasonable number of channels. Aborting.\n"); + return -ENODEV; + } + idt_handle->entries = len >> 2; endpoint->num_channels = count; @@ -639,6 +676,8 @@ static int xilly_obtain_idt(struct xilly_endpoint *endpoint) channel->wr_sleepy = 1; + xilly_allow_isr(endpoint, true); + iowrite32(1 | (3 << 24), /* Opcode 3 for channel 0 = Send IDT */ endpoint->registers + fpga_buf_ctrl_reg); @@ -647,6 +686,8 @@ static int xilly_obtain_idt(struct xilly_endpoint *endpoint) (!channel->wr_sleepy), XILLY_TIMEOUT); + xilly_allow_isr(endpoint, false); + if (t <= 0) { dev_err(endpoint->dev, "Failed to obtain IDT. Aborting.\n"); @@ -694,7 +735,8 @@ static ssize_t xillybus_read(struct file *filp, char __user *userbuf, unsigned long flags; int bytes_done = 0; int no_time_left = 0; - long deadline, left_to_sleep; + unsigned long deadline; + long left_to_sleep; struct xilly_channel *channel = filp->private_data; int empty, reached_eof, exhausted, ready; @@ -724,8 +766,18 @@ static ssize_t xillybus_read(struct file *filp, char __user *userbuf, bufidx = channel->wr_host_buf_idx; bufpos = channel->wr_host_buf_pos; howmany = ((channel->wr_buffers[bufidx]->end_offset - + 1) << channel->log2_element_size) - - bufpos; + + 1) << channel->log2_element_size); + + if (howmany > channel->wr_buf_size || + howmany < bufpos) { + dev_err(channel->endpoint->dev, + "Illegal buffer fill level from hardware\n"); + channel->endpoint->fatal_error = 1; + spin_unlock_irqrestore(&channel->wr_spinlock, flags); + break; + } + + howmany -= bufpos; /* Update wr_host_* to its post-operation state */ if (howmany > bytes_to_do) { @@ -938,7 +990,7 @@ interrupted: /* Mutex is not held if got here */ return -EINTR; } - left_to_sleep = deadline - ((long) jiffies); + left_to_sleep = deadline - jiffies; /* * If our time is out, skip the waiting. We may miss wr_sleepy @@ -1814,6 +1866,9 @@ struct xilly_endpoint *xillybus_init_endpoint(struct device *dev) endpoint->failed_messages = 0; endpoint->fatal_error = 0; + endpoint->allow_isr = false; + spin_lock_init(&endpoint->allow_isr_lock); + init_waitqueue_head(&endpoint->ep_wait); mutex_init(&endpoint->register_mutex); @@ -1826,6 +1881,9 @@ static int xilly_quiesce(struct xilly_endpoint *endpoint) long t; endpoint->idtlen = -1; + endpoint->num_channels = 0; + + xilly_allow_isr(endpoint, true); iowrite32((u32) (endpoint->dma_using_dac & 0x0001), endpoint->registers + fpga_dma_control_reg); @@ -1833,6 +1891,9 @@ static int xilly_quiesce(struct xilly_endpoint *endpoint) t = wait_event_interruptible_timeout(endpoint->ep_wait, (endpoint->idtlen >= 0), XILLY_TIMEOUT); + + xilly_allow_isr(endpoint, false); + if (t <= 0) { dev_err(endpoint->dev, "Failed to quiesce the device on exit.\n"); @@ -1886,6 +1947,8 @@ int xillybus_endpoint_discovery(struct xilly_endpoint *endpoint) endpoint->idtlen = -1; + xilly_allow_isr(endpoint, true); + /* * Set DMA 32/64 bit mode, quiesce the device (?!) and get IDT * buffer size. @@ -1896,11 +1959,19 @@ int xillybus_endpoint_discovery(struct xilly_endpoint *endpoint) t = wait_event_interruptible_timeout(endpoint->ep_wait, (endpoint->idtlen >= 0), XILLY_TIMEOUT); + + xilly_allow_isr(endpoint, false); + if (t <= 0) { dev_err(endpoint->dev, "No response from FPGA. Aborting.\n"); return -ENODEV; } + if (endpoint->idtlen < 4 || endpoint->idtlen > XILLYBUS_MAX_IDT) { + dev_err(endpoint->dev, "Invalid IDT length. Aborting.\n"); + return -ENODEV; + } + /* Enable DMA */ iowrite32((u32) (0x0002 | (endpoint->dma_using_dac & 0x0001)), endpoint->registers + fpga_dma_control_reg); @@ -1917,6 +1988,7 @@ int xillybus_endpoint_discovery(struct xilly_endpoint *endpoint) if (rc) goto failed_idt; + /* xilly_obtain_idt() allows and then disallows the ISR */ rc = xilly_obtain_idt(endpoint); if (rc) goto failed_idt; @@ -1935,6 +2007,8 @@ int xillybus_endpoint_discovery(struct xilly_endpoint *endpoint) if (rc) goto failed_idt; + xilly_allow_isr(endpoint, true); + rc = xillybus_init_chrdev(dev, &xillybus_fops, endpoint->owner, endpoint, idt_handle.names, diff --git a/drivers/char/xillybus/xillybus_of.c b/drivers/char/xillybus/xillybus_of.c index 46e1046abfca..44b0c754deb2 100644 --- a/drivers/char/xillybus/xillybus_of.c +++ b/drivers/char/xillybus/xillybus_of.c @@ -53,6 +53,9 @@ static int xilly_drv_probe(struct platform_device *op) irq = platform_get_irq(op, 0); + if (irq < 0) + return irq; + rc = devm_request_irq(dev, irq, xillybus_isr, 0, xillyname, endpoint); if (rc) diff --git a/drivers/char/xillybus/xillyusb.c b/drivers/char/xillybus/xillyusb.c index 34e7ad3bcab3..bd3babd39f35 100644 --- a/drivers/char/xillybus/xillyusb.c +++ b/drivers/char/xillybus/xillyusb.c @@ -24,6 +24,7 @@ #include <linux/spinlock.h> #include <linux/mutex.h> #include <linux/workqueue.h> +#include <linux/sizes.h> #include <linux/crc32.h> #include <linux/poll.h> #include <linux/delay.h> @@ -50,6 +51,7 @@ MODULE_LICENSE("GPL v2"); static const char xillyname[] = "xillyusb"; static unsigned int fifo_buf_order; +static DEFINE_MUTEX(fifo_buf_order_mutex); static struct workqueue_struct *wakeup_wq; #define USB_VENDOR_ID_XILINX 0x03fd @@ -71,7 +73,6 @@ struct xillyfifo { unsigned int bufsize; /* In bytes, always a power of 2 */ unsigned int bufnum; unsigned int size; /* Lazy: Equals bufsize * bufnum */ - unsigned int buf_order; int fill; /* Number of bytes in the FIFO */ spinlock_t lock; @@ -95,7 +96,6 @@ struct xillyusb_endpoint { struct list_head filled_buffers; spinlock_t buffers_lock; /* protect these two lists */ - unsigned int order; unsigned int buffer_size; unsigned int fill_mask; @@ -370,37 +370,41 @@ static int fifo_init(struct xillyfifo *fifo, unsigned int log2_size) { unsigned int log2_bufnum; - unsigned int buf_order; int i; unsigned int log2_fifo_buf_size; + mutex_lock(&fifo_buf_order_mutex); + retry: log2_fifo_buf_size = fifo_buf_order + PAGE_SHIFT; if (log2_size > log2_fifo_buf_size) { log2_bufnum = log2_size - log2_fifo_buf_size; - buf_order = fifo_buf_order; fifo->bufsize = 1 << log2_fifo_buf_size; } else { log2_bufnum = 0; - buf_order = (log2_size > PAGE_SHIFT) ? - log2_size - PAGE_SHIFT : 0; fifo->bufsize = 1 << log2_size; } fifo->bufnum = 1 << log2_bufnum; fifo->size = fifo->bufnum * fifo->bufsize; - fifo->buf_order = buf_order; + + if (!fifo->size || /* Unsigned integer overflow */ + fifo->size > SZ_1G) { /* Avoid signed int issues */ + mutex_unlock(&fifo_buf_order_mutex); + return -ENOMEM; /* Reported as greed for memory */ + } fifo->mem = kmalloc_array(fifo->bufnum, sizeof(void *), GFP_KERNEL); - if (!fifo->mem) + if (!fifo->mem) { + mutex_unlock(&fifo_buf_order_mutex); return -ENOMEM; + } for (i = 0; i < fifo->bufnum; i++) { - fifo->mem[i] = (void *) - __get_free_pages(GFP_KERNEL, buf_order); + fifo->mem[i] = kmalloc(fifo->bufsize, GFP_KERNEL); if (!fifo->mem[i]) goto memfail; @@ -413,11 +417,13 @@ retry: fifo->writebuf = 0; spin_lock_init(&fifo->lock); init_waitqueue_head(&fifo->waitq); + + mutex_unlock(&fifo_buf_order_mutex); return 0; memfail: for (i--; i >= 0; i--) - free_pages((unsigned long)fifo->mem[i], buf_order); + kfree(fifo->mem[i]); kfree(fifo->mem); fifo->mem = NULL; @@ -426,6 +432,7 @@ memfail: fifo_buf_order--; goto retry; } else { + mutex_unlock(&fifo_buf_order_mutex); return -ENOMEM; } } @@ -438,7 +445,7 @@ static void fifo_mem_release(struct xillyfifo *fifo) return; for (i = 0; i < fifo->bufnum; i++) - free_pages((unsigned long)fifo->mem[i], fifo->buf_order); + kfree(fifo->mem[i]); kfree(fifo->mem); } @@ -477,7 +484,7 @@ static void endpoint_dealloc(struct xillyusb_endpoint *ep) struct xillybuffer *xb = list_entry(this, struct xillybuffer, entry); - free_pages((unsigned long)xb->buf, ep->order); + kfree(xb->buf); kfree(xb); } @@ -509,8 +516,7 @@ static struct xillyusb_endpoint init_usb_anchor(&ep->anchor); INIT_WORK(&ep->workitem, work); - ep->order = order; - ep->buffer_size = 1 << (PAGE_SHIFT + order); + ep->buffer_size = 1 << (PAGE_SHIFT + order); ep->outstanding_urbs = 0; ep->drained = true; ep->wake_on_drain = false; @@ -520,7 +526,6 @@ static struct xillyusb_endpoint for (i = 0; i < bufnum; i++) { struct xillybuffer *xb; - unsigned long addr; xb = kzalloc_obj(*xb); @@ -529,15 +534,14 @@ static struct xillyusb_endpoint return NULL; } - addr = __get_free_pages(GFP_KERNEL, order); + xb->buf = kmalloc(ep->buffer_size, GFP_KERNEL); - if (!addr) { + if (!xb->buf) { kfree(xb); endpoint_dealloc(ep); return NULL; } - xb->buf = (void *)addr; xb->ep = ep; list_add_tail(&xb->entry, &ep->buffers); } @@ -885,6 +889,7 @@ static int process_in_opcode(struct xillyusb_dev *xdev, struct xillyusb_channel *chan; struct device *dev = xdev->dev; int chan_idx = chan_num >> 1; + struct xillyfifo *in_fifo; if (chan_idx >= xdev->num_channels) { dev_err(dev, "Received illegal channel ID %d from FPGA\n", @@ -909,7 +914,10 @@ static int process_in_opcode(struct xillyusb_dev *xdev, */ smp_wmb(); WRITE_ONCE(chan->read_data_ok, 0); - wake_up_interruptible(&chan->in_fifo->waitq); + + in_fifo = READ_ONCE(chan->in_fifo); + if (in_fifo) + wake_up_interruptible(&in_fifo->waitq); break; case OPCODE_REACHED_CHECKPOINT: @@ -1124,12 +1132,13 @@ unlock_done: */ static int flush_downstream(struct xillyusb_channel *chan, - long timeout, + unsigned long timeout, bool interruptible) { struct xillyusb_dev *xdev = chan->xdev; int chan_num = chan->chan_idx << 1; - long deadline, left_to_sleep; + unsigned long deadline; + long left_to_sleep; int rc; if (chan->flushed) @@ -1138,7 +1147,8 @@ static int flush_downstream(struct xillyusb_channel *chan, deadline = jiffies + 1 + timeout; if (chan->flushing) { - long cancel_deadline = jiffies + 1 + XILLY_RESPONSE_TIMEOUT; + unsigned long cancel_deadline = + jiffies + 1 + XILLY_RESPONSE_TIMEOUT; chan->canceled = 0; rc = xillyusb_send_opcode(xdev, chan_num, @@ -1149,7 +1159,7 @@ static int flush_downstream(struct xillyusb_channel *chan, /* Ignoring interrupts. Cancellation must be handled */ while (!chan->canceled) { - left_to_sleep = cancel_deadline - ((long)jiffies); + left_to_sleep = cancel_deadline - jiffies; if (left_to_sleep <= 0) { report_io_error(xdev, -EIO); @@ -1199,7 +1209,7 @@ static int flush_downstream(struct xillyusb_channel *chan, } while (chan->flushing) { - left_to_sleep = deadline - ((long)jiffies); + left_to_sleep = deadline - jiffies; if (left_to_sleep <= 0) return -ETIMEDOUT; @@ -1416,16 +1426,12 @@ unopen: if (filp->f_mode & FMODE_WRITE) chan->open_for_write = 0; +unmutex_fail: mutex_unlock(&chan->lock); kref_put(&xdev->kref, cleanup_dev); return rc; - -unmutex_fail: - kref_put(&xdev->kref, cleanup_dev); - mutex_unlock(&chan->lock); - return rc; } static ssize_t xillyusb_read(struct file *filp, char __user *userbuf, @@ -1436,7 +1442,8 @@ static ssize_t xillyusb_read(struct file *filp, char __user *userbuf, struct xillyfifo *fifo = chan->in_fifo; int chan_num = (chan->chan_idx << 1) | 1; - long deadline, left_to_sleep; + unsigned long deadline; + long left_to_sleep; int bytes_done = 0; bool sent_set_push = false; int rc; @@ -1465,7 +1472,7 @@ static ssize_t xillyusb_read(struct file *filp, char __user *userbuf, bytes_done += rc; chan->in_consumed_bytes += rc; - left_to_sleep = deadline - ((long)jiffies); + left_to_sleep = deadline - jiffies; /* * Some 32-bit arithmetic that may wrap. Note that @@ -1508,8 +1515,8 @@ static ssize_t xillyusb_read(struct file *filp, char __user *userbuf, */ if (chan->read_data_ok && - (leap > (fifo->size >> 3) || - (checkpoint_for_complete && leap > 0))) { + (leap > 0 && (leap > (fifo->size >> 3) || + checkpoint_for_complete))) { chan->in_current_checkpoint = checkpoint; rc = xillyusb_send_opcode(xdev, chan_num, OPCODE_SET_CHECKPOINT, @@ -1943,6 +1950,9 @@ static int setup_channels(struct xillyusb_dev *xdev, struct xillyusb_channel *chan, *new_channels; int i; + /* Don't let process_bulk_in() run while we change the channels */ + guard(mutex)(&xdev->process_in_mutex); + chan = kzalloc_objs(*chan, num_channels); if (!chan) return -ENOMEM; @@ -2067,6 +2077,13 @@ static int xillyusb_discovery(struct usb_interface *interface) } idt_len = READ_ONCE(idt_fifo.fill); + + if (idt_len < 4 || idt_len > XILLYBUS_MAX_IDT) { + rc = -ENODEV; + dev_err(&interface->dev, "Invalid IDT length. Aborting.\n"); + goto unfifo; + } + idt = kmalloc(idt_len, GFP_KERNEL); if (!idt) { @@ -2101,6 +2118,12 @@ static int xillyusb_discovery(struct usb_interface *interface) goto unidt; } + if (num_channels == 0 || num_channels > XILLYBUS_MAX_NODES) { + dev_err(&interface->dev, "Unreasonable number of channels. Aborting.\n"); + rc = -ENODEV; + goto unidt; + } + rc = setup_channels(xdev, (void *)idt + 3, num_channels); if (rc) diff --git a/drivers/comedi/drivers/addi_apci_3xxx.c b/drivers/comedi/drivers/addi_apci_3xxx.c index 695cce103177..1dcda82d9503 100644 --- a/drivers/comedi/drivers/addi_apci_3xxx.c +++ b/drivers/comedi/drivers/addi_apci_3xxx.c @@ -787,7 +787,7 @@ static int apci3xxx_auto_attach(struct comedi_device *dev, dev->irq = pcidev->irq; } - n_subdevices = (board->ai_n_chan ? 0 : 1) + board->has_ao + + n_subdevices = (board->ai_n_chan ? 1 : 0) + board->has_ao + board->has_dig_in + board->has_dig_out + board->has_ttl_io; ret = comedi_alloc_subdevices(dev, n_subdevices); diff --git a/drivers/comedi/drivers/comedi_bond.c b/drivers/comedi/drivers/comedi_bond.c index 8e10ecab4f0d..a10cfbd04c72 100644 --- a/drivers/comedi/drivers/comedi_bond.c +++ b/drivers/comedi/drivers/comedi_bond.c @@ -39,6 +39,7 @@ #include <linux/module.h> #include <linux/string.h> +#include <linux/seq_buf.h> #include <linux/slab.h> #include <linux/comedi.h> #include <linux/comedi/comedilib.h> @@ -170,10 +171,11 @@ static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it) { struct comedi_bond_private *devpriv = dev->private; DECLARE_BITMAP(devs_opened, COMEDI_NUM_BOARD_MINORS); + struct seq_buf s; int i; memset(&devs_opened, 0, sizeof(devs_opened)); - devpriv->name[0] = 0; + seq_buf_init(&s, devpriv->name, sizeof(devpriv->name)); /* * Loop through all comedi devices specified on the command-line, * building our device list. @@ -250,15 +252,9 @@ static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it) } devpriv->devs = devs; devpriv->devs[devpriv->ndevs++] = bdev; - { - /* Append dev:subdev to devpriv->name */ - char buf[20]; - - snprintf(buf, sizeof(buf), "%u:%u ", - bdev->minor, bdev->subdev); - strlcat(devpriv->name, buf, - sizeof(devpriv->name)); - } + + /* Append dev:subdev to devpriv->name */ + seq_buf_printf(&s, "%u:%u ", bdev->minor, bdev->subdev); } } @@ -267,6 +263,8 @@ static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it) return -EINVAL; } + seq_buf_str(&s); + return 0; } diff --git a/drivers/comedi/drivers/comedi_parport.c b/drivers/comedi/drivers/comedi_parport.c index 57ee3f9dfba2..94284ff157ac 100644 --- a/drivers/comedi/drivers/comedi_parport.c +++ b/drivers/comedi/drivers/comedi_parport.c @@ -243,7 +243,8 @@ static int parport_attach(struct comedi_device *dev, outb(0, dev->iobase + PARPORT_DATA_REG); outb(0, dev->iobase + PARPORT_CTRL_REG); - if (it->options[1]) { + /* only ISA interrupts are valid on a parallel port */ + if (it->options[1] >= 1 && it->options[1] <= 15) { ret = request_irq(it->options[1], parport_interrupt, 0, dev->board_name, dev); if (ret == 0) diff --git a/drivers/comedi/drivers/dmm32at.c b/drivers/comedi/drivers/dmm32at.c index d1d9f75e168f..c6a6ba2899e6 100644 --- a/drivers/comedi/drivers/dmm32at.c +++ b/drivers/comedi/drivers/dmm32at.c @@ -600,7 +600,8 @@ static int dmm32at_attach(struct comedi_device *dev, return ret; } - if (it->options[1]) { + /* only ISA interrupts are valid on this board */ + if (it->options[1] >= 1 && it->options[1] <= 15) { ret = request_irq(it->options[1], dmm32at_isr, 0, dev->board_name, dev); if (ret == 0) diff --git a/drivers/comedi/drivers/dt2811.c b/drivers/comedi/drivers/dt2811.c index 0438b8c90e44..53cb17993ae7 100644 --- a/drivers/comedi/drivers/dt2811.c +++ b/drivers/comedi/drivers/dt2811.c @@ -567,7 +567,7 @@ static int dt2811_attach(struct comedi_device *dev, struct comedi_devconfig *it) dt2811_reset(dev); /* IRQ's 2,3,5,7 are valid for async command support */ - if (it->options[1] <= 7 && (BIT(it->options[1]) & 0xac)) { + if (it->options[1] >= 0 && it->options[1] <= 7 && (BIT(it->options[1]) & 0xac)) { ret = request_irq(it->options[1], dt2811_interrupt, 0, dev->board_name, dev); if (ret == 0) diff --git a/drivers/comedi/drivers/dt2814.c b/drivers/comedi/drivers/dt2814.c index caec16482afb..63b7f17691ff 100644 --- a/drivers/comedi/drivers/dt2814.c +++ b/drivers/comedi/drivers/dt2814.c @@ -316,7 +316,8 @@ static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it) return -EIO; } - if (it->options[1]) { + /* only ISA interrupts are valid on this board */ + if (it->options[1] >= 1 && it->options[1] <= 15) { ret = request_irq(it->options[1], dt2814_interrupt, 0, dev->board_name, dev); if (ret == 0) diff --git a/drivers/comedi/drivers/ni_atmio16d.c b/drivers/comedi/drivers/ni_atmio16d.c index 6765cdc276ca..87fed16b112c 100644 --- a/drivers/comedi/drivers/ni_atmio16d.c +++ b/drivers/comedi/drivers/ni_atmio16d.c @@ -593,7 +593,9 @@ static int atmio16d_attach(struct comedi_device *dev, /* reset the atmio16d hardware */ reset_atmio16d(dev); - if (it->options[1]) { + /* only irqs 3, 4, 5, 6, 7, 9, 10, 11, 12, 14, and 15 are valid */ + if (it->options[1] >= 3 && it->options[1] <= 15 && + (1 << it->options[1]) & 0xdef8) { ret = request_irq(it->options[1], atmio16d_interrupt, 0, dev->board_name, dev); if (ret == 0) diff --git a/drivers/comedi/drivers/pcmmio.c b/drivers/comedi/drivers/pcmmio.c index f42b7343b4e4..afe2bda91659 100644 --- a/drivers/comedi/drivers/pcmmio.c +++ b/drivers/comedi/drivers/pcmmio.c @@ -684,7 +684,8 @@ static int pcmmio_attach(struct comedi_device *dev, struct comedi_devconfig *it) pcmmio_reset(dev); - if (it->options[1]) { + /* the irq is configured in software, so any ISA irq is OK */ + if (it->options[1] >= 1 && it->options[1] <= 15) { ret = request_irq(it->options[1], interrupt_pcmmio, 0, dev->board_name, dev); if (ret == 0) { diff --git a/drivers/comedi/drivers/pcmuio.c b/drivers/comedi/drivers/pcmuio.c index d9995cbeecb6..da80bca0bb55 100644 --- a/drivers/comedi/drivers/pcmuio.c +++ b/drivers/comedi/drivers/pcmuio.c @@ -546,7 +546,8 @@ static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it) pcmuio_reset(dev); - if (it->options[1]) { + /* only ISA interrupts are valid on this board */ + if (it->options[1] >= 1 && it->options[1] <= 15) { /* request the irq for the 1st asic */ ret = request_irq(it->options[1], pcmuio_interrupt, 0, dev->board_name, dev); @@ -558,7 +559,7 @@ static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it) if (it->options[2] == dev->irq) { /* the same irq (or none) is used by both asics */ devpriv->irq2 = it->options[2]; - } else if (it->options[2]) { + } else if (it->options[2] >= 1 && it->options[2] <= 15) { /* request the irq for the 2nd asic */ ret = request_irq(it->options[2], pcmuio_interrupt, 0, dev->board_name, dev); diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index f8c2da207cb4..88391ea161ed 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -7,15 +7,19 @@ #include <linux/atomic.h> #include <linux/completion.h> #include <linux/delay.h> +#include <linux/dma-mapping.h> #include <linux/genalloc.h> #include <linux/hashtable.h> #include <linux/idr.h> +#include <linux/interrupt.h> #include <linux/io.h> +#include <linux/iommu.h> #include <linux/kfifo.h> #include <linux/kthread.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/of.h> +#include <linux/of_irq.h> #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/slab.h> @@ -24,6 +28,11 @@ #include <linux/firmware/intel/stratix10-smc.h> #include <linux/firmware/intel/stratix10-svc-client.h> #include <linux/types.h> +#include <linux/cpu.h> +#include <linux/cpumask.h> +#include <linux/of_device.h> +#include <linux/reboot.h> +#include <linux/workqueue.h> /** * SVC_NUM_DATA_IN_FIFO - number of struct stratix10_svc_data in the FIFO @@ -34,16 +43,51 @@ * from the secure world for FPGA manager to reuse, or to free the buffer(s) * when all bit-stream data had be send. * - * FPGA_CONFIG_STATUS_TIMEOUT_SEC - poll the FPGA configuration status, - * service layer will return error to FPGA manager when timeout occurs, - * timeout is set to 30 seconds (30 * 1000) at Intel Stratix10 SoC. + * FPGA_CONFIG_STATUS_TIMEOUT_SEC - overall poll budget for FPGA + * configuration / service completion status. Service layer returns + * error to the client when this timeout expires (30 seconds). + * + * SVC_POLL_INTERVAL_MS_FAST / SVC_POLL_INTERVAL_MS_SLOW - sleep between + * SMC status polls for the fast and slow phases. + * + * SVC_POLL_FAST_WINDOW_MS - duration of the fast-poll phase before + * switching to the slower interval (1 second). + * + * SVC_POLL_COUNT_FAST / SVC_POLL_COUNT_SLOW - iteration budgets derived + * from the intervals and overall timeout so the two phases still sum to + * FPGA_CONFIG_STATUS_TIMEOUT_SEC. */ #define SVC_NUM_DATA_IN_FIFO 8 #define SVC_NUM_CHANNEL 4 #define FPGA_CONFIG_DATA_CLAIM_TIMEOUT_MS 2000 #define FPGA_CONFIG_STATUS_TIMEOUT_SEC 30 +#define SVC_POLL_INTERVAL_MS_FAST 20 +#define SVC_POLL_INTERVAL_MS_SLOW 500 +#define SVC_POLL_FAST_WINDOW_MS 1000 +#define SVC_POLL_COUNT_FAST \ + (SVC_POLL_FAST_WINDOW_MS / SVC_POLL_INTERVAL_MS_FAST) +#define SVC_POLL_COUNT_SLOW \ + (((FPGA_CONFIG_STATUS_TIMEOUT_SEC * MSEC_PER_SEC) - \ + SVC_POLL_FAST_WINDOW_MS) / SVC_POLL_INTERVAL_MS_SLOW) #define BYTE_TO_WORD_SIZE 4 +/* + * SVC_SDM_DMA_ADDR_BITS - constrains the IOVA allocated by + * dma_alloc_coherent() to 29 bits (0x0000_0000 - 0x1FFF_FFFF) + * when SMMU is active on Agilex5. The SDM accesses these buffers + * via the SMMU using IOVAs, so the 29-bit limit keeps IOVAs within + * the SDM's addressable window. + * + * SVC_SDM_DMA_ADDR_OFFSET - ATF on Agilex5 distinguishes + * SMMU-mapped buffers from direct physical addresses by the + * presence of this offset. The driver adds it to the IOVA before + * passing the address to ATF via SMC; ATF strips it, translates + * the remaining IOVA through the SMMU, and the SDM accesses the + * underlying physical memory. + */ +#define SVC_SDM_DMA_ADDR_BITS 29 +#define SVC_SDM_DMA_ADDR_OFFSET 0x80000000UL + /* stratix10 service layer clients */ #define STRATIX10_RSU "stratix10-rsu" #define SOCFPGA_HWMON "socfpga-hwmon" @@ -97,6 +141,19 @@ #define STRATIX10_GET_SDM_STATUS_CODE(status) \ (FIELD_GET(STRATIX10_SDM_STATUS_MASK, status)) +struct stratix10_svc_pdata { + bool needs_psci_cpu_off; + bool use_dma_mem; +}; + +static const struct stratix10_svc_pdata psci_cpu_off_pdata = { + .needs_psci_cpu_off = true, +}; + +static const struct stratix10_svc_pdata agilex5_pdata = { + .use_dma_mem = true, +}; + typedef void (svc_invoke_fn)(unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, @@ -137,18 +194,25 @@ struct stratix10_svc_sh_memory { /** * struct stratix10_svc_data_mem - service memory structure * @vaddr: virtual address - * @paddr: physical address + * @paddr: address passed to ATF via SMC and echoed back in completion + * notifications; used as the lookup key in svc_pa_to_va(). + * On the SMMU path this is (IOVA + %SVC_SDM_DMA_ADDR_OFFSET); + * on the gen_pool path this equals the raw physical address. * @size: size of memory + * @dma_addr: IOVA returned by dma_alloc_coherent(); used to free the + * mapping via dma_free_coherent() on the SMMU path. * @node: link list head node * * This struct is used in a list that keeps track of buffers which have * been allocated or freed from the memory pool. Service layer driver also - * uses this struct to transfer physical address to virtual address. + * uses this struct to map the address returned by ATF back to a virtual + * address. */ struct stratix10_svc_data_mem { void *vaddr; phys_addr_t paddr; size_t size; + dma_addr_t dma_addr; struct list_head node; }; @@ -219,6 +283,7 @@ struct stratix10_async_chan { * @supported: Flag indicating whether the system supports async operations * @initialized: Flag indicating whether the control structure has * been initialized + * @irq: Interrupt request number associated with the asynchronous control * @invoke_fn: Function pointer for invoking Stratix10 service calls * to EL3 secure firmware * @async_id_pool: Pointer to the ID pool used for asynchronous @@ -229,12 +294,14 @@ struct stratix10_async_chan { * structure * @trx_list_lock: Spinlock for protecting the transaction list * operations + * @async_work: Work structure for scheduling asynchronous work * @trx_list: Hash table for managing asynchronous transactions */ struct stratix10_async_ctrl { bool supported; bool initialized; + int irq; void (*invoke_fn)(struct stratix10_async_ctrl *actrl, const struct arm_smccc_1_2_regs *args, struct arm_smccc_1_2_regs *res); @@ -243,6 +310,7 @@ struct stratix10_async_ctrl { struct stratix10_async_chan *common_async_chan; /* spinlock to protect trx_list hash table */ spinlock_t trx_list_lock; + struct work_struct async_work; DECLARE_HASHTABLE(trx_list, ASYNC_TRX_HASH_BITS); }; @@ -283,6 +351,16 @@ struct stratix10_svc_chan { * @svc: manages the list of client svc drivers * @sdm_lock: only allows a single command single response to SDM * @actrl: async control structure + * @psci_reboot_nb: reboot notifier for PSCI secondary CPU offlining + * @use_dma_mem: when true, buffers are allocated via dma_alloc_coherent() + * instead of the ATF reserved-memory gen_pool. + * @dma_addr_offset: value added to the DMA address (IOVA) before passing it + * to ATF via SMC. ATF uses this offset to distinguish + * SMMU-mapped buffers from direct physical addresses; it + * strips the offset, translates the remaining IOVA through + * the SMMU, and the SDM accesses the underlying memory. + * Set to %SVC_SDM_DMA_ADDR_OFFSET on Agilex5 when SMMU is + * active; zero otherwise. * @chans: array of service channels * * This struct is used to create communication channels for service clients, to @@ -299,6 +377,9 @@ struct stratix10_svc_controller { struct stratix10_svc *svc; struct mutex sdm_lock; struct stratix10_async_ctrl actrl; + struct notifier_block psci_reboot_nb; + bool use_dma_mem; + unsigned long dma_addr_offset; struct stratix10_svc_chan chans[] __counted_by(num_chans); }; @@ -324,11 +405,12 @@ static void *svc_pa_to_va(unsigned long addr) pr_debug("claim back P-addr=0x%016x\n", (unsigned int)addr); guard(mutex)(&svc_mem_lock); - list_for_each_entry(pmem, &svc_data_mem, node) + list_for_each_entry(pmem, &svc_data_mem, node) { if (pmem->paddr == addr) return pmem->vaddr; + } - /* physical address is not found */ + /* address is not found */ return NULL; } @@ -362,11 +444,17 @@ static void svc_thread_cmd_data_claim(struct stratix10_svc_controller *ctrl, break; } cb_data->status = BIT(SVC_STATUS_BUFFER_DONE); - cb_data->kaddr1 = svc_pa_to_va(res.a1); + /* + * The firmware COMPLETED_WRITE response returns the + * raw IOVA (without dma_addr_offset). Add it back to + * match the key stored in pmem->paddr at allocation + * time. dma_addr_offset is zero on non-SMMU paths. + */ + cb_data->kaddr1 = svc_pa_to_va(res.a1 + ctrl->dma_addr_offset); cb_data->kaddr2 = (res.a2) ? - svc_pa_to_va(res.a2) : NULL; + svc_pa_to_va(res.a2 + ctrl->dma_addr_offset) : NULL; cb_data->kaddr3 = (res.a3) ? - svc_pa_to_va(res.a3) : NULL; + svc_pa_to_va(res.a3 + ctrl->dma_addr_offset) : NULL; p_data->chan->scl->receive_cb(p_data->chan->scl, cb_data); } else { @@ -379,6 +467,52 @@ static void svc_thread_cmd_data_claim(struct stratix10_svc_controller *ctrl, } /** + * svc_cmd_poll_status() - poll secure-world service completion status + * @p_data: pointer to service data structure + * @ctrl: pointer to service layer controller + * @res: pointer to store the latest SMC response + * @poll_count: maximum number of SMC polls for this phase + * @poll_interval_in_ms: sleep duration between polls while status is busy + * + * Invokes the appropriate ISDONE / SERVICE_COMPLETED SMC until the secure + * world returns a terminal status (OK, ERROR, or REJECTED), or until + * @poll_count attempts have been exhausted. + * + * Return: true if a terminal status was received, false on poll budget + * exhaustion. + */ +static bool svc_cmd_poll_status(struct stratix10_svc_data *p_data, + struct stratix10_svc_controller *ctrl, + struct arm_smccc_res *res, + int poll_count, int poll_interval_in_ms) +{ + unsigned long a0, a1, a2; + + a0 = INTEL_SIP_SMC_FPGA_CONFIG_ISDONE; + a1 = (unsigned long)p_data->paddr; + a2 = (unsigned long)p_data->size; + + if (p_data->command == COMMAND_POLL_SERVICE_STATUS) + a0 = INTEL_SIP_SMC_SERVICE_COMPLETED; + + while (poll_count--) { + ctrl->invoke_fn(a0, a1, a2, 0, 0, 0, 0, 0, res); + if (res->a0 == INTEL_SIP_SMC_STATUS_OK || + res->a0 == INTEL_SIP_SMC_STATUS_ERROR || + res->a0 == INTEL_SIP_SMC_STATUS_REJECTED) + return true; + + /* + * request is still in progress, go to sleep then + * poll again + */ + msleep(poll_interval_in_ms); + } + + return false; +} + +/** * svc_thread_cmd_config_status() - check configuration status * @ctrl: pointer to service layer controller * @p_data: pointer to service data structure @@ -392,8 +526,7 @@ static void svc_thread_cmd_config_status(struct stratix10_svc_controller *ctrl, struct stratix10_svc_cb_data *cb_data) { struct arm_smccc_res res; - int count_in_sec; - unsigned long a0, a1, a2; + bool done; cb_data->kaddr1 = NULL; cb_data->kaddr2 = NULL; @@ -402,34 +535,20 @@ static void svc_thread_cmd_config_status(struct stratix10_svc_controller *ctrl, pr_debug("%s: polling config status\n", __func__); - a0 = INTEL_SIP_SMC_FPGA_CONFIG_ISDONE; - a1 = (unsigned long)p_data->paddr; - a2 = (unsigned long)p_data->size; - - if (p_data->command == COMMAND_POLL_SERVICE_STATUS) - a0 = INTEL_SIP_SMC_SERVICE_COMPLETED; + done = svc_cmd_poll_status(p_data, ctrl, &res, SVC_POLL_COUNT_FAST, + SVC_POLL_INTERVAL_MS_FAST); + /* Fall back to a longer interval if still busy after the fast window */ + if (!done) + done = svc_cmd_poll_status(p_data, ctrl, &res, + SVC_POLL_COUNT_SLOW, + SVC_POLL_INTERVAL_MS_SLOW); - count_in_sec = FPGA_CONFIG_STATUS_TIMEOUT_SEC; - while (count_in_sec) { - ctrl->invoke_fn(a0, a1, a2, 0, 0, 0, 0, 0, &res); - if ((res.a0 == INTEL_SIP_SMC_STATUS_OK) || - (res.a0 == INTEL_SIP_SMC_STATUS_ERROR) || - (res.a0 == INTEL_SIP_SMC_STATUS_REJECTED)) - break; - - /* - * request is still in progress, wait one second then - * poll again - */ - msleep(1000); - count_in_sec--; - } - - if (!count_in_sec) { + if (!done) { pr_err("%s: poll status timeout\n", __func__); cb_data->status = BIT(SVC_STATUS_BUSY); } else if (res.a0 == INTEL_SIP_SMC_STATUS_OK) { cb_data->status = BIT(SVC_STATUS_COMPLETED); + cb_data->kaddr1 = &res.a1; cb_data->kaddr2 = (res.a2) ? svc_pa_to_va(res.a2) : NULL; cb_data->kaddr3 = (res.a3) ? &res.a3 : NULL; @@ -869,20 +988,11 @@ static int svc_normal_to_secure_thread(void *data) default: pr_warn("Secure firmware doesn't support...\n"); - /* - * be compatible with older version firmware which - * doesn't support newer RSU commands - */ - if ((pdata->command != COMMAND_RSU_UPDATE) && - (pdata->command != COMMAND_RSU_STATUS)) { - cbdata->status = - BIT(SVC_STATUS_NO_SUPPORT); - cbdata->kaddr1 = NULL; - cbdata->kaddr2 = NULL; - cbdata->kaddr3 = NULL; - pdata->chan->scl->receive_cb( - pdata->chan->scl, cbdata); - } + cbdata->status = BIT(SVC_STATUS_NO_SUPPORT); + cbdata->kaddr1 = NULL; + cbdata->kaddr2 = NULL; + cbdata->kaddr3 = NULL; + pdata->chan->scl->receive_cb(pdata->chan->scl, cbdata); break; } @@ -1038,6 +1148,38 @@ svc_create_memory_pool(struct platform_device *pdev, } /** + * svc_setup_dma_memory() - configure the device for dynamic DMA allocation + * @pdev: pointer to service layer device + * + * Called instead of svc_get_sh_memory() + svc_create_memory_pool() when + * the device is behind an SMMU. Sets a 29-bit coherent DMA mask so that + * every subsequent dma_alloc_coherent() call yields an IOVA within the + * first 512MB (0x0000_0000 - 0x1FFF_FFFF). The driver then adds + * %SVC_SDM_DMA_ADDR_OFFSET to the IOVA before passing it to ATF; ATF + * strips the offset and uses the SMMU to translate the IOVA to the + * underlying physical memory for SDM access. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int svc_setup_dma_memory(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(SVC_SDM_DMA_ADDR_BITS)); + if (ret) { + dev_err(dev, + "failed to set %u-bit DMA mask: %d\n", + SVC_SDM_DMA_ADDR_BITS, ret); + return ret; + } + + dev_info(dev, + "SMMU enabled: using dynamic DMA allocation (IOVA range 0-512MB)\n"); + return 0; +} + +/** * svc_smccc_smc() - secure monitor call between normal and secure world * @a0: argument passed in registers 0 * @a1: argument passed in registers 1 @@ -1596,7 +1738,7 @@ int stratix10_svc_async_poll(struct stratix10_svc_chan *chan, return -EAGAIN; } - dev_err(ctrl->dev, + dev_dbg(ctrl->dev, "Failed to poll async message ,got status as %ld\n", handle->res.a0); return -EINVAL; @@ -1659,14 +1801,96 @@ static inline void stratix10_smc_1_2(struct stratix10_async_ctrl *actrl, arm_smccc_1_2_smc(args, res); } +static irqreturn_t stratix10_svc_async_irq_handler(int irq, void *dev_id) +{ + struct stratix10_svc_controller *ctrl = dev_id; + struct stratix10_async_ctrl *actrl = &ctrl->actrl; + + /* + * Only disable the IRQ when work is newly queued. If the work is + * already pending/running, queue_work() returns false; an extra + * disable_irq_nosync() would leave the IRQ disabled permanently + * after enable_irq() in the work handler. + */ + if (queue_work(system_bh_wq, &actrl->async_work)) + disable_irq_nosync(irq); + return IRQ_HANDLED; +} + +/** + * stratix10_async_workqueue_handler - Handler for the asynchronous + * workqueue in Stratix10 service controller. + * @work: Pointer to the work structure that contains the asynchronous + * workqueue handler. + * + * This function is the handler for the asynchronous workqueue. It performs + * the following tasks: + * - Invokes the asynchronous polling on interrupt supervisory call. + * - On success, it retrieves the bitmap of pending transactions from mailbox + * fifo in ATF. + * - It processes each pending transaction by calling the corresponding + * callback function. + * + * The function ensures that the IRQ is enabled after processing the + * transactions. + */ +static void stratix10_async_workqueue_handler(struct work_struct *work) +{ + struct stratix10_async_ctrl *actrl = + container_of(work, struct stratix10_async_ctrl, async_work); + struct arm_smccc_1_2_regs + args = { .a0 = INTEL_SIP_SMC_ASYNC_POLL_ON_IRQ }, res; + DECLARE_BITMAP(pend_on_irq, TOTAL_TRANSACTION_IDS); + struct stratix10_svc_async_handler *handler; + unsigned long transaction_id = 0; + u64 bitmap_array[4]; + async_callback_t cb; + void *cb_arg; + + actrl->invoke_fn(actrl, &args, &res); + if (res.a0 == INTEL_SIP_SMC_STATUS_OK) { + bitmap_array[0] = res.a1; + bitmap_array[1] = res.a2; + bitmap_array[2] = res.a3; + bitmap_array[3] = res.a4; + bitmap_from_arr64(pend_on_irq, bitmap_array, TOTAL_TRANSACTION_IDS); + do { + transaction_id = find_next_bit(pend_on_irq, + TOTAL_TRANSACTION_IDS, + transaction_id); + if (transaction_id >= TOTAL_TRANSACTION_IDS) + break; + + cb = NULL; + cb_arg = NULL; + spin_lock(&actrl->trx_list_lock); + hash_for_each_possible(actrl->trx_list, handler, + next, transaction_id) { + if (handler->transaction_id == transaction_id) { + cb = handler->cb; + cb_arg = handler->cb_arg; + break; + } + } + spin_unlock(&actrl->trx_list_lock); + if (cb) + cb(cb_arg); + + transaction_id++; + } while (transaction_id < TOTAL_TRANSACTION_IDS); + } + enable_irq(actrl->irq); +} + /** * stratix10_svc_async_init - Initialize the Stratix10 service * controller for asynchronous operations. * @controller: Pointer to the Stratix10 service controller structure. * * This function initializes the asynchronous service controller by - * setting up the necessary data structures and initializing the - * transaction list. + * setting up the necessary data structures ,initializing the + * transaction list and registering the IRQ handler for asynchronous + * transactions. * * Return: 0 on success, -EINVAL if the controller is NULL or already * initialized, -ENOMEM if memory allocation fails, @@ -1679,7 +1903,7 @@ static int stratix10_svc_async_init(struct stratix10_svc_controller *controller) struct stratix10_async_ctrl *actrl; struct arm_smccc_res res; struct device *dev; - int ret; + int ret, irq; if (!controller) return -EINVAL; @@ -1727,6 +1951,22 @@ static int stratix10_svc_async_init(struct stratix10_svc_controller *controller) hash_init(actrl->trx_list); atomic_set(&actrl->common_achan_refcount, 0); + irq = of_irq_get(dev_of_node(dev), 0); + if (irq <= 0) { + dev_warn(dev, "Failed to get IRQ, falling back to polling mode\n"); + } else { + ret = devm_request_any_context_irq(dev, irq, stratix10_svc_async_irq_handler, + IRQF_NO_AUTOEN, "stratix10_svc", controller); + if (ret == 0) { + dev_info(dev, + "Registered IRQ %d for sip async operations\n", + irq); + actrl->irq = irq; + INIT_WORK(&actrl->async_work, stratix10_async_workqueue_handler); + enable_irq(actrl->irq); + } + } + actrl->initialized = true; return 0; } @@ -1736,13 +1976,14 @@ static int stratix10_svc_async_init(struct stratix10_svc_controller *controller) * service controller * @ctrl: Pointer to the stratix10_svc_controller structure * - * This function performs the necessary cleanup for the asynchronous - * service controller. It checks if the controller is valid and if it - * has been initialized. It then locks the transaction list and safely - * removes and deallocates each handler in the list. The function also - * removes any asynchronous clients associated with the controller's - * channels and destroys the asynchronous ID pool. Finally, it resets - * the asynchronous ID pool and invoke function pointers to NULL. + * This function performs the necessary cleanup for the asynchronous service + * controller. It checks if the controller is valid and if it has been + * initialized. Also If the controller has an IRQ assigned, it frees the IRQ + * and flushes any pending asynchronous work. It then locks the transaction + * list and safely removes and deallocates each handler in the list. + * The function also removes any asynchronous clients associated with the + * controller's channels and destroys the asynchronous ID pool. Finally, it + * resets the asynchronous ID pool and invoke function pointers to NULL. * * Return: 0 on success, -EINVAL if the controller is invalid or not * initialized. @@ -1764,6 +2005,13 @@ static int stratix10_svc_async_exit(struct stratix10_svc_controller *ctrl) actrl->initialized = false; + if (actrl->irq > 0) { + disable_irq(actrl->irq); + flush_work(&actrl->async_work); + devm_free_irq(ctrl->dev, actrl->irq, ctrl); + actrl->irq = 0; + } + scoped_guard(spinlock_bh, &actrl->trx_list_lock) { hash_for_each_safe(actrl->trx_list, i, tmp, handler, next) { ida_free(&handler->achan->job_id_pool, @@ -1943,34 +2191,50 @@ EXPORT_SYMBOL_GPL(stratix10_svc_done); void *stratix10_svc_allocate_memory(struct stratix10_svc_chan *chan, size_t size) { + struct stratix10_svc_controller *ctrl = chan->ctrl; struct stratix10_svc_data_mem *pmem; - unsigned long va; - phys_addr_t pa; - struct gen_pool *genpool = chan->ctrl->genpool; - size_t s = roundup(size, 1 << genpool->min_alloc_order); + struct gen_pool *genpool; + dma_addr_t dma_addr; + size_t s; + void *va; pmem = kzalloc_obj(*pmem); if (!pmem) return ERR_PTR(-ENOMEM); - guard(mutex)(&svc_mem_lock); - va = gen_pool_alloc(genpool, s); - if (!va) { - kfree(pmem); - return ERR_PTR(-ENOMEM); - } + if (ctrl->use_dma_mem) { + va = dma_alloc_coherent(ctrl->dev, size, &dma_addr, GFP_KERNEL); + if (!va) { + kfree(pmem); + return ERR_PTR(-ENOMEM); + } - memset((void *)va, 0, s); - pa = gen_pool_virt_to_phys(genpool, va); + pmem->vaddr = va; + pmem->paddr = dma_addr + ctrl->dma_addr_offset; + pmem->dma_addr = dma_addr; + pmem->size = size; + } else { + genpool = ctrl->genpool; + s = roundup(size, 1 << genpool->min_alloc_order); + + va = (void *)gen_pool_alloc(genpool, s); + if (!va) { + kfree(pmem); + return ERR_PTR(-ENOMEM); + } + + memset(va, 0, s); + pmem->vaddr = va; + pmem->paddr = gen_pool_virt_to_phys(genpool, (unsigned long)va); + pmem->size = s; + } - pmem->vaddr = (void *)va; - pmem->paddr = pa; - pmem->size = s; + guard(mutex)(&svc_mem_lock); list_add_tail(&pmem->node, &svc_data_mem); - pr_debug("%s: %s: va=%p, pa=0x%016x\n", __func__, - chan->name, pmem->vaddr, (unsigned int)pmem->paddr); + pr_debug("%s: %s: va=%p, addr=0x%016llx\n", __func__, + chan->name, pmem->vaddr, (unsigned long long)pmem->paddr); - return (void *)va; + return va; } EXPORT_SYMBOL_GPL(stratix10_svc_allocate_memory); @@ -1983,25 +2247,87 @@ EXPORT_SYMBOL_GPL(stratix10_svc_allocate_memory); */ void stratix10_svc_free_memory(struct stratix10_svc_chan *chan, void *kaddr) { + struct stratix10_svc_controller *ctrl = chan->ctrl; struct stratix10_svc_data_mem *pmem; guard(mutex)(&svc_mem_lock); - list_for_each_entry(pmem, &svc_data_mem, node) - if (pmem->vaddr == kaddr) { - gen_pool_free(chan->ctrl->genpool, - (unsigned long)kaddr, pmem->size); - pmem->vaddr = NULL; - list_del(&pmem->node); - kfree(pmem); - return; + list_for_each_entry(pmem, &svc_data_mem, node) { + if (pmem->vaddr != kaddr) + continue; + + if (ctrl->use_dma_mem) { + dma_free_coherent(ctrl->dev, pmem->size, + pmem->vaddr, pmem->dma_addr); + } else { + gen_pool_free(ctrl->genpool, + (unsigned long)kaddr, pmem->size); } + list_del(&pmem->node); + kfree(pmem); + return; + } + + dev_warn(ctrl->dev, "free of unknown buffer %p\n", kaddr); } EXPORT_SYMBOL_GPL(stratix10_svc_free_memory); +static void psci_offline_secondary_cpus(struct stratix10_svc_controller *ctrl) +{ + cpumask_var_t mask; + int cpu, ret; + + if (!alloc_cpumask_var(&mask, GFP_KERNEL)) + return; + + /* + * Snapshot cpu_online_mask before the loop; remove_cpu() modifies it + * as each CPU is brought down. Always preserve CPU 0 (boot CPU) to + * run the reboot. + */ + cpumask_copy(mask, cpu_online_mask); + cpumask_clear_cpu(0, mask); + + /* + * Offlining is best-effort: if a CPU refuses to go down we log the + * error and continue so the remaining secondaries are still attempted + * and the warm reboot can proceed. + */ + for_each_cpu(cpu, mask) { + ret = remove_cpu(cpu); + if (ret) + dev_err(ctrl->dev, + "psci_cpu_off: failed to offline CPU%d: %d\n", + cpu, ret); + } + + free_cpumask_var(mask); +} + +static int psci_cpu_off_reboot_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct stratix10_svc_controller *ctrl = + container_of(nb, struct stratix10_svc_controller, psci_reboot_nb); + + if (reboot_mode != REBOOT_WARM) + return NOTIFY_DONE; + + if (action == SYS_RESTART) + psci_offline_secondary_cpus(ctrl); + + return NOTIFY_OK; +} + +static void psci_cpu_off_teardown(struct stratix10_svc_controller *ctrl) +{ + unregister_reboot_notifier(&ctrl->psci_reboot_nb); +} + static const struct of_device_id stratix10_svc_drv_match[] = { - {.compatible = "intel,stratix10-svc"}, - {.compatible = "intel,agilex-svc"}, + { .compatible = "intel,stratix10-svc", .data = &psci_cpu_off_pdata }, + { .compatible = "intel,agilex-svc", .data = &psci_cpu_off_pdata }, + { .compatible = "intel,agilex5-svc", .data = &agilex5_pdata }, {}, }; @@ -2012,13 +2338,38 @@ static const char * const chan_names[SVC_NUM_CHANNEL] = { SVC_CLIENT_HWMON }; +static void svc_data_mem_cleanup(void *data) +{ + struct stratix10_svc_controller *ctrl = data; + struct stratix10_svc_data_mem *pmem, *tmp; + + guard(mutex)(&svc_mem_lock); + + list_for_each_entry_safe(pmem, tmp, &svc_data_mem, node) { + dev_warn(ctrl->dev, "leaked svc buffer %p, freeing on unbind\n", + pmem->vaddr); + if (ctrl->use_dma_mem) { + dma_free_coherent(ctrl->dev, pmem->size, + pmem->vaddr, pmem->dma_addr); + } else { + gen_pool_free(ctrl->genpool, + (unsigned long)pmem->vaddr, pmem->size); + } + list_del(&pmem->node); + kfree(pmem); + } +} + static int stratix10_svc_drv_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct stratix10_svc_controller *controller; - struct gen_pool *genpool; + struct gen_pool *genpool = NULL; struct stratix10_svc_sh_memory *sh_memory; struct stratix10_svc *svc = NULL; + const struct stratix10_svc_pdata *pdata = of_device_get_match_data(dev); + struct arm_smccc_res res; + bool use_dma_mem = false; svc_invoke_fn *invoke_fn; size_t fifo_size; @@ -2029,18 +2380,38 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev) if (IS_ERR(invoke_fn)) return -EINVAL; - sh_memory = devm_kzalloc(dev, sizeof(*sh_memory), GFP_KERNEL); - if (!sh_memory) - return -ENOMEM; + use_dma_mem = pdata && pdata->use_dma_mem; - sh_memory->invoke_fn = invoke_fn; - ret = svc_get_sh_memory(pdev, sh_memory); - if (ret) - return ret; + if (use_dma_mem) { + if (!iommu_get_domain_for_dev(dev)) { + dev_err(dev, + "SMMU is required for agilex5-svc but no IOMMU domain found\n"); + dev_err(dev, + "Ensure the SMMU node is enabled in the device tree and 'iommus' is set for this node\n"); + return -ENODEV; + } - genpool = svc_create_memory_pool(pdev, sh_memory); - if (IS_ERR(genpool)) - return PTR_ERR(genpool); + invoke_fn(INTEL_SIP_SMC_SDM_REMAPPER_CONFIG, + INTEL_SIP_SMC_SDM_REMAPPER_BYPASS, + 0, 0, 0, 0, 0, 0, &res); + + ret = svc_setup_dma_memory(pdev); + if (ret) + return ret; + } else { + sh_memory = devm_kzalloc(dev, sizeof(*sh_memory), GFP_KERNEL); + if (!sh_memory) + return -ENOMEM; + + sh_memory->invoke_fn = invoke_fn; + ret = svc_get_sh_memory(pdev, sh_memory); + if (ret) + return ret; + + genpool = svc_create_memory_pool(pdev, sh_memory); + if (IS_ERR(genpool)) + return PTR_ERR(genpool); + } /* allocate service controller and supporting channel */ controller = devm_kzalloc(dev, struct_size(controller, chans, SVC_NUM_CHANNEL), @@ -2055,16 +2426,36 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev) controller->num_active_client = 0; controller->genpool = genpool; controller->invoke_fn = invoke_fn; + controller->use_dma_mem = use_dma_mem; + controller->dma_addr_offset = use_dma_mem ? SVC_SDM_DMA_ADDR_OFFSET : 0; INIT_LIST_HEAD(&controller->node); init_completion(&controller->complete_status); + if (use_dma_mem) { + ret = devm_add_action_or_reset(dev, svc_data_mem_cleanup, controller); + if (ret) + goto err_destroy_pool; + } + + if (pdata && pdata->needs_psci_cpu_off) { + controller->psci_reboot_nb.notifier_call = + psci_cpu_off_reboot_notifier; + controller->psci_reboot_nb.priority = INT_MAX; + + ret = register_reboot_notifier(&controller->psci_reboot_nb); + if (ret) { + dev_err(dev, "failed to register reboot notifier: %d\n", ret); + goto err_destroy_pool; + } + } + ret = stratix10_svc_async_init(controller); if (ret == -EOPNOTSUPP) { dev_info(dev, "Intel Service Layer Driver Initialized (sync-only mode)\n"); } else if (ret) { dev_dbg(dev, "Intel Service Layer Driver: Error on stratix10_svc_async_init %d\n", ret); - goto err_destroy_pool; + goto err_free_notifier; } else { dev_info(dev, "Intel Service Layer Driver Initialized\n"); } @@ -2150,8 +2541,11 @@ err_free_fifos: while (i--) kfifo_free(&controller->chans[i].svc_fifo); stratix10_svc_async_exit(controller); +err_free_notifier: + psci_cpu_off_teardown(controller); err_destroy_pool: - gen_pool_destroy(genpool); + if (genpool) + gen_pool_destroy(genpool); return ret; } @@ -2166,6 +2560,8 @@ static void stratix10_svc_drv_remove(struct platform_device *pdev) if (svc->stratix10_svc_hwmon) platform_device_unregister(svc->stratix10_svc_hwmon); + psci_cpu_off_teardown(ctrl); + stratix10_svc_async_exit(ctrl); of_platform_depopulate(ctrl->dev); diff --git a/drivers/fpga/altera-cvp.c b/drivers/fpga/altera-cvp.c index 070c025ee215..507e29d3a79f 100644 --- a/drivers/fpga/altera-cvp.c +++ b/drivers/fpga/altera-cvp.c @@ -280,6 +280,16 @@ static int altera_cvp_send_block(struct altera_cvp_conf *conf, return 0; } +static void altera_cvp_disable_cvp_mode(struct altera_cvp_conf *conf) +{ + u32 val; + + altera_read_config_dword(conf, VSE_CVP_MODE_CTRL, &val); + val &= ~VSE_CVP_MODE_CTRL_HIP_CLK_SEL; + val &= ~VSE_CVP_MODE_CTRL_CVP_MODE; + altera_write_config_dword(conf, VSE_CVP_MODE_CTRL, val); +} + static int altera_cvp_teardown(struct fpga_manager *mgr, struct fpga_image_info *info) { @@ -308,7 +318,29 @@ static int altera_cvp_teardown(struct fpga_manager *mgr, ret = altera_cvp_wait_status(conf, VSE_CVP_STATUS_CFG_RDY, 0, conf->priv->poll_time_us); if (ret) - dev_err(&mgr->dev, "CFG_RDY == 0 timeout\n"); + dev_warn(&mgr->dev, "CFG_RDY == 0 timeout\n"); + + return ret; +} + +static int altera_cvp_recovery(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct altera_cvp_conf *conf = mgr->priv; + int ret; + + ret = altera_cvp_teardown(mgr, info); + if (ret) { + /* + * CVP_STATUS may get stuck if the device left user mode + * before the poll. Exit CvP mode to switch back the clock + * feeding CVP_STATUS, then try the teardown again. + */ + altera_cvp_disable_cvp_mode(conf); + ret = altera_cvp_teardown(mgr, info); + if (ret) + dev_err(&mgr->dev, "Tear-down failed\n"); + } return ret; } @@ -345,7 +377,7 @@ static int altera_cvp_write_init(struct fpga_manager *mgr, if (val & VSE_CVP_STATUS_CFG_RDY) { dev_warn(&mgr->dev, "CvP already started, tear down first\n"); - ret = altera_cvp_teardown(mgr, info); + ret = altera_cvp_recovery(mgr, info); if (ret) return ret; } @@ -486,7 +518,7 @@ static int altera_cvp_write_complete(struct fpga_manager *mgr, u32 mask, val; int ret; - ret = altera_cvp_teardown(mgr, info); + ret = altera_cvp_recovery(mgr, info); if (ret) return ret; @@ -498,10 +530,7 @@ static int altera_cvp_write_complete(struct fpga_manager *mgr, } /* STEP 17 - reset CVP_MODE and HIP_CLK_SEL bit */ - altera_read_config_dword(conf, VSE_CVP_MODE_CTRL, &val); - val &= ~VSE_CVP_MODE_CTRL_HIP_CLK_SEL; - val &= ~VSE_CVP_MODE_CTRL_CVP_MODE; - altera_write_config_dword(conf, VSE_CVP_MODE_CTRL, val); + altera_cvp_disable_cvp_mode(conf); /* STEP 18 - poll PLD_CLK_IN_USE and USER_MODE bits */ mask = VSE_CVP_STATUS_PLD_CLK_IN_USE | VSE_CVP_STATUS_USERMODE; diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h index 9549f63b00eb..9a65617bb870 100644 --- a/drivers/fpga/dfl.h +++ b/drivers/fpga/dfl.h @@ -23,7 +23,6 @@ #include <linux/interrupt.h> #include <linux/iopoll.h> #include <linux/io-64-nonatomic-lo-hi.h> -#include <linux/mod_devicetable.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/uuid.h> diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index 2672070ced02..5a0b7780b335 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -744,7 +744,7 @@ EXPORT_SYMBOL_GPL(fpga_mgr_put); * @mgr: fpga manager * * Given a pointer to FPGA Manager (from fpga_mgr_get() or - * of_fpga_mgr_put()) attempt to get the mutex. The user should call + * of_fpga_mgr_get()) attempt to get the mutex. The user should call * fpga_mgr_lock() and verify that it returns 0 before attempting to * program the FPGA. Likewise, the user should call fpga_mgr_unlock * when done programming the FPGA. diff --git a/drivers/fpga/stratix10-soc.c b/drivers/fpga/stratix10-soc.c index b8ec2e6f615f..5aed560050c2 100644 --- a/drivers/fpga/stratix10-soc.c +++ b/drivers/fpga/stratix10-soc.c @@ -286,7 +286,7 @@ static int s10_ops_write(struct fpga_manager *mgr, const char *buf, /* * Loop waiting for buffers to be returned. When a buffer is returned, - * reuse it to send more data or free if if all data has been sent. + * reuse it to send more data or free it if all data has been sent. */ while (count > 0 || s10_free_buffer_count(mgr) != NUM_SVC_BUFS) { reinit_completion(&priv->status_return_completion); diff --git a/drivers/fpga/xilinx-selectmap.c b/drivers/fpga/xilinx-selectmap.c index d0cbb5fdfe3a..ff167b23fded 100644 --- a/drivers/fpga/xilinx-selectmap.c +++ b/drivers/fpga/xilinx-selectmap.c @@ -19,6 +19,8 @@ struct xilinx_selectmap_conf { struct xilinx_fpga_core core; void __iomem *base; + struct gpio_desc *csi_b; + struct gpio_desc *rdwr_b; }; #define to_xilinx_selectmap_conf(obj) \ @@ -30,16 +32,28 @@ static int xilinx_selectmap_write(struct xilinx_fpga_core *core, struct xilinx_selectmap_conf *conf = to_xilinx_selectmap_conf(core); size_t i; + /* + * Select this device and put its SelectMAP port into write mode. + * + * RDWR_B is set before CSI_B. UG470 describes in chapter + * "SelectMAP ABORT" that changing RDWR_B while the device is + * selected aborts the configuration on the next CCLK. + */ + gpiod_set_value(conf->rdwr_b, 1); + gpiod_set_value(conf->csi_b, 1); + for (i = 0; i < count; ++i) writeb(buf[i], conf->base); + gpiod_set_value(conf->csi_b, 0); + gpiod_set_value(conf->rdwr_b, 0); + return 0; } static int xilinx_selectmap_probe(struct platform_device *pdev) { struct xilinx_selectmap_conf *conf; - struct gpio_desc *gpio; void __iomem *base; conf = devm_kzalloc(&pdev->dev, sizeof(*conf), GFP_KERNEL); @@ -55,16 +69,23 @@ static int xilinx_selectmap_probe(struct platform_device *pdev) "ioremap error\n"); conf->base = base; - /* CSI_B is active low */ - gpio = devm_gpiod_get_optional(&pdev->dev, "csi", GPIOD_OUT_HIGH); - if (IS_ERR(gpio)) - return dev_err_probe(&pdev->dev, PTR_ERR(gpio), + /* + * Request both signals deasserted, so a device sharing the SelectMAP + * bus with others stays off that bus until its bitstream is written. + * + * The value in the gpiod flags is logical, gpiolib drives the line + * high for GPIOD_OUT_LOW when the firmware describes it active low. + */ + conf->csi_b = devm_gpiod_get_optional(&pdev->dev, "csi", + GPIOD_OUT_LOW); + if (IS_ERR(conf->csi_b)) + return dev_err_probe(&pdev->dev, PTR_ERR(conf->csi_b), "Failed to get CSI_B gpio\n"); - /* RDWR_B is active low */ - gpio = devm_gpiod_get_optional(&pdev->dev, "rdwr", GPIOD_OUT_HIGH); - if (IS_ERR(gpio)) - return dev_err_probe(&pdev->dev, PTR_ERR(gpio), + conf->rdwr_b = devm_gpiod_get_optional(&pdev->dev, "rdwr", + GPIOD_OUT_LOW); + if (IS_ERR(conf->rdwr_b)) + return dev_err_probe(&pdev->dev, PTR_ERR(conf->rdwr_b), "Failed to get RDWR_B gpio\n"); return xilinx_core_probe(&conf->core); diff --git a/drivers/greybus/greybus_trace.h b/drivers/greybus/greybus_trace.h index 616a3bd61aa6..3c331e09ea83 100644 --- a/drivers/greybus/greybus_trace.h +++ b/drivers/greybus/greybus_trace.h @@ -339,7 +339,7 @@ DEFINE_INTERFACE_EVENT(gb_interface_create); DEFINE_INTERFACE_EVENT(gb_interface_release); /* - * Occurs after an interface been registerd. + * Occurs after an interface been registered. */ DEFINE_INTERFACE_EVENT(gb_interface_add); diff --git a/drivers/hwtracing/coresight/coresight-config.c b/drivers/hwtracing/coresight/coresight-config.c index 4f72ae71b696..0e296289f286 100644 --- a/drivers/hwtracing/coresight/coresight-config.c +++ b/drivers/hwtracing/coresight/coresight-config.c @@ -73,13 +73,12 @@ static void cscfg_init_reg_param(struct cscfg_feature_csdev *feat_csdev, /* set values into the driver locations referenced in cscfg_reg_csdev */ static int cscfg_set_on_enable(struct cscfg_feature_csdev *feat_csdev) { - unsigned long flags; int i; - raw_spin_lock_irqsave(feat_csdev->drv_spinlock, flags); - for (i = 0; i < feat_csdev->nr_regs; i++) - cscfg_set_reg(&feat_csdev->regs_csdev[i]); - raw_spin_unlock_irqrestore(feat_csdev->drv_spinlock, flags); + scoped_guard(feat_csdev_lock, feat_csdev) { + for (i = 0; i < feat_csdev->nr_regs; i++) + cscfg_set_reg(&feat_csdev->regs_csdev[i]); + } dev_dbg(&feat_csdev->csdev->dev, "Feature %s: %s", feat_csdev->feat_desc->name, "set on enable"); return 0; @@ -88,13 +87,12 @@ static int cscfg_set_on_enable(struct cscfg_feature_csdev *feat_csdev) /* copy back values from the driver locations referenced in cscfg_reg_csdev */ static void cscfg_save_on_disable(struct cscfg_feature_csdev *feat_csdev) { - unsigned long flags; int i; - raw_spin_lock_irqsave(feat_csdev->drv_spinlock, flags); - for (i = 0; i < feat_csdev->nr_regs; i++) - cscfg_save_reg(&feat_csdev->regs_csdev[i]); - raw_spin_unlock_irqrestore(feat_csdev->drv_spinlock, flags); + scoped_guard(feat_csdev_lock, feat_csdev) { + for (i = 0; i < feat_csdev->nr_regs; i++) + cscfg_save_reg(&feat_csdev->regs_csdev[i]); + } dev_dbg(&feat_csdev->csdev->dev, "Feature %s: %s", feat_csdev->feat_desc->name, "save on disable"); } diff --git a/drivers/hwtracing/coresight/coresight-config.h b/drivers/hwtracing/coresight/coresight-config.h index 90fd937d3bd8..ae6295e30813 100644 --- a/drivers/hwtracing/coresight/coresight-config.h +++ b/drivers/hwtracing/coresight/coresight-config.h @@ -7,6 +7,7 @@ #ifndef _CORESIGHT_CORESIGHT_CONFIG_H #define _CORESIGHT_CORESIGHT_CONFIG_H +#include <linux/cleanup.h> #include <linux/coresight.h> #include <linux/types.h> @@ -259,4 +260,25 @@ void cscfg_csdev_disable_config(struct cscfg_config_csdev *config_csdev); /* reset a feature to default values */ void cscfg_reset_feat(struct cscfg_feature_csdev *feat_csdev); +#define feat_csdev_lock(feat_csdev, flags) \ + do { \ + raw_spinlock_t *__lock = feat_csdev->drv_spinlock; \ + typecheck(unsigned long, flags); \ + if (__lock) \ + raw_spin_lock_irqsave(__lock, flags); \ + } while (0) + +#define feat_csdev_unlock(feat_csdev, flags) \ + do { \ + raw_spinlock_t *__lock = feat_csdev->drv_spinlock; \ + typecheck(unsigned long, flags); \ + if (__lock) \ + raw_spin_unlock_irqrestore(__lock, flags); \ + } while (0) + +DEFINE_LOCK_GUARD_1(feat_csdev_lock, struct cscfg_feature_csdev, + feat_csdev_lock(_T->lock, _T->flags), + feat_csdev_unlock(_T->lock, _T->flags), + unsigned long flags) + #endif /* _CORESIGHT_CORESIGHT_CONFIG_H */ diff --git a/drivers/hwtracing/coresight/coresight-core.c b/drivers/hwtracing/coresight/coresight-core.c index 928488a216d4..d2f9c1fb77d9 100644 --- a/drivers/hwtracing/coresight/coresight-core.c +++ b/drivers/hwtracing/coresight/coresight-core.c @@ -1543,6 +1543,7 @@ coresight_init_device(struct coresight_desc *desc) csdev->ops = desc->ops; csdev->access = desc->access; csdev->orphan = true; + csdev->flags = desc->flags; if (desc->flags & CORESIGHT_DESC_CPU_BOUND) { csdev->cpu = desc->cpu; diff --git a/drivers/hwtracing/coresight/coresight-etm.h b/drivers/hwtracing/coresight/coresight-etm.h index 1d753cca2943..cf65d89a8e98 100644 --- a/drivers/hwtracing/coresight/coresight-etm.h +++ b/drivers/hwtracing/coresight/coresight-etm.h @@ -141,6 +141,30 @@ ETM_EVENT_NOT_A) /** + * struct etm_caps - specifics ETM capabilities + * @port_size: port size as reported by ETMCR bit 4-6 and 21. + * @nr_addr_cmp:Number of pairs of address comparators as found in ETMCCR. + * @nr_cntr: Number of counters as found in ETMCCR bit 13-15. + * @nr_ext_inp: Number of external input as found in ETMCCR bit 17-19. + * @nr_ext_out: Number of external output as found in ETMCCR bit 20-22. + * @nr_ctxid_cmp: Number of contextID comparators as found in ETMCCR bit 24-25. + * @fifofull: FIFOFULL logic is present. + * @timestamp: Timestamping is implemented. + * @retstack: Return stack is implemented. + */ +struct etm_caps { + int port_size; + u8 nr_addr_cmp; + u8 nr_cntr; + u8 nr_ext_inp; + u8 nr_ext_out; + u8 nr_ctxid_cmp; + bool fifofull : 1; + bool timestamp : 1; + bool retstack : 1; +}; + +/** * struct etm_config - configuration information related to an ETM * @mode: controls various modes supported by this ETM/PTM. * @ctrl: used in conjunction with @mode. @@ -212,21 +236,15 @@ struct etm_config { * @csdev: component vitals needed by the framework. * @spinlock: only one at a time pls. * @cpu: the cpu this component is affined to. - * @port_size: port size as reported by ETMCR bit 4-6 and 21. * @arch: ETM/PTM version number. + * @caps: ETM capabilities. * @use_cpu14: true if management registers need to be accessed via CP14. * @sticky_enable: true if ETM base configuration has been done. * @boot_enable:true if we should start tracing at boot time. * @os_unlock: true if access to management registers is allowed. - * @nr_addr_cmp:Number of pairs of address comparators as found in ETMCCR. - * @nr_cntr: Number of counters as found in ETMCCR bit 13-15. - * @nr_ext_inp: Number of external input as found in ETMCCR bit 17-19. - * @nr_ext_out: Number of external output as found in ETMCCR bit 20-22. - * @nr_ctxid_cmp: Number of contextID comparators as found in ETMCCR bit 24-25. - * @etmccr: value of register ETMCCR. - * @etmccer: value of register ETMCCER. * @traceid: value of the current ID for this component. - * @config: structure holding configuration parameters. + * @curr_config: structure holding current running configuration. + * @sysfs_config: structure holding sysfs mode configuration. */ struct etm_drvdata { struct csdev_access csa; @@ -234,21 +252,15 @@ struct etm_drvdata { struct coresight_device *csdev; spinlock_t spinlock; int cpu; - int port_size; u8 arch; + struct etm_caps caps; bool use_cp14; bool sticky_enable; bool boot_enable; bool os_unlock; - u8 nr_addr_cmp; - u8 nr_cntr; - u8 nr_ext_inp; - u8 nr_ext_out; - u8 nr_ctxid_cmp; - u32 etmccr; - u32 etmccer; u32 traceid; - struct etm_config config; + struct etm_config curr_config; + struct etm_config sysfs_config; }; static inline void etm_writel(struct etm_drvdata *drvdata, diff --git a/drivers/hwtracing/coresight/coresight-etm3x-core.c b/drivers/hwtracing/coresight/coresight-etm3x-core.c index 862ad0786699..b670a324bb41 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-core.c @@ -308,7 +308,8 @@ void etm_config_trace_mode(struct etm_config *config) static int etm_parse_event_config(struct etm_drvdata *drvdata, struct perf_event *event) { - struct etm_config *config = &drvdata->config; + const struct etm_caps *caps = &drvdata->caps; + struct etm_config *curr_config = &drvdata->curr_config; struct perf_event_attr *attr = &event->attr; u8 ts_level; @@ -316,28 +317,28 @@ static int etm_parse_event_config(struct etm_drvdata *drvdata, return -EINVAL; /* Clear configuration from previous run */ - memset(config, 0, sizeof(struct etm_config)); + memset(curr_config, 0, sizeof(struct etm_config)); if (attr->exclude_kernel) - config->mode = ETM_MODE_EXCL_KERN; + curr_config->mode = ETM_MODE_EXCL_KERN; if (attr->exclude_user) - config->mode = ETM_MODE_EXCL_USER; + curr_config->mode = ETM_MODE_EXCL_USER; /* Always start from the default config */ - etm_set_default(config); + etm_set_default(curr_config); /* * By default the tracers are configured to trace the whole address * range. Narrow the field only if requested by user space. */ - if (config->mode) - etm_config_trace_mode(config); + if (curr_config->mode) + etm_config_trace_mode(curr_config); - config->ctrl = 0; + curr_config->ctrl = 0; if (ATTR_CFG_GET_FLD(attr, cycacc)) - config->ctrl |= ETMCR_CYC_ACC; + curr_config->ctrl |= ETMCR_CYC_ACC; ts_level = max(ATTR_CFG_GET_FLD(attr, timestamp), ATTR_CFG_GET_FLD(attr, deprecated_timestamp)); @@ -349,16 +350,15 @@ static int etm_parse_event_config(struct etm_drvdata *drvdata, } if (ts_level) - config->ctrl |= ETMCR_TIMESTAMP_EN; + curr_config->ctrl |= ETMCR_TIMESTAMP_EN; /* * Possible to have cores with PTM (supports ret stack) and ETM (never * has ret stack) on the same SoC. So only enable when it can be honored * - trace will still continue normally otherwise. */ - if (ATTR_CFG_GET_FLD(attr, retstack) && - (drvdata->etmccer & ETMCCER_RETSTACK)) - config->ctrl |= ETMCR_RETURN_STACK; + if (ATTR_CFG_GET_FLD(attr, retstack) && (caps->retstack)) + curr_config->ctrl |= ETMCR_RETURN_STACK; return 0; } @@ -367,7 +367,8 @@ static int etm_enable_hw(struct etm_drvdata *drvdata) { int i, rc; u32 etmcr; - struct etm_config *config = &drvdata->config; + const struct etm_caps *caps = &drvdata->caps; + struct etm_config *curr_config = &drvdata->curr_config; struct coresight_device *csdev = drvdata->csdev; CS_UNLOCK(drvdata->csa.base); @@ -388,41 +389,41 @@ static int etm_enable_hw(struct etm_drvdata *drvdata) etmcr = etm_readl(drvdata, ETMCR); /* Clear setting from a previous run if need be */ etmcr &= ~ETM3X_SUPPORTED_OPTIONS; - etmcr |= drvdata->port_size; + etmcr |= caps->port_size; etmcr |= ETMCR_ETM_EN; - etm_writel(drvdata, config->ctrl | etmcr, ETMCR); - etm_writel(drvdata, config->trigger_event, ETMTRIGGER); - etm_writel(drvdata, config->startstop_ctrl, ETMTSSCR); - etm_writel(drvdata, config->enable_event, ETMTEEVR); - etm_writel(drvdata, config->enable_ctrl1, ETMTECR1); - etm_writel(drvdata, config->fifofull_level, ETMFFLR); - for (i = 0; i < drvdata->nr_addr_cmp; i++) { - etm_writel(drvdata, config->addr_val[i], ETMACVRn(i)); - etm_writel(drvdata, config->addr_acctype[i], ETMACTRn(i)); + etm_writel(drvdata, curr_config->ctrl | etmcr, ETMCR); + etm_writel(drvdata, curr_config->trigger_event, ETMTRIGGER); + etm_writel(drvdata, curr_config->startstop_ctrl, ETMTSSCR); + etm_writel(drvdata, curr_config->enable_event, ETMTEEVR); + etm_writel(drvdata, curr_config->enable_ctrl1, ETMTECR1); + etm_writel(drvdata, curr_config->fifofull_level, ETMFFLR); + for (i = 0; i < caps->nr_addr_cmp; i++) { + etm_writel(drvdata, curr_config->addr_val[i], ETMACVRn(i)); + etm_writel(drvdata, curr_config->addr_acctype[i], ETMACTRn(i)); } - for (i = 0; i < drvdata->nr_cntr; i++) { - etm_writel(drvdata, config->cntr_rld_val[i], ETMCNTRLDVRn(i)); - etm_writel(drvdata, config->cntr_event[i], ETMCNTENRn(i)); - etm_writel(drvdata, config->cntr_rld_event[i], + for (i = 0; i < caps->nr_cntr; i++) { + etm_writel(drvdata, curr_config->cntr_rld_val[i], ETMCNTRLDVRn(i)); + etm_writel(drvdata, curr_config->cntr_event[i], ETMCNTENRn(i)); + etm_writel(drvdata, curr_config->cntr_rld_event[i], ETMCNTRLDEVRn(i)); - etm_writel(drvdata, config->cntr_val[i], ETMCNTVRn(i)); + etm_writel(drvdata, curr_config->cntr_val[i], ETMCNTVRn(i)); } - etm_writel(drvdata, config->seq_12_event, ETMSQ12EVR); - etm_writel(drvdata, config->seq_21_event, ETMSQ21EVR); - etm_writel(drvdata, config->seq_23_event, ETMSQ23EVR); - etm_writel(drvdata, config->seq_31_event, ETMSQ31EVR); - etm_writel(drvdata, config->seq_32_event, ETMSQ32EVR); - etm_writel(drvdata, config->seq_13_event, ETMSQ13EVR); - etm_writel(drvdata, config->seq_curr_state, ETMSQR); - for (i = 0; i < drvdata->nr_ext_out; i++) + etm_writel(drvdata, curr_config->seq_12_event, ETMSQ12EVR); + etm_writel(drvdata, curr_config->seq_21_event, ETMSQ21EVR); + etm_writel(drvdata, curr_config->seq_23_event, ETMSQ23EVR); + etm_writel(drvdata, curr_config->seq_31_event, ETMSQ31EVR); + etm_writel(drvdata, curr_config->seq_32_event, ETMSQ32EVR); + etm_writel(drvdata, curr_config->seq_13_event, ETMSQ13EVR); + etm_writel(drvdata, curr_config->seq_curr_state, ETMSQR); + for (i = 0; i < caps->nr_ext_out; i++) etm_writel(drvdata, ETM_DEFAULT_EVENT_VAL, ETMEXTOUTEVRn(i)); - for (i = 0; i < drvdata->nr_ctxid_cmp; i++) - etm_writel(drvdata, config->ctxid_pid[i], ETMCIDCVRn(i)); - etm_writel(drvdata, config->ctxid_mask, ETMCIDCMR); - etm_writel(drvdata, config->sync_freq, ETMSYNCFR); + for (i = 0; i < caps->nr_ctxid_cmp; i++) + etm_writel(drvdata, curr_config->ctxid_pid[i], ETMCIDCVRn(i)); + etm_writel(drvdata, curr_config->ctxid_mask, ETMCIDCMR); + etm_writel(drvdata, curr_config->sync_freq, ETMSYNCFR); /* No external input selected */ etm_writel(drvdata, 0x0, ETMEXTINSELR); - etm_writel(drvdata, config->timestamp_event, ETMTSEVR); + etm_writel(drvdata, curr_config->timestamp_event, ETMTSEVR); /* No auxiliary control selected */ etm_writel(drvdata, 0x0, ETMAUXCR); etm_writel(drvdata, drvdata->traceid, ETMTRACEIDR); @@ -448,26 +449,22 @@ struct etm_enable_arg { static void etm_enable_sysfs_smp_call(void *info) { struct etm_enable_arg *arg = info; + struct etm_drvdata *drvdata; struct coresight_device *csdev; if (WARN_ON(!arg)) return; - csdev = arg->drvdata->csdev; - if (!coresight_take_mode(csdev, CS_MODE_SYSFS)) { - /* Someone is already using the tracer */ - arg->rc = -EBUSY; - return; - } + drvdata = arg->drvdata; + csdev = drvdata->csdev; - arg->rc = etm_enable_hw(arg->drvdata); + drvdata->traceid = arg->path->trace_id; - /* The tracer didn't start */ - if (arg->rc) { - coresight_set_mode(csdev, CS_MODE_DISABLED); + arg->rc = etm_enable_hw(arg->drvdata); + if (arg->rc) return; - } + drvdata->sticky_enable = true; csdev->path = arg->path; } @@ -512,34 +509,33 @@ static int etm_enable_sysfs(struct coresight_device *csdev, struct coresight_pat struct etm_enable_arg arg = { }; int ret; - spin_lock(&drvdata->spinlock); - - drvdata->traceid = path->trace_id; - - /* - * Configure the ETM only if the CPU is online. If it isn't online - * hw configuration will take place on the local CPU during bring up. - */ - if (cpu_online(drvdata->cpu)) { - arg.drvdata = drvdata; - arg.path = path; - ret = smp_call_function_single(drvdata->cpu, - etm_enable_sysfs_smp_call, &arg, 1); - if (!ret) - ret = arg.rc; - if (!ret) - drvdata->sticky_enable = true; - } else { - ret = -ENODEV; + if (!coresight_take_mode(csdev, CS_MODE_SYSFS)) { + /* Someone is already using the tracer */ + return -EBUSY; } - if (ret) - etm_release_trace_id(drvdata); + arg.drvdata = drvdata; + arg.path = path; - spin_unlock(&drvdata->spinlock); + scoped_guard(spinlock, &drvdata->spinlock) { + drvdata->curr_config = drvdata->sysfs_config; + } + ret = smp_call_function_single(drvdata->cpu, + etm_enable_sysfs_smp_call, &arg, 1); if (!ret) + ret = arg.rc; + else + ret = -ENODEV; + + if (!ret) { dev_dbg(&csdev->dev, "ETM tracing enabled\n"); + } else { + etm_release_trace_id(drvdata); + /* The tracer didn't start */ + coresight_set_mode(csdev, CS_MODE_DISABLED); + } + return ret; } @@ -565,17 +561,18 @@ static int etm_enable(struct coresight_device *csdev, struct perf_event *event, static void etm_disable_hw(struct etm_drvdata *drvdata) { int i; - struct etm_config *config = &drvdata->config; + const struct etm_caps *caps = &drvdata->caps; + struct etm_config *curr_config = &drvdata->curr_config; struct coresight_device *csdev = drvdata->csdev; CS_UNLOCK(drvdata->csa.base); etm_set_prog(drvdata); /* Read back sequencer and counters for post trace analysis */ - config->seq_curr_state = (etm_readl(drvdata, ETMSQR) & ETM_SQR_MASK); + curr_config->seq_curr_state = (etm_readl(drvdata, ETMSQR) & ETM_SQR_MASK); - for (i = 0; i < drvdata->nr_cntr; i++) - config->cntr_val[i] = etm_readl(drvdata, ETMCNTVRn(i)); + for (i = 0; i < caps->nr_cntr; i++) + curr_config->cntr_val[i] = etm_readl(drvdata, ETMCNTVRn(i)); etm_set_pwrdwn(drvdata); coresight_disclaim_device_unlocked(csdev); @@ -593,7 +590,6 @@ static void etm_disable_sysfs_smp_call(void *info) etm_disable_hw(drvdata); drvdata->csdev->path = NULL; - coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED); } static void etm_disable_perf(struct coresight_device *csdev) @@ -630,8 +626,10 @@ static void etm_disable_perf(struct coresight_device *csdev) static void etm_disable_sysfs(struct coresight_device *csdev) { struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - spin_lock(&drvdata->spinlock); + const struct etm_caps *caps = &drvdata->caps; + struct etm_config *sysfs_config = &drvdata->sysfs_config; + const struct etm_config *curr_config = &drvdata->curr_config; + int i; /* * Executing etm_disable_hw on the cpu whose ETM is being disabled @@ -640,7 +638,14 @@ static void etm_disable_sysfs(struct coresight_device *csdev) smp_call_function_single(drvdata->cpu, etm_disable_sysfs_smp_call, drvdata, 1); - spin_unlock(&drvdata->spinlock); + /* + * Userspace may read seq_curr_state and cntr_val through sysfs + * after the sysfs-session has been disabled. + */ + for (i = 0; i < caps->nr_cntr; i++) + sysfs_config->cntr_val[i] = curr_config->cntr_val[i]; + + sysfs_config->seq_curr_state = curr_config->seq_curr_state; /* * we only release trace IDs when resetting sysfs. @@ -649,6 +654,8 @@ static void etm_disable_sysfs(struct coresight_device *csdev) * prior trace id allocation method */ + coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED); + dev_dbg(&csdev->dev, "ETM tracing disabled\n"); } @@ -720,7 +727,9 @@ static void etm_init_arch_data(void *info) { u32 etmidr; u32 etmccr; + u32 etmccer; struct etm_drvdata *drvdata = info; + struct etm_caps *caps = &drvdata->caps; /* Make sure all registers are accessible */ etm_os_unlock(drvdata); @@ -745,16 +754,19 @@ static void etm_init_arch_data(void *info) /* Find all capabilities */ etmidr = etm_readl(drvdata, ETMIDR); drvdata->arch = BMVAL(etmidr, 4, 11); - drvdata->port_size = etm_readl(drvdata, ETMCR) & PORT_SIZE_MASK; + caps->port_size = etm_readl(drvdata, ETMCR) & PORT_SIZE_MASK; + + etmccer = etm_readl(drvdata, ETMCCER); + caps->timestamp = !!(etmccer & ETMCCER_TIMESTAMP); + caps->retstack = !!(etmccer & ETMCCER_RETSTACK); - drvdata->etmccer = etm_readl(drvdata, ETMCCER); etmccr = etm_readl(drvdata, ETMCCR); - drvdata->etmccr = etmccr; - drvdata->nr_addr_cmp = BMVAL(etmccr, 0, 3) * 2; - drvdata->nr_cntr = BMVAL(etmccr, 13, 15); - drvdata->nr_ext_inp = BMVAL(etmccr, 17, 19); - drvdata->nr_ext_out = BMVAL(etmccr, 20, 22); - drvdata->nr_ctxid_cmp = BMVAL(etmccr, 24, 25); + caps->fifofull = !!(etmccr & ETMCCR_FIFOFULL); + caps->nr_addr_cmp = BMVAL(etmccr, 0, 3) * 2; + caps->nr_cntr = BMVAL(etmccr, 13, 15); + caps->nr_ext_inp = BMVAL(etmccr, 17, 19); + caps->nr_ext_out = BMVAL(etmccr, 20, 22); + caps->nr_ctxid_cmp = BMVAL(etmccr, 24, 25); coresight_clear_self_claim_tag_unlocked(&drvdata->csa); etm_set_pwrdwn(drvdata); @@ -832,7 +844,7 @@ static int etm_probe(struct amba_device *adev, const struct amba_id *id) if (etm_arch_supported(drvdata->arch) == false) return -EINVAL; - etm_set_default(&drvdata->config); + etm_set_default(&drvdata->sysfs_config); pdata = coresight_get_platform_data(dev); if (IS_ERR(pdata)) diff --git a/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c b/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c index b3c67e96a82a..d185fb8270e1 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c @@ -6,17 +6,56 @@ #include <linux/pid_namespace.h> #include <linux/pm_runtime.h> +#include <linux/smp.h> #include <linux/sysfs.h> #include "coresight-etm.h" #include "coresight-priv.h" +struct etm_readl_cslocked_arg { + struct etm_drvdata *drvdata; + u32 off; + unsigned long *val; +}; + +static void etm_readl_cslocked_smp_call(void *info) +{ + struct etm_readl_cslocked_arg *arg = info; + + CS_UNLOCK(arg->drvdata->csa.base); + *arg->val = etm_readl(arg->drvdata, arg->off); + CS_LOCK(arg->drvdata->csa.base); +} + +static int etm_readl_cslocked(struct etm_drvdata *drvdata, u32 off, + unsigned long *val) +{ + int ret; + struct etm_readl_cslocked_arg arg; + + ret = pm_runtime_get_sync(drvdata->csdev->dev.parent); + if (ret < 0) + goto out; + + arg.drvdata = drvdata; + arg.off = off; + arg.val = val; + + ret = smp_call_function_single(drvdata->cpu, + etm_readl_cslocked_smp_call, + (void *)&arg, 1); +out: + pm_runtime_put(drvdata->csdev->dev.parent); + return ret; +} + static ssize_t nr_addr_cmp_show(struct device *dev, struct device_attribute *attr, char *buf) { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + const struct etm_caps *caps = &drvdata->caps; - val = drvdata->nr_addr_cmp; + val = caps->nr_addr_cmp; return sprintf(buf, "%#lx\n", val); } static DEVICE_ATTR_RO(nr_addr_cmp); @@ -25,8 +64,9 @@ static ssize_t nr_cntr_show(struct device *dev, struct device_attribute *attr, char *buf) { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + const struct etm_caps *caps = &drvdata->caps; - val = drvdata->nr_cntr; + val = caps->nr_cntr; return sprintf(buf, "%#lx\n", val); } static DEVICE_ATTR_RO(nr_cntr); @@ -37,7 +77,7 @@ static ssize_t nr_ctxid_cmp_show(struct device *dev, unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - val = drvdata->nr_ctxid_cmp; + val = drvdata->caps.nr_ctxid_cmp; return sprintf(buf, "%#lx\n", val); } static DEVICE_ATTR_RO(nr_ctxid_cmp); @@ -45,18 +85,13 @@ static DEVICE_ATTR_RO(nr_ctxid_cmp); static ssize_t etmsr_show(struct device *dev, struct device_attribute *attr, char *buf) { - unsigned long flags, val; + int ret; + unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - pm_runtime_get_sync(dev->parent); - spin_lock_irqsave(&drvdata->spinlock, flags); - CS_UNLOCK(drvdata->csa.base); - - val = etm_readl(drvdata, ETMSR); - - CS_LOCK(drvdata->csa.base); - spin_unlock_irqrestore(&drvdata->spinlock, flags); - pm_runtime_put(dev->parent); + ret = etm_readl_cslocked(drvdata, ETMSR, &val); + if (ret) + return ret; return sprintf(buf, "%#lx\n", val); } @@ -69,22 +104,26 @@ static ssize_t reset_store(struct device *dev, int i, ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; + if (IS_ERR_OR_NULL(drvdata->csdev) || + coresight_get_mode(drvdata->csdev) == CS_MODE_SYSFS) + return -EBUSY; + if (val) { spin_lock(&drvdata->spinlock); - memset(config, 0, sizeof(struct etm_config)); - config->mode = ETM_MODE_EXCLUDE; - config->trigger_event = ETM_DEFAULT_EVENT_VAL; - for (i = 0; i < drvdata->nr_addr_cmp; i++) { - config->addr_type[i] = ETM_ADDR_TYPE_NONE; + memset(sysfs_config, 0, sizeof(struct etm_config)); + sysfs_config->mode = ETM_MODE_EXCLUDE; + sysfs_config->trigger_event = ETM_DEFAULT_EVENT_VAL; + for (i = 0; i < drvdata->caps.nr_addr_cmp; i++) { + sysfs_config->addr_type[i] = ETM_ADDR_TYPE_NONE; } - etm_set_default(config); + etm_set_default(sysfs_config); etm_release_trace_id(drvdata); spin_unlock(&drvdata->spinlock); } @@ -98,9 +137,9 @@ static ssize_t mode_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->mode; + val = sysfs_config->mode; return sprintf(buf, "%#lx\n", val); } @@ -111,62 +150,63 @@ static ssize_t mode_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + const struct etm_caps *caps = &drvdata->caps; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; spin_lock(&drvdata->spinlock); - config->mode = val & ETM_MODE_ALL; + sysfs_config->mode = val & ETM_MODE_ALL; - if (config->mode & ETM_MODE_EXCLUDE) - config->enable_ctrl1 |= ETMTECR1_INC_EXC; + if (sysfs_config->mode & ETM_MODE_EXCLUDE) + sysfs_config->enable_ctrl1 |= ETMTECR1_INC_EXC; else - config->enable_ctrl1 &= ~ETMTECR1_INC_EXC; + sysfs_config->enable_ctrl1 &= ~ETMTECR1_INC_EXC; - if (config->mode & ETM_MODE_CYCACC) - config->ctrl |= ETMCR_CYC_ACC; + if (sysfs_config->mode & ETM_MODE_CYCACC) + sysfs_config->ctrl |= ETMCR_CYC_ACC; else - config->ctrl &= ~ETMCR_CYC_ACC; + sysfs_config->ctrl &= ~ETMCR_CYC_ACC; - if (config->mode & ETM_MODE_STALL) { - if (!(drvdata->etmccr & ETMCCR_FIFOFULL)) { + if (sysfs_config->mode & ETM_MODE_STALL) { + if (!caps->fifofull) { dev_warn(dev, "stall mode not supported\n"); ret = -EINVAL; goto err_unlock; } - config->ctrl |= ETMCR_STALL_MODE; + sysfs_config->ctrl |= ETMCR_STALL_MODE; } else - config->ctrl &= ~ETMCR_STALL_MODE; + sysfs_config->ctrl &= ~ETMCR_STALL_MODE; - if (config->mode & ETM_MODE_TIMESTAMP) { - if (!(drvdata->etmccer & ETMCCER_TIMESTAMP)) { + if (sysfs_config->mode & ETM_MODE_TIMESTAMP) { + if (!caps->timestamp) { dev_warn(dev, "timestamp not supported\n"); ret = -EINVAL; goto err_unlock; } - config->ctrl |= ETMCR_TIMESTAMP_EN; + sysfs_config->ctrl |= ETMCR_TIMESTAMP_EN; } else - config->ctrl &= ~ETMCR_TIMESTAMP_EN; + sysfs_config->ctrl &= ~ETMCR_TIMESTAMP_EN; - if (config->mode & ETM_MODE_CTXID) - config->ctrl |= ETMCR_CTXID_SIZE; + if (sysfs_config->mode & ETM_MODE_CTXID) + sysfs_config->ctrl |= ETMCR_CTXID_SIZE; else - config->ctrl &= ~ETMCR_CTXID_SIZE; + sysfs_config->ctrl &= ~ETMCR_CTXID_SIZE; - if (config->mode & ETM_MODE_BBROAD) - config->ctrl |= ETMCR_BRANCH_BROADCAST; + if (sysfs_config->mode & ETM_MODE_BBROAD) + sysfs_config->ctrl |= ETMCR_BRANCH_BROADCAST; else - config->ctrl &= ~ETMCR_BRANCH_BROADCAST; + sysfs_config->ctrl &= ~ETMCR_BRANCH_BROADCAST; - if (config->mode & ETM_MODE_RET_STACK) - config->ctrl |= ETMCR_RETURN_STACK; + if (sysfs_config->mode & ETM_MODE_RET_STACK) + sysfs_config->ctrl |= ETMCR_RETURN_STACK; else - config->ctrl &= ~ETMCR_RETURN_STACK; + sysfs_config->ctrl &= ~ETMCR_RETURN_STACK; - if (config->mode & (ETM_MODE_EXCL_KERN | ETM_MODE_EXCL_USER)) - etm_config_trace_mode(config); + if (sysfs_config->mode & (ETM_MODE_EXCL_KERN | ETM_MODE_EXCL_USER)) + etm_config_trace_mode(sysfs_config); spin_unlock(&drvdata->spinlock); @@ -183,9 +223,9 @@ static ssize_t trigger_event_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->trigger_event; + val = sysfs_config->trigger_event; return sprintf(buf, "%#lx\n", val); } @@ -196,13 +236,13 @@ static ssize_t trigger_event_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - config->trigger_event = val & ETM_EVENT_MASK; + sysfs_config->trigger_event = val & ETM_EVENT_MASK; return size; } @@ -213,9 +253,9 @@ static ssize_t enable_event_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->enable_event; + val = sysfs_config->enable_event; return sprintf(buf, "%#lx\n", val); } @@ -226,13 +266,13 @@ static ssize_t enable_event_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - config->enable_event = val & ETM_EVENT_MASK; + sysfs_config->enable_event = val & ETM_EVENT_MASK; return size; } @@ -243,9 +283,9 @@ static ssize_t fifofull_level_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->fifofull_level; + val = sysfs_config->fifofull_level; return sprintf(buf, "%#lx\n", val); } @@ -256,13 +296,13 @@ static ssize_t fifofull_level_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - config->fifofull_level = val; + sysfs_config->fifofull_level = val; return size; } @@ -273,9 +313,9 @@ static ssize_t addr_idx_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->addr_idx; + val = sysfs_config->addr_idx; return sprintf(buf, "%#lx\n", val); } @@ -286,13 +326,14 @@ static ssize_t addr_idx_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + const struct etm_caps *caps = &drvdata->caps; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - if (val >= drvdata->nr_addr_cmp) + if (val >= caps->nr_addr_cmp) return -EINVAL; /* @@ -300,7 +341,7 @@ static ssize_t addr_idx_store(struct device *dev, * dereferenced multiple times within a spinlock block elsewhere. */ spin_lock(&drvdata->spinlock); - config->addr_idx = val; + sysfs_config->addr_idx = val; spin_unlock(&drvdata->spinlock); return size; @@ -313,17 +354,17 @@ static ssize_t addr_single_show(struct device *dev, u8 idx; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; spin_lock(&drvdata->spinlock); - idx = config->addr_idx; - if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || - config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { + idx = sysfs_config->addr_idx; + if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE || + sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { spin_unlock(&drvdata->spinlock); return -EINVAL; } - val = config->addr_val[idx]; + val = sysfs_config->addr_val[idx]; spin_unlock(&drvdata->spinlock); return sprintf(buf, "%#lx\n", val); @@ -337,22 +378,22 @@ static ssize_t addr_single_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; spin_lock(&drvdata->spinlock); - idx = config->addr_idx; - if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || - config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { + idx = sysfs_config->addr_idx; + if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE || + sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { spin_unlock(&drvdata->spinlock); return -EINVAL; } - config->addr_val[idx] = val; - config->addr_type[idx] = ETM_ADDR_TYPE_SINGLE; + sysfs_config->addr_val[idx] = val; + sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_SINGLE; spin_unlock(&drvdata->spinlock); return size; @@ -365,24 +406,24 @@ static ssize_t addr_range_show(struct device *dev, u8 idx; unsigned long val1, val2; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; spin_lock(&drvdata->spinlock); - idx = config->addr_idx; + idx = sysfs_config->addr_idx; if (idx % 2 != 0) { spin_unlock(&drvdata->spinlock); return -EPERM; } - if (!((config->addr_type[idx] == ETM_ADDR_TYPE_NONE && - config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || - (config->addr_type[idx] == ETM_ADDR_TYPE_RANGE && - config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { + if (!((sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE && + sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || + (sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_RANGE && + sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { spin_unlock(&drvdata->spinlock); return -EPERM; } - val1 = config->addr_val[idx]; - val2 = config->addr_val[idx + 1]; + val1 = sysfs_config->addr_val[idx]; + val2 = sysfs_config->addr_val[idx + 1]; spin_unlock(&drvdata->spinlock); return sprintf(buf, "%#lx %#lx\n", val1, val2); @@ -395,7 +436,7 @@ static ssize_t addr_range_store(struct device *dev, u8 idx; unsigned long val1, val2; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; if (sscanf(buf, "%lx %lx", &val1, &val2) != 2) return -EINVAL; @@ -404,24 +445,24 @@ static ssize_t addr_range_store(struct device *dev, return -EINVAL; spin_lock(&drvdata->spinlock); - idx = config->addr_idx; + idx = sysfs_config->addr_idx; if (idx % 2 != 0) { spin_unlock(&drvdata->spinlock); return -EPERM; } - if (!((config->addr_type[idx] == ETM_ADDR_TYPE_NONE && - config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || - (config->addr_type[idx] == ETM_ADDR_TYPE_RANGE && - config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { + if (!((sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE && + sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || + (sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_RANGE && + sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { spin_unlock(&drvdata->spinlock); return -EPERM; } - config->addr_val[idx] = val1; - config->addr_type[idx] = ETM_ADDR_TYPE_RANGE; - config->addr_val[idx + 1] = val2; - config->addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE; - config->enable_ctrl1 |= (1 << (idx/2)); + sysfs_config->addr_val[idx] = val1; + sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_RANGE; + sysfs_config->addr_val[idx + 1] = val2; + sysfs_config->addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE; + sysfs_config->enable_ctrl1 |= (1 << (idx/2)); spin_unlock(&drvdata->spinlock); return size; @@ -434,17 +475,17 @@ static ssize_t addr_start_show(struct device *dev, u8 idx; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; spin_lock(&drvdata->spinlock); - idx = config->addr_idx; - if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || - config->addr_type[idx] == ETM_ADDR_TYPE_START)) { + idx = sysfs_config->addr_idx; + if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE || + sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_START)) { spin_unlock(&drvdata->spinlock); return -EPERM; } - val = config->addr_val[idx]; + val = sysfs_config->addr_val[idx]; spin_unlock(&drvdata->spinlock); return sprintf(buf, "%#lx\n", val); @@ -458,24 +499,24 @@ static ssize_t addr_start_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; spin_lock(&drvdata->spinlock); - idx = config->addr_idx; - if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || - config->addr_type[idx] == ETM_ADDR_TYPE_START)) { + idx = sysfs_config->addr_idx; + if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE || + sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_START)) { spin_unlock(&drvdata->spinlock); return -EPERM; } - config->addr_val[idx] = val; - config->addr_type[idx] = ETM_ADDR_TYPE_START; - config->startstop_ctrl |= (1 << idx); - config->enable_ctrl1 |= ETMTECR1_START_STOP; + sysfs_config->addr_val[idx] = val; + sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_START; + sysfs_config->startstop_ctrl |= (1 << idx); + sysfs_config->enable_ctrl1 |= ETMTECR1_START_STOP; spin_unlock(&drvdata->spinlock); return size; @@ -488,17 +529,17 @@ static ssize_t addr_stop_show(struct device *dev, u8 idx; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; spin_lock(&drvdata->spinlock); - idx = config->addr_idx; - if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || - config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { + idx = sysfs_config->addr_idx; + if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE || + sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { spin_unlock(&drvdata->spinlock); return -EPERM; } - val = config->addr_val[idx]; + val = sysfs_config->addr_val[idx]; spin_unlock(&drvdata->spinlock); return sprintf(buf, "%#lx\n", val); @@ -512,24 +553,24 @@ static ssize_t addr_stop_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; spin_lock(&drvdata->spinlock); - idx = config->addr_idx; - if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || - config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { + idx = sysfs_config->addr_idx; + if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE || + sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { spin_unlock(&drvdata->spinlock); return -EPERM; } - config->addr_val[idx] = val; - config->addr_type[idx] = ETM_ADDR_TYPE_STOP; - config->startstop_ctrl |= (1 << (idx + 16)); - config->enable_ctrl1 |= ETMTECR1_START_STOP; + sysfs_config->addr_val[idx] = val; + sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_STOP; + sysfs_config->startstop_ctrl |= (1 << (idx + 16)); + sysfs_config->enable_ctrl1 |= ETMTECR1_START_STOP; spin_unlock(&drvdata->spinlock); return size; @@ -541,10 +582,10 @@ static ssize_t addr_acctype_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; spin_lock(&drvdata->spinlock); - val = config->addr_acctype[config->addr_idx]; + val = sysfs_config->addr_acctype[sysfs_config->addr_idx]; spin_unlock(&drvdata->spinlock); return sprintf(buf, "%#lx\n", val); @@ -557,14 +598,14 @@ static ssize_t addr_acctype_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; spin_lock(&drvdata->spinlock); - config->addr_acctype[config->addr_idx] = val; + sysfs_config->addr_acctype[sysfs_config->addr_idx] = val; spin_unlock(&drvdata->spinlock); return size; @@ -576,9 +617,9 @@ static ssize_t cntr_idx_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->cntr_idx; + val = sysfs_config->cntr_idx; return sprintf(buf, "%#lx\n", val); } @@ -589,20 +630,21 @@ static ssize_t cntr_idx_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + const struct etm_caps *caps = &drvdata->caps; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - if (val >= drvdata->nr_cntr) + if (val >= caps->nr_cntr) return -EINVAL; /* * Use spinlock to ensure index doesn't change while it gets * dereferenced multiple times within a spinlock block elsewhere. */ spin_lock(&drvdata->spinlock); - config->cntr_idx = val; + sysfs_config->cntr_idx = val; spin_unlock(&drvdata->spinlock); return size; @@ -614,10 +656,10 @@ static ssize_t cntr_rld_val_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; spin_lock(&drvdata->spinlock); - val = config->cntr_rld_val[config->cntr_idx]; + val = sysfs_config->cntr_rld_val[sysfs_config->cntr_idx]; spin_unlock(&drvdata->spinlock); return sprintf(buf, "%#lx\n", val); @@ -630,14 +672,14 @@ static ssize_t cntr_rld_val_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; spin_lock(&drvdata->spinlock); - config->cntr_rld_val[config->cntr_idx] = val; + sysfs_config->cntr_rld_val[sysfs_config->cntr_idx] = val; spin_unlock(&drvdata->spinlock); return size; @@ -649,10 +691,10 @@ static ssize_t cntr_event_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; spin_lock(&drvdata->spinlock); - val = config->cntr_event[config->cntr_idx]; + val = sysfs_config->cntr_event[sysfs_config->cntr_idx]; spin_unlock(&drvdata->spinlock); return sprintf(buf, "%#lx\n", val); @@ -665,14 +707,14 @@ static ssize_t cntr_event_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; spin_lock(&drvdata->spinlock); - config->cntr_event[config->cntr_idx] = val & ETM_EVENT_MASK; + sysfs_config->cntr_event[sysfs_config->cntr_idx] = val & ETM_EVENT_MASK; spin_unlock(&drvdata->spinlock); return size; @@ -684,10 +726,10 @@ static ssize_t cntr_rld_event_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; spin_lock(&drvdata->spinlock); - val = config->cntr_rld_event[config->cntr_idx]; + val = sysfs_config->cntr_rld_event[sysfs_config->cntr_idx]; spin_unlock(&drvdata->spinlock); return sprintf(buf, "%#lx\n", val); @@ -700,14 +742,14 @@ static ssize_t cntr_rld_event_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; spin_lock(&drvdata->spinlock); - config->cntr_rld_event[config->cntr_idx] = val & ETM_EVENT_MASK; + sysfs_config->cntr_rld_event[sysfs_config->cntr_idx] = val & ETM_EVENT_MASK; spin_unlock(&drvdata->spinlock); return size; @@ -717,16 +759,21 @@ static DEVICE_ATTR_RW(cntr_rld_event); static ssize_t cntr_val_show(struct device *dev, struct device_attribute *attr, char *buf) { + int ret; u32 val; + unsigned long val2; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; if (!coresight_get_mode(drvdata->csdev)) { spin_lock(&drvdata->spinlock); - val = config->cntr_val[config->cntr_idx]; + val = sysfs_config->cntr_val[sysfs_config->cntr_idx]; spin_unlock(&drvdata->spinlock); } else { - val = etm_readl(drvdata, ETMCNTVRn(config->cntr_idx)); + ret = etm_readl_cslocked(drvdata, ETMCNTVRn(sysfs_config->cntr_idx), &val2); + if (ret) + return ret; + val = val2; } return sysfs_emit(buf, "%#x\n", val); @@ -739,14 +786,17 @@ static ssize_t cntr_val_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; + if (IS_ERR_OR_NULL(drvdata->csdev) || + coresight_get_mode(drvdata->csdev) == CS_MODE_SYSFS) + return -EBUSY; spin_lock(&drvdata->spinlock); - config->cntr_val[config->cntr_idx] = val; + sysfs_config->cntr_val[sysfs_config->cntr_idx] = val; spin_unlock(&drvdata->spinlock); return size; @@ -758,9 +808,9 @@ static ssize_t seq_12_event_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->seq_12_event; + val = sysfs_config->seq_12_event; return sprintf(buf, "%#lx\n", val); } @@ -771,13 +821,13 @@ static ssize_t seq_12_event_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - config->seq_12_event = val & ETM_EVENT_MASK; + sysfs_config->seq_12_event = val & ETM_EVENT_MASK; return size; } static DEVICE_ATTR_RW(seq_12_event); @@ -787,9 +837,9 @@ static ssize_t seq_21_event_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->seq_21_event; + val = sysfs_config->seq_21_event; return sprintf(buf, "%#lx\n", val); } @@ -800,13 +850,13 @@ static ssize_t seq_21_event_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - config->seq_21_event = val & ETM_EVENT_MASK; + sysfs_config->seq_21_event = val & ETM_EVENT_MASK; return size; } static DEVICE_ATTR_RW(seq_21_event); @@ -816,9 +866,9 @@ static ssize_t seq_23_event_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->seq_23_event; + val = sysfs_config->seq_23_event; return sprintf(buf, "%#lx\n", val); } @@ -829,13 +879,13 @@ static ssize_t seq_23_event_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - config->seq_23_event = val & ETM_EVENT_MASK; + sysfs_config->seq_23_event = val & ETM_EVENT_MASK; return size; } static DEVICE_ATTR_RW(seq_23_event); @@ -845,9 +895,9 @@ static ssize_t seq_31_event_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->seq_31_event; + val = sysfs_config->seq_31_event; return sprintf(buf, "%#lx\n", val); } @@ -858,13 +908,13 @@ static ssize_t seq_31_event_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - config->seq_31_event = val & ETM_EVENT_MASK; + sysfs_config->seq_31_event = val & ETM_EVENT_MASK; return size; } static DEVICE_ATTR_RW(seq_31_event); @@ -874,9 +924,9 @@ static ssize_t seq_32_event_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->seq_32_event; + val = sysfs_config->seq_32_event; return sprintf(buf, "%#lx\n", val); } @@ -887,13 +937,13 @@ static ssize_t seq_32_event_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - config->seq_32_event = val & ETM_EVENT_MASK; + sysfs_config->seq_32_event = val & ETM_EVENT_MASK; return size; } static DEVICE_ATTR_RW(seq_32_event); @@ -903,9 +953,9 @@ static ssize_t seq_13_event_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->seq_13_event; + val = sysfs_config->seq_13_event; return sprintf(buf, "%#lx\n", val); } @@ -916,13 +966,13 @@ static ssize_t seq_13_event_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - config->seq_13_event = val & ETM_EVENT_MASK; + sysfs_config->seq_13_event = val & ETM_EVENT_MASK; return size; } static DEVICE_ATTR_RW(seq_13_event); @@ -930,24 +980,20 @@ static DEVICE_ATTR_RW(seq_13_event); static ssize_t seq_curr_state_show(struct device *dev, struct device_attribute *attr, char *buf) { - unsigned long val, flags; + int ret; + unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; if (!coresight_get_mode(drvdata->csdev)) { - val = config->seq_curr_state; + val = sysfs_config->seq_curr_state; goto out; } - pm_runtime_get_sync(dev->parent); - spin_lock_irqsave(&drvdata->spinlock, flags); - - CS_UNLOCK(drvdata->csa.base); - val = (etm_readl(drvdata, ETMSQR) & ETM_SQR_MASK); - CS_LOCK(drvdata->csa.base); - - spin_unlock_irqrestore(&drvdata->spinlock, flags); - pm_runtime_put(dev->parent); + ret = etm_readl_cslocked(drvdata, ETMSQR, &val); + if (ret) + return ret; + val &= ETM_SQR_MASK; out: return sprintf(buf, "%#lx\n", val); } @@ -959,7 +1005,7 @@ static ssize_t seq_curr_state_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) @@ -968,7 +1014,7 @@ static ssize_t seq_curr_state_store(struct device *dev, if (val > ETM_SEQ_STATE_MAX_VAL) return -EINVAL; - config->seq_curr_state = val; + sysfs_config->seq_curr_state = val; return size; } @@ -979,9 +1025,9 @@ static ssize_t ctxid_idx_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->ctxid_idx; + val = sysfs_config->ctxid_idx; return sprintf(buf, "%#lx\n", val); } @@ -992,13 +1038,14 @@ static ssize_t ctxid_idx_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + const struct etm_caps *caps = &drvdata->caps; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - if (val >= drvdata->nr_ctxid_cmp) + if (val >= caps->nr_ctxid_cmp) return -EINVAL; /* @@ -1006,7 +1053,7 @@ static ssize_t ctxid_idx_store(struct device *dev, * dereferenced multiple times within a spinlock block elsewhere. */ spin_lock(&drvdata->spinlock); - config->ctxid_idx = val; + sysfs_config->ctxid_idx = val; spin_unlock(&drvdata->spinlock); return size; @@ -1018,7 +1065,7 @@ static ssize_t ctxid_pid_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; /* * Don't use contextID tracing if coming from a PID namespace. See @@ -1028,7 +1075,7 @@ static ssize_t ctxid_pid_show(struct device *dev, return -EINVAL; spin_lock(&drvdata->spinlock); - val = config->ctxid_pid[config->ctxid_idx]; + val = sysfs_config->ctxid_pid[sysfs_config->ctxid_idx]; spin_unlock(&drvdata->spinlock); return sprintf(buf, "%#lx\n", val); @@ -1041,7 +1088,7 @@ static ssize_t ctxid_pid_store(struct device *dev, int ret; unsigned long pid; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; /* * When contextID tracing is enabled the tracers will insert the @@ -1060,7 +1107,7 @@ static ssize_t ctxid_pid_store(struct device *dev, return ret; spin_lock(&drvdata->spinlock); - config->ctxid_pid[config->ctxid_idx] = pid; + sysfs_config->ctxid_pid[sysfs_config->ctxid_idx] = pid; spin_unlock(&drvdata->spinlock); return size; @@ -1072,7 +1119,7 @@ static ssize_t ctxid_mask_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; /* * Don't use contextID tracing if coming from a PID namespace. See @@ -1081,7 +1128,7 @@ static ssize_t ctxid_mask_show(struct device *dev, if (task_active_pid_ns(current) != &init_pid_ns) return -EINVAL; - val = config->ctxid_mask; + val = sysfs_config->ctxid_mask; return sprintf(buf, "%#lx\n", val); } @@ -1092,7 +1139,7 @@ static ssize_t ctxid_mask_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; /* * Don't use contextID tracing if coming from a PID namespace. See @@ -1105,7 +1152,7 @@ static ssize_t ctxid_mask_store(struct device *dev, if (ret) return ret; - config->ctxid_mask = val; + sysfs_config->ctxid_mask = val; return size; } static DEVICE_ATTR_RW(ctxid_mask); @@ -1115,9 +1162,9 @@ static ssize_t sync_freq_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->sync_freq; + val = sysfs_config->sync_freq; return sprintf(buf, "%#lx\n", val); } @@ -1128,13 +1175,13 @@ static ssize_t sync_freq_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - config->sync_freq = val & ETM_SYNC_MASK; + sysfs_config->sync_freq = val & ETM_SYNC_MASK; return size; } static DEVICE_ATTR_RW(sync_freq); @@ -1144,9 +1191,9 @@ static ssize_t timestamp_event_show(struct device *dev, { unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; - val = config->timestamp_event; + val = sysfs_config->timestamp_event; return sprintf(buf, "%#lx\n", val); } @@ -1157,13 +1204,13 @@ static ssize_t timestamp_event_store(struct device *dev, int ret; unsigned long val; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etm_config *config = &drvdata->config; + struct etm_config *sysfs_config = &drvdata->sysfs_config; ret = kstrtoul(buf, 16, &val); if (ret) return ret; - config->timestamp_event = val & ETM_EVENT_MASK; + sysfs_config->timestamp_event = val & ETM_EVENT_MASK; return size; } static DEVICE_ATTR_RW(timestamp_event); diff --git a/drivers/hwtracing/coresight/coresight-etm4x-cfg.c b/drivers/hwtracing/coresight/coresight-etm4x-cfg.c index e1a59b434505..52cb036bb04b 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-cfg.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-cfg.c @@ -47,7 +47,7 @@ static int etm4_cfg_map_reg_offset(struct etmv4_drvdata *drvdata, struct cscfg_regval_csdev *reg_csdev, u32 offset) { int err = -EINVAL, idx; - struct etmv4_config *drvcfg = &drvdata->config; + struct etmv4_config *drvcfg = &drvdata->curr_config; u32 off_mask; if (((offset >= TRCEVENTCTL0R) && (offset <= TRCVIPCSSCTLR)) || @@ -86,7 +86,6 @@ static int etm4_cfg_map_reg_offset(struct etmv4_drvdata *drvdata, off_mask = (offset & GENMASK(11, 5)); do { CHECKREGIDX(TRCSSCCRn(0), ss_ctrl, idx, off_mask); - CHECKREGIDX(TRCSSCSRn(0), ss_status, idx, off_mask); CHECKREGIDX(TRCSSPCICRn(0), ss_pe_cmp, idx, off_mask); } while (0); } else if ((offset >= TRCCIDCVRn(0)) && (offset <= TRCVMIDCVRn(7))) { @@ -154,11 +153,14 @@ static int etm4_cfg_load_feature(struct coresight_device *csdev, int i = 0, err = 0; /* - * essential we set the device spinlock - this is used in the generic - * programming routines when copying values into the drvdata structures - * via the pointers setup in etm4_cfg_map_reg_offset(). + * drvdata structure of etm4 for configfs is curr_config and + * this curr_config is protected with csdev->mode by restricting + * access to curr_config after mode changed to PERF or SYSFS. + * + * Therefore, it doesn't need to protected with etmv4_drvdata->spinlock + * to feature applying and set the NULL. */ - feat_csdev->drv_spinlock = &drvdata->spinlock; + feat_csdev->drv_spinlock = NULL; /* process the register descriptions */ for (i = 0; i < feat_csdev->nr_regs && !err; i++) { diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index 2247ad55d444..e21be6468e95 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -66,10 +66,12 @@ MODULE_PARM_DESC(pm_save_enable, "Save/restore state on power down: 1 = never, 2 = self-hosted. MMIO and DT only."); static struct etmv4_drvdata *etmdrvdata[NR_CPUS]; -static void etm4_set_default_config(struct etmv4_config *config); +static void etm4_set_default_config(struct etmv4_config *config, + const struct etmv4_caps *caps); static int etm4_set_event_filters(struct etmv4_drvdata *drvdata, struct perf_event *event); -static u64 etm4_get_access_type(struct etmv4_config *config); +static u64 etm4_get_access_type(struct etmv4_config *config, + const struct etmv4_caps *caps); static enum cpuhp_state hp_online; @@ -92,9 +94,10 @@ static int etm4_probe_cpu(unsigned int cpu); */ static bool etm4x_sspcicrn_present(struct etmv4_drvdata *drvdata, int n) { - return (n < drvdata->nr_ss_cmp) && - drvdata->nr_pe_cmp && - (drvdata->config.ss_status[n] & TRCSSCSRn_PC); + const struct etmv4_caps *caps = &drvdata->caps; + + return (n < caps->nr_ss_cmp) && caps->nr_pe_cmp && + (drvdata->ss_status[n] & TRCSSCSRn_PC); } u64 etm4x_sysreg_read(u32 offset, bool _relaxed, bool _64bit) @@ -164,17 +167,20 @@ static void ete_sysreg_write(u64 val, u32 offset, bool _relaxed, bool _64bit) static void etm_detect_os_lock(struct etmv4_drvdata *drvdata, struct csdev_access *csa) { + struct etmv4_caps *caps = &drvdata->caps; u32 oslsr = etm4x_relaxed_read32(csa, TRCOSLSR); - drvdata->os_lock_model = ETM_OSLSR_OSLM(oslsr); + caps->os_lock_model = ETM_OSLSR_OSLM(oslsr); } static void etm_write_os_lock(struct etmv4_drvdata *drvdata, struct csdev_access *csa, u32 val) { + const struct etmv4_caps *caps = &drvdata->caps; + val = !!val; - switch (drvdata->os_lock_model) { + switch (caps->os_lock_model) { case ETM_OSLOCK_PRESENT: etm4x_relaxed_write32(csa, val, TRCOSLAR); break; @@ -183,7 +189,7 @@ static void etm_write_os_lock(struct etmv4_drvdata *drvdata, break; default: pr_warn_once("CPU%d: Unsupported Trace OSLock model: %x\n", - smp_processor_id(), drvdata->os_lock_model); + smp_processor_id(), caps->os_lock_model); fallthrough; case ETM_OSLOCK_NI: return; @@ -264,10 +270,11 @@ static void etm4x_prohibit_trace(struct etmv4_drvdata *drvdata) static u64 etm4x_get_kern_user_filter(struct etmv4_drvdata *drvdata) { u64 trfcr = drvdata->trfcr; + struct etmv4_config *curr_config = &drvdata->curr_config; - if (drvdata->config.mode & ETM_MODE_EXCL_KERN) + if (curr_config->mode & ETM_MODE_EXCL_KERN) trfcr &= ~TRFCR_EL1_ExTRE; - if (drvdata->config.mode & ETM_MODE_EXCL_USER) + if (curr_config->mode & ETM_MODE_EXCL_USER) trfcr &= ~TRFCR_EL1_E0TRE; return trfcr; @@ -275,7 +282,7 @@ static u64 etm4x_get_kern_user_filter(struct etmv4_drvdata *drvdata) /* * etm4x_allow_trace - Allow CPU tracing in the respective ELs, - * as configured by the drvdata->config.mode for the current + * as configured by the drvdata->curr_config.mode for the current * session. Even though we have TRCVICTLR bits to filter the * trace in the ELs, it doesn't prevent the ETM from generating * a packet (e.g, TraceInfo) that might contain the addresses from @@ -286,12 +293,13 @@ static u64 etm4x_get_kern_user_filter(struct etmv4_drvdata *drvdata) static void etm4x_allow_trace(struct etmv4_drvdata *drvdata) { u64 trfcr, guest_trfcr; + struct etmv4_config *curr_config = &drvdata->curr_config; /* If the CPU doesn't support FEAT_TRF, nothing to do */ if (!drvdata->trfcr) return; - if (drvdata->config.mode & ETM_MODE_EXCL_HOST) + if (curr_config->mode & ETM_MODE_EXCL_HOST) trfcr = drvdata->trfcr & ~(TRFCR_EL1_ExTRE | TRFCR_EL1_E0TRE); else trfcr = etm4x_get_kern_user_filter(drvdata); @@ -299,7 +307,7 @@ static void etm4x_allow_trace(struct etmv4_drvdata *drvdata) write_trfcr(trfcr); /* Set filters for guests and pass to KVM */ - if (drvdata->config.mode & ETM_MODE_EXCL_GUEST) + if (curr_config->mode & ETM_MODE_EXCL_GUEST) guest_trfcr = drvdata->trfcr & ~(TRFCR_EL1_ExTRE | TRFCR_EL1_E0TRE); else guest_trfcr = etm4x_get_kern_user_filter(drvdata); @@ -492,7 +500,8 @@ static int etm4_enable_trace_unit(struct etmv4_drvdata *drvdata) static int etm4_enable_hw(struct etmv4_drvdata *drvdata) { int i, rc; - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *curr_config = &drvdata->curr_config; struct coresight_device *csdev = drvdata->csdev; struct device *etm_dev = &csdev->dev; struct csdev_access *csa = &csdev->access; @@ -523,73 +532,73 @@ static int etm4_enable_hw(struct etmv4_drvdata *drvdata) if (etm4x_wait_status(csa, TRCSTATR_IDLE_BIT, 1)) dev_err(etm_dev, "timeout while waiting for Idle Trace Status\n"); - if (drvdata->nr_pe) - etm4x_relaxed_write32(csa, config->pe_sel, TRCPROCSELR); - etm4x_relaxed_write32(csa, config->cfg, TRCCONFIGR); + if (caps->nr_pe) + etm4x_relaxed_write32(csa, curr_config->pe_sel, TRCPROCSELR); + etm4x_relaxed_write32(csa, curr_config->cfg, TRCCONFIGR); /* nothing specific implemented */ etm4x_relaxed_write32(csa, 0x0, TRCAUXCTLR); - etm4x_relaxed_write32(csa, config->eventctrl0, TRCEVENTCTL0R); - etm4x_relaxed_write32(csa, config->eventctrl1, TRCEVENTCTL1R); - if (drvdata->stallctl) - etm4x_relaxed_write32(csa, config->stall_ctrl, TRCSTALLCTLR); - etm4x_relaxed_write32(csa, config->ts_ctrl, TRCTSCTLR); - etm4x_relaxed_write32(csa, config->syncfreq, TRCSYNCPR); - etm4x_relaxed_write32(csa, config->ccctlr, TRCCCCTLR); - etm4x_relaxed_write32(csa, config->bb_ctrl, TRCBBCTLR); + etm4x_relaxed_write32(csa, curr_config->eventctrl0, TRCEVENTCTL0R); + etm4x_relaxed_write32(csa, curr_config->eventctrl1, TRCEVENTCTL1R); + if (caps->stallctl) + etm4x_relaxed_write32(csa, curr_config->stall_ctrl, TRCSTALLCTLR); + etm4x_relaxed_write32(csa, curr_config->ts_ctrl, TRCTSCTLR); + etm4x_relaxed_write32(csa, curr_config->syncfreq, TRCSYNCPR); + etm4x_relaxed_write32(csa, curr_config->ccctlr, TRCCCCTLR); + etm4x_relaxed_write32(csa, curr_config->bb_ctrl, TRCBBCTLR); etm4x_relaxed_write32(csa, drvdata->trcid, TRCTRACEIDR); - etm4x_relaxed_write32(csa, config->vinst_ctrl, TRCVICTLR); - etm4x_relaxed_write32(csa, config->viiectlr, TRCVIIECTLR); - etm4x_relaxed_write32(csa, config->vissctlr, TRCVISSCTLR); - if (drvdata->nr_pe_cmp) - etm4x_relaxed_write32(csa, config->vipcssctlr, TRCVIPCSSCTLR); - - for (i = 0; i < drvdata->nr_seq_ctrls; i++) - etm4x_relaxed_write32(csa, config->seq_ctrl[i], TRCSEQEVRn(i)); - if (drvdata->nrseqstate) { - etm4x_relaxed_write32(csa, config->seq_rst, TRCSEQRSTEVR); - etm4x_relaxed_write32(csa, config->seq_state, TRCSEQSTR); + etm4x_relaxed_write32(csa, curr_config->vinst_ctrl, TRCVICTLR); + etm4x_relaxed_write32(csa, curr_config->viiectlr, TRCVIIECTLR); + etm4x_relaxed_write32(csa, curr_config->vissctlr, TRCVISSCTLR); + if (caps->nr_pe_cmp) + etm4x_relaxed_write32(csa, curr_config->vipcssctlr, TRCVIPCSSCTLR); + + for (i = 0; i < caps->nr_seq_ctrls; i++) + etm4x_relaxed_write32(csa, curr_config->seq_ctrl[i], TRCSEQEVRn(i)); + if (caps->nrseqstate) { + etm4x_relaxed_write32(csa, curr_config->seq_rst, TRCSEQRSTEVR); + etm4x_relaxed_write32(csa, curr_config->seq_state, TRCSEQSTR); } - if (drvdata->numextinsel) - etm4x_relaxed_write32(csa, config->ext_inp, TRCEXTINSELR); - for (i = 0; i < drvdata->nr_cntr; i++) { - etm4x_relaxed_write32(csa, config->cntrldvr[i], TRCCNTRLDVRn(i)); - etm4x_relaxed_write32(csa, config->cntr_ctrl[i], TRCCNTCTLRn(i)); - etm4x_relaxed_write32(csa, config->cntr_val[i], TRCCNTVRn(i)); + if (caps->numextinsel) + etm4x_relaxed_write32(csa, curr_config->ext_inp, TRCEXTINSELR); + for (i = 0; i < caps->nr_cntr; i++) { + etm4x_relaxed_write32(csa, curr_config->cntrldvr[i], TRCCNTRLDVRn(i)); + etm4x_relaxed_write32(csa, curr_config->cntr_ctrl[i], TRCCNTCTLRn(i)); + etm4x_relaxed_write32(csa, curr_config->cntr_val[i], TRCCNTVRn(i)); } /* * Resource selector pair 0 is always implemented and reserved. As * such start at 2. */ - for (i = 2; i < drvdata->nr_resource * 2; i++) - etm4x_relaxed_write32(csa, config->res_ctrl[i], TRCRSCTLRn(i)); - - for (i = 0; i < drvdata->nr_ss_cmp; i++) { - /* always clear status bit on restart if using single-shot */ - if (config->ss_ctrl[i] || config->ss_pe_cmp[i]) - config->ss_status[i] &= ~TRCSSCSRn_STATUS; - etm4x_relaxed_write32(csa, config->ss_ctrl[i], TRCSSCCRn(i)); - etm4x_relaxed_write32(csa, config->ss_status[i], TRCSSCSRn(i)); + for (i = 2; i < caps->nr_resource * 2; i++) + etm4x_relaxed_write32(csa, curr_config->res_ctrl[i], TRCRSCTLRn(i)); + + for (i = 0; i < caps->nr_ss_cmp; i++) { + /* always clear status and pending bits on restart if using single-shot */ + if (curr_config->ss_ctrl[i] || curr_config->ss_pe_cmp[i]) + drvdata->ss_status[i] &= ~(TRCSSCSRn_STATUS | TRCSSCSRn_PENDING); + etm4x_relaxed_write32(csa, curr_config->ss_ctrl[i], TRCSSCCRn(i)); + etm4x_relaxed_write32(csa, drvdata->ss_status[i], TRCSSCSRn(i)); if (etm4x_sspcicrn_present(drvdata, i)) - etm4x_relaxed_write32(csa, config->ss_pe_cmp[i], TRCSSPCICRn(i)); + etm4x_relaxed_write32(csa, curr_config->ss_pe_cmp[i], TRCSSPCICRn(i)); } - for (i = 0; i < drvdata->nr_addr_cmp * 2; i++) { - etm4x_relaxed_write64(csa, config->addr_val[i], TRCACVRn(i)); - etm4x_relaxed_write64(csa, config->addr_acc[i], TRCACATRn(i)); + for (i = 0; i < caps->nr_addr_cmp * 2; i++) { + etm4x_relaxed_write64(csa, curr_config->addr_val[i], TRCACVRn(i)); + etm4x_relaxed_write64(csa, curr_config->addr_acc[i], TRCACATRn(i)); } - for (i = 0; i < drvdata->numcidc; i++) - etm4x_relaxed_write64(csa, config->ctxid_pid[i], TRCCIDCVRn(i)); - etm4x_relaxed_write32(csa, config->ctxid_mask0, TRCCIDCCTLR0); - if (drvdata->numcidc > 4) - etm4x_relaxed_write32(csa, config->ctxid_mask1, TRCCIDCCTLR1); - - for (i = 0; i < drvdata->numvmidc; i++) - etm4x_relaxed_write64(csa, config->vmid_val[i], TRCVMIDCVRn(i)); - etm4x_relaxed_write32(csa, config->vmid_mask0, TRCVMIDCCTLR0); - if (drvdata->numvmidc > 4) - etm4x_relaxed_write32(csa, config->vmid_mask1, TRCVMIDCCTLR1); - - if (!drvdata->skip_power_up) { + for (i = 0; i < caps->numcidc; i++) + etm4x_relaxed_write64(csa, curr_config->ctxid_pid[i], TRCCIDCVRn(i)); + etm4x_relaxed_write32(csa, curr_config->ctxid_mask0, TRCCIDCCTLR0); + if (caps->numcidc > 4) + etm4x_relaxed_write32(csa, curr_config->ctxid_mask1, TRCCIDCCTLR1); + + for (i = 0; i < caps->numvmidc; i++) + etm4x_relaxed_write64(csa, curr_config->vmid_val[i], TRCVMIDCVRn(i)); + etm4x_relaxed_write32(csa, curr_config->vmid_mask0, TRCVMIDCCTLR0); + if (caps->numvmidc > 4) + etm4x_relaxed_write32(csa, curr_config->vmid_mask1, TRCVMIDCCTLR1); + + if (!caps->skip_power_up) { u32 trcpdcr = etm4x_relaxed_read32(csa, TRCPDCR); /* @@ -612,27 +621,42 @@ done: static void etm4_enable_sysfs_smp_call(void *info) { struct etm4_enable_arg *arg = info; + struct etmv4_drvdata *drvdata; struct coresight_device *csdev; + unsigned long cfg_hash; + int preset; if (WARN_ON(!arg)) return; - csdev = arg->drvdata->csdev; - if (!coresight_take_mode(csdev, CS_MODE_SYSFS)) { - /* Someone is already using the tracer */ - arg->rc = -EBUSY; - return; + drvdata = arg->drvdata; + csdev = drvdata->csdev; + + /* enable any config activated by configfs */ + cscfg_config_sysfs_get_active_cfg(&cfg_hash, &preset); + if (cfg_hash) { + arg->rc = cscfg_csdev_enable_active_config(csdev, + cfg_hash, + preset); + if (arg->rc) + return; } - arg->rc = etm4_enable_hw(arg->drvdata); + drvdata->trcid = arg->path->trace_id; - /* The tracer didn't start */ + /* Tracer will never be paused in sysfs mode */ + drvdata->paused = false; + + arg->rc = etm4_enable_hw(drvdata); if (arg->rc) { - coresight_set_mode(csdev, CS_MODE_DISABLED); + cscfg_csdev_disable_active_config(csdev); return; } + drvdata->sticky_enable = true; csdev->path = arg->path; + + return; } /* @@ -669,19 +693,20 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata, { int ctridx; int rselector; - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *curr_config = &drvdata->curr_config; /* No point in trying if we don't have at least one counter */ - if (!drvdata->nr_cntr) + if (!caps->nr_cntr) return -EINVAL; /* Find a counter that hasn't been initialised */ - for (ctridx = 0; ctridx < drvdata->nr_cntr; ctridx++) - if (config->cntr_val[ctridx] == 0) + for (ctridx = 0; ctridx < caps->nr_cntr; ctridx++) + if (curr_config->cntr_val[ctridx] == 0) break; /* All the counters have been configured already, bail out */ - if (ctridx == drvdata->nr_cntr) { + if (ctridx == caps->nr_cntr) { pr_debug("%s: no available counter found\n", __func__); return -ENOSPC; } @@ -697,18 +722,18 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata, * ETMIDR4 gives the number of resource selector _pairs_, hence multiply * by 2. */ - for (rselector = 2; rselector < drvdata->nr_resource * 2; rselector++) - if (!config->res_ctrl[rselector]) + for (rselector = 2; rselector < caps->nr_resource * 2; rselector++) + if (!curr_config->res_ctrl[rselector]) break; - if (rselector == drvdata->nr_resource * 2) { + if (rselector == caps->nr_resource * 2) { pr_debug("%s: no available resource selector found\n", __func__); return -ENOSPC; } /* Initialise original and reload counter value. */ - config->cntr_val[ctridx] = config->cntrldvr[ctridx] = 1 << (ts_level - 1); + curr_config->cntr_val[ctridx] = curr_config->cntrldvr[ctridx] = 1 << (ts_level - 1); /* * Trace Counter Control Register TRCCNTCTLRn @@ -718,11 +743,11 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata, * RLDEVENT = RES_SEL_FALSE (0), reload on single false resource (never reload) * CNTEVENT = RES_SEL_TRUE (1), count single fixed 'always true' resource (always decrement) */ - config->cntr_ctrl[ctridx] = TRCCNTCTLRn_RLDSELF | - FIELD_PREP(TRCCNTCTLRn_RLDEVENT_MASK, - etm4_res_sel_single(ETM4_RES_SEL_FALSE)) | - FIELD_PREP(TRCCNTCTLRn_CNTEVENT_MASK, - etm4_res_sel_single(ETM4_RES_SEL_TRUE)); + curr_config->cntr_ctrl[ctridx] = TRCCNTCTLRn_RLDSELF | + FIELD_PREP(TRCCNTCTLRn_RLDEVENT_MASK, + etm4_res_sel_single(ETM4_RES_SEL_FALSE)) | + FIELD_PREP(TRCCNTCTLRn_CNTEVENT_MASK, + etm4_res_sel_single(ETM4_RES_SEL_TRUE)); /* * Resource Selection Control Register TRCRSCTLRn @@ -733,16 +758,16 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata, * Multiple counters can be selected, and each bit signifies a counter, * so set bit 'ctridx' to select our counter. */ - config->res_ctrl[rselector] = FIELD_PREP(TRCRSCTLRn_GROUP_MASK, 2) | - FIELD_PREP(TRCRSCTLRn_SELECT_MASK, 1 << ctridx); + curr_config->res_ctrl[rselector] = FIELD_PREP(TRCRSCTLRn_GROUP_MASK, 2) | + FIELD_PREP(TRCRSCTLRn_SELECT_MASK, 1 << ctridx); /* * Global Timestamp Control Register TRCTSCTLR * * EVENT = generate timestamp on single resource 'rselector' */ - config->ts_ctrl = FIELD_PREP(TRCTSCTLR_EVENT_MASK, - etm4_res_sel_single(rselector)); + curr_config->ts_ctrl = FIELD_PREP(TRCTSCTLR_EVENT_MASK, + etm4_res_sel_single(rselector)); return 0; } @@ -752,7 +777,8 @@ static int etm4_parse_event_config(struct coresight_device *csdev, { int ret = 0; struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *curr_config = &drvdata->curr_config; struct perf_event_attr max_timestamp = { .ATTR_CFG_FLD_timestamp_CFG = U64_MAX, }; @@ -761,22 +787,22 @@ static int etm4_parse_event_config(struct coresight_device *csdev, u8 ts_level; /* Clear configuration from previous run */ - memset(config, 0, sizeof(struct etmv4_config)); + memset(curr_config, 0, sizeof(struct etmv4_config)); if (attr->exclude_kernel) - config->mode = ETM_MODE_EXCL_KERN; + curr_config->mode = ETM_MODE_EXCL_KERN; if (attr->exclude_user) - config->mode = ETM_MODE_EXCL_USER; + curr_config->mode = ETM_MODE_EXCL_USER; if (attr->exclude_host) - config->mode |= ETM_MODE_EXCL_HOST; + curr_config->mode |= ETM_MODE_EXCL_HOST; if (attr->exclude_guest) - config->mode |= ETM_MODE_EXCL_GUEST; + curr_config->mode |= ETM_MODE_EXCL_GUEST; /* Always start from the default config */ - etm4_set_default_config(config); + etm4_set_default_config(curr_config, caps); /* Configure filters specified on the perf cmd line, if any. */ ret = etm4_set_event_filters(drvdata, event); @@ -785,14 +811,14 @@ static int etm4_parse_event_config(struct coresight_device *csdev, /* Go from generic option to ETMv4 specifics */ if (ATTR_CFG_GET_FLD(attr, cycacc)) { - config->cfg |= TRCCONFIGR_CCI; + curr_config->cfg |= TRCCONFIGR_CCI; /* TRM: Must program this for cycacc to work */ cc_threshold = ATTR_CFG_GET_FLD(attr, cc_threshold); if (!cc_threshold) cc_threshold = ETM_CYC_THRESHOLD_DEFAULT; - if (cc_threshold < drvdata->ccitmin) - cc_threshold = drvdata->ccitmin; - config->ccctlr = cc_threshold; + if (cc_threshold < caps->ccitmin) + cc_threshold = caps->ccitmin; + curr_config->ccctlr = cc_threshold; } ts_level = max(ATTR_CFG_GET_FLD(attr, timestamp), @@ -814,14 +840,14 @@ static int etm4_parse_event_config(struct coresight_device *csdev, } /* bit[11], Global timestamp tracing bit */ - config->cfg |= TRCCONFIGR_TS; + curr_config->cfg |= TRCCONFIGR_TS; } /* Only trace contextID when runs in root PID namespace */ if (ATTR_CFG_GET_FLD(attr, contextid1) && task_is_in_init_pid_ns(current)) /* bit[6], Context ID tracing bit */ - config->cfg |= TRCCONFIGR_CID; + curr_config->cfg |= TRCCONFIGR_CID; /* * If set bit contextid2 in perf config, this asks to trace VMID for @@ -835,17 +861,17 @@ static int etm4_parse_event_config(struct coresight_device *csdev, } /* Only trace virtual contextID when runs in root PID namespace */ if (task_is_in_init_pid_ns(current)) - config->cfg |= TRCCONFIGR_VMID | TRCCONFIGR_VMIDOPT; + curr_config->cfg |= TRCCONFIGR_VMID | TRCCONFIGR_VMIDOPT; } /* return stack - enable if selected and supported */ - if (ATTR_CFG_GET_FLD(attr, retstack) && drvdata->retstack) + if (ATTR_CFG_GET_FLD(attr, retstack) && caps->retstack) /* bit[12], Return stack enable bit */ - config->cfg |= TRCCONFIGR_RS; + curr_config->cfg |= TRCCONFIGR_RS; /* branch broadcast - enable if selected and supported */ if (ATTR_CFG_GET_FLD(attr, branch_broadcast)) { - if (!drvdata->trcbb) { + if (!caps->trcbb) { /* * Missing BB support could cause silent decode errors * so fail to open if it's not supported. @@ -853,7 +879,7 @@ static int etm4_parse_event_config(struct coresight_device *csdev, ret = -EINVAL; goto out; } else { - config->cfg |= TRCCONFIGR_BB; + curr_config->cfg |= TRCCONFIGR_BB; } } @@ -919,46 +945,36 @@ static int etm4_enable_sysfs(struct coresight_device *csdev, struct coresight_pa { struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); struct etm4_enable_arg arg = { }; - unsigned long cfg_hash; - int ret, preset; + int ret; - /* enable any config activated by configfs */ - cscfg_config_sysfs_get_active_cfg(&cfg_hash, &preset); - if (cfg_hash) { - ret = cscfg_csdev_enable_active_config(csdev, cfg_hash, preset); - if (ret) { - etm4_release_trace_id(drvdata); - return ret; - } + if (!coresight_take_mode(csdev, CS_MODE_SYSFS)) { + /* Someone is already using the tracer */ + return -EBUSY; } - raw_spin_lock(&drvdata->spinlock); - - drvdata->trcid = path->trace_id; - - /* Tracer will never be paused in sysfs mode */ - drvdata->paused = false; - /* * Executing etm4_enable_hw on the cpu whose ETM is being enabled * ensures that register writes occur when cpu is powered. */ arg.drvdata = drvdata; arg.path = path; + + scoped_guard(raw_spinlock, &drvdata->spinlock) { + drvdata->curr_config = drvdata->sysfs_config; + } + ret = smp_call_function_single(drvdata->cpu, etm4_enable_sysfs_smp_call, &arg, 1); if (!ret) ret = arg.rc; - if (!ret) - drvdata->sticky_enable = true; - - if (ret) + if (!ret) { + dev_dbg(&csdev->dev, "ETM tracing enabled\n"); + } else { etm4_release_trace_id(drvdata); + /* The tracer didn't start */ + coresight_set_mode(csdev, CS_MODE_DISABLED); + } - raw_spin_unlock(&drvdata->spinlock); - - if (!ret) - dev_dbg(&csdev->dev, "ETM tracing enabled\n"); return ret; } @@ -1044,7 +1060,8 @@ static void etm4_disable_trace_unit(struct etmv4_drvdata *drvdata) static void etm4_disable_hw(struct etmv4_drvdata *drvdata) { u32 control; - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *curr_config = &drvdata->curr_config; struct coresight_device *csdev = drvdata->csdev; struct csdev_access *csa = &csdev->access; int i; @@ -1052,7 +1069,7 @@ static void etm4_disable_hw(struct etmv4_drvdata *drvdata) etm4_cs_unlock(drvdata, csa); etm4_disable_arch_specific(drvdata); - if (!drvdata->skip_power_up) { + if (!caps->skip_power_up) { /* power can be removed from the trace unit now */ control = etm4x_relaxed_read32(csa, TRCPDCR); control &= ~TRCPDCR_PU; @@ -1062,17 +1079,21 @@ static void etm4_disable_hw(struct etmv4_drvdata *drvdata) etm4_disable_trace_unit(drvdata); /* read the status of the single shot comparators */ - for (i = 0; i < drvdata->nr_ss_cmp; i++) { - config->ss_status[i] = + for (i = 0; i < caps->nr_ss_cmp; i++) { + drvdata->ss_status[i] = etm4x_relaxed_read32(csa, TRCSSCSRn(i)); } /* read back the current counter values */ - for (i = 0; i < drvdata->nr_cntr; i++) { - config->cntr_val[i] = - etm4x_relaxed_read32(csa, TRCCNTVRn(i)); + for (i = 0; i < caps->nr_cntr; i++) { + curr_config->cntr_val[i] = + etm4x_relaxed_read32(csa, TRCCNTVRn(i)); } + /* read back the sequence state */ + if (caps->nrseqstate) + curr_config->seq_state = etm4x_relaxed_read32(csa, TRCSEQSTR); + coresight_disclaim_device_unlocked(csdev); etm4_cs_lock(drvdata, csa); @@ -1086,8 +1107,9 @@ static void etm4_disable_sysfs_smp_call(void *info) etm4_disable_hw(drvdata); + cscfg_csdev_disable_active_config(drvdata->csdev); + drvdata->csdev->path = NULL; - coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED); } static int etm4_disable_perf(struct coresight_device *csdev, @@ -1130,8 +1152,10 @@ static int etm4_disable_perf(struct coresight_device *csdev, static void etm4_disable_sysfs(struct coresight_device *csdev) { struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - raw_spin_lock(&drvdata->spinlock); + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; + const struct etmv4_config *curr_config = &drvdata->curr_config; + int i; /* * Executing etm4_disable_hw on the cpu whose ETM is being disabled @@ -1140,9 +1164,16 @@ static void etm4_disable_sysfs(struct coresight_device *csdev) smp_call_function_single(drvdata->cpu, etm4_disable_sysfs_smp_call, drvdata, 1); - raw_spin_unlock(&drvdata->spinlock); + /* + * Userspace may read cntr_val through sysfs after + * the sysfs-session has been disabled. + */ + for (i = 0; i < caps->nr_cntr; i++) + sysfs_config->cntr_val[i] = curr_config->cntr_val[i]; - cscfg_csdev_disable_active_config(csdev); + sysfs_config->seq_state = curr_config->seq_state; + + coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED); /* * we only release trace IDs when resetting sysfs. @@ -1353,7 +1384,7 @@ static struct midr_range etm_wrong_ccitmin_cpus[] = { {}, }; -static void etm4_fixup_wrong_ccitmin(struct etmv4_drvdata *drvdata) +static void etm4_fixup_wrong_ccitmin(struct etmv4_caps *caps) { /* * Erratum affected cpus will read 256 as the minimum @@ -1363,8 +1394,8 @@ static void etm4_fixup_wrong_ccitmin(struct etmv4_drvdata *drvdata) * this problem. */ if (is_midr_in_range_list(etm_wrong_ccitmin_cpus)) { - if (drvdata->ccitmin == 256) - drvdata->ccitmin = 4; + if (caps->ccitmin == 256) + caps->ccitmin = 4; } } @@ -1377,11 +1408,13 @@ static void etm4_init_arch_data(void *info) u32 etmidr5; struct etm4_init_arg *init_arg = info; struct etmv4_drvdata *drvdata; + struct etmv4_caps *caps; struct csdev_access *csa; struct device *dev = init_arg->dev; int i; drvdata = dev_get_drvdata(init_arg->dev); + caps = &drvdata->caps; csa = init_arg->csa; /* @@ -1394,7 +1427,7 @@ static void etm4_init_arch_data(void *info) if (!csa->io_mem || fwnode_property_present(dev_fwnode(dev), "qcom,skip-power-up")) - drvdata->skip_power_up = true; + caps->skip_power_up = true; /* Detect the support for OS Lock before we actually use it */ etm_detect_os_lock(drvdata, csa); @@ -1409,71 +1442,70 @@ static void etm4_init_arch_data(void *info) etmidr0 = etm4x_relaxed_read32(csa, TRCIDR0); /* INSTP0, bits[2:1] P0 tracing support field */ - drvdata->instrp0 = !!(FIELD_GET(TRCIDR0_INSTP0_MASK, etmidr0) == 0b11); + caps->instrp0 = !!(FIELD_GET(TRCIDR0_INSTP0_MASK, etmidr0) == 0b11); /* TRCBB, bit[5] Branch broadcast tracing support bit */ - drvdata->trcbb = !!(etmidr0 & TRCIDR0_TRCBB); + caps->trcbb = !!(etmidr0 & TRCIDR0_TRCBB); /* TRCCOND, bit[6] Conditional instruction tracing support bit */ - drvdata->trccond = !!(etmidr0 & TRCIDR0_TRCCOND); + caps->trccond = !!(etmidr0 & TRCIDR0_TRCCOND); /* TRCCCI, bit[7] Cycle counting instruction bit */ - drvdata->trccci = !!(etmidr0 & TRCIDR0_TRCCCI); + caps->trccci = !!(etmidr0 & TRCIDR0_TRCCCI); /* RETSTACK, bit[9] Return stack bit */ - drvdata->retstack = !!(etmidr0 & TRCIDR0_RETSTACK); + caps->retstack = !!(etmidr0 & TRCIDR0_RETSTACK); /* NUMEVENT, bits[11:10] Number of events field */ - drvdata->nr_event = FIELD_GET(TRCIDR0_NUMEVENT_MASK, etmidr0); + caps->nr_event = FIELD_GET(TRCIDR0_NUMEVENT_MASK, etmidr0); /* QSUPP, bits[16:15] Q element support field */ - drvdata->q_support = FIELD_GET(TRCIDR0_QSUPP_MASK, etmidr0); - if (drvdata->q_support) - drvdata->q_filt = !!(etmidr0 & TRCIDR0_QFILT); + caps->q_support = FIELD_GET(TRCIDR0_QSUPP_MASK, etmidr0); + if (caps->q_support) + caps->q_filt = !!(etmidr0 & TRCIDR0_QFILT); /* TSSIZE, bits[28:24] Global timestamp size field */ - drvdata->ts_size = FIELD_GET(TRCIDR0_TSSIZE_MASK, etmidr0); + caps->ts_size = FIELD_GET(TRCIDR0_TSSIZE_MASK, etmidr0); /* maximum size of resources */ etmidr2 = etm4x_relaxed_read32(csa, TRCIDR2); /* CIDSIZE, bits[9:5] Indicates the Context ID size */ - drvdata->ctxid_size = FIELD_GET(TRCIDR2_CIDSIZE_MASK, etmidr2); + caps->ctxid_size = FIELD_GET(TRCIDR2_CIDSIZE_MASK, etmidr2); /* VMIDSIZE, bits[14:10] Indicates the VMID size */ - drvdata->vmid_size = FIELD_GET(TRCIDR2_VMIDSIZE_MASK, etmidr2); + caps->vmid_size = FIELD_GET(TRCIDR2_VMIDSIZE_MASK, etmidr2); /* CCSIZE, bits[28:25] size of the cycle counter in bits minus 12 */ - drvdata->ccsize = FIELD_GET(TRCIDR2_CCSIZE_MASK, etmidr2); + caps->ccsize = FIELD_GET(TRCIDR2_CCSIZE_MASK, etmidr2); etmidr3 = etm4x_relaxed_read32(csa, TRCIDR3); /* CCITMIN, bits[11:0] minimum threshold value that can be programmed */ - drvdata->ccitmin = FIELD_GET(TRCIDR3_CCITMIN_MASK, etmidr3); - etm4_fixup_wrong_ccitmin(drvdata); + caps->ccitmin = FIELD_GET(TRCIDR3_CCITMIN_MASK, etmidr3); + etm4_fixup_wrong_ccitmin(caps); /* EXLEVEL_S, bits[19:16] Secure state instruction tracing */ - drvdata->s_ex_level = FIELD_GET(TRCIDR3_EXLEVEL_S_MASK, etmidr3); - drvdata->config.s_ex_level = drvdata->s_ex_level; + caps->s_ex_level = FIELD_GET(TRCIDR3_EXLEVEL_S_MASK, etmidr3); /* EXLEVEL_NS, bits[23:20] Non-secure state instruction tracing */ - drvdata->ns_ex_level = FIELD_GET(TRCIDR3_EXLEVEL_NS_MASK, etmidr3); + caps->ns_ex_level = FIELD_GET(TRCIDR3_EXLEVEL_NS_MASK, etmidr3); /* * TRCERR, bit[24] whether a trace unit can trace a * system error exception. */ - drvdata->trc_error = !!(etmidr3 & TRCIDR3_TRCERR); + caps->trc_error = !!(etmidr3 & TRCIDR3_TRCERR); /* SYNCPR, bit[25] implementation has a fixed synchronization period? */ - drvdata->syncpr = !!(etmidr3 & TRCIDR3_SYNCPR); + caps->syncpr = !!(etmidr3 & TRCIDR3_SYNCPR); /* STALLCTL, bit[26] is stall control implemented? */ - drvdata->stallctl = !!(etmidr3 & TRCIDR3_STALLCTL); + caps->stallctl = !!(etmidr3 & TRCIDR3_STALLCTL); /* SYSSTALL, bit[27] implementation can support stall control? */ - drvdata->sysstall = !!(etmidr3 & TRCIDR3_SYSSTALL); + caps->sysstall = !!(etmidr3 & TRCIDR3_SYSSTALL); /* * NUMPROC - the number of PEs available for tracing, 5bits * = TRCIDR3.bits[13:12]bits[30:28] * bits[4:3] = TRCIDR3.bits[13:12] (since etm-v4.2, otherwise RES0) * bits[3:0] = TRCIDR3.bits[30:28] */ - drvdata->nr_pe = (FIELD_GET(TRCIDR3_NUMPROC_HI_MASK, etmidr3) << 3) | - FIELD_GET(TRCIDR3_NUMPROC_LO_MASK, etmidr3); + caps->nr_pe = (FIELD_GET(TRCIDR3_NUMPROC_HI_MASK, etmidr3) << 3) | + FIELD_GET(TRCIDR3_NUMPROC_LO_MASK, etmidr3); /* NOOVERFLOW, bit[31] is trace overflow prevention supported */ - drvdata->nooverflow = !!(etmidr3 & TRCIDR3_NOOVERFLOW); + caps->nooverflow = !!(etmidr3 & TRCIDR3_NOOVERFLOW); /* number of resources trace unit supports */ etmidr4 = etm4x_relaxed_read32(csa, TRCIDR4); /* NUMACPAIRS, bits[0:3] number of addr comparator pairs for tracing */ - drvdata->nr_addr_cmp = FIELD_GET(TRCIDR4_NUMACPAIRS_MASK, etmidr4); + caps->nr_addr_cmp = FIELD_GET(TRCIDR4_NUMACPAIRS_MASK, etmidr4); /* NUMPC, bits[15:12] number of PE comparator inputs for tracing */ - drvdata->nr_pe_cmp = FIELD_GET(TRCIDR4_NUMPC_MASK, etmidr4); + caps->nr_pe_cmp = FIELD_GET(TRCIDR4_NUMPC_MASK, etmidr4); /* * NUMRSPAIR, bits[19:16] * The number of resource pairs conveyed by the HW starts at 0, i.e a @@ -1484,62 +1516,64 @@ static void etm4_init_arch_data(void *info) * the default TRUE and FALSE resource selectors are omitted. * Otherwise for values 0x1 and above the number is N + 1 as per v4.2. */ - drvdata->nr_resource = FIELD_GET(TRCIDR4_NUMRSPAIR_MASK, etmidr4); - if ((drvdata->arch < ETM_ARCH_V4_3) || (drvdata->nr_resource > 0)) - drvdata->nr_resource += 1; + caps->nr_resource = FIELD_GET(TRCIDR4_NUMRSPAIR_MASK, etmidr4); + if ((drvdata->arch < ETM_ARCH_V4_3) || (caps->nr_resource > 0)) + caps->nr_resource += 1; /* * NUMSSCC, bits[23:20] the number of single-shot * comparator control for tracing. Read any status regs as these * also contain RO capability data. */ - drvdata->nr_ss_cmp = FIELD_GET(TRCIDR4_NUMSSCC_MASK, etmidr4); - for (i = 0; i < drvdata->nr_ss_cmp; i++) { - drvdata->config.ss_status[i] = - etm4x_relaxed_read32(csa, TRCSSCSRn(i)); + caps->nr_ss_cmp = FIELD_GET(TRCIDR4_NUMSSCC_MASK, etmidr4); + for (i = 0; i < caps->nr_ss_cmp; i++) { + drvdata->ss_status[i] = etm4x_relaxed_read32(csa, TRCSSCSRn(i)); } /* NUMCIDC, bits[27:24] number of Context ID comparators for tracing */ - drvdata->numcidc = FIELD_GET(TRCIDR4_NUMCIDC_MASK, etmidr4); + caps->numcidc = FIELD_GET(TRCIDR4_NUMCIDC_MASK, etmidr4); /* NUMVMIDC, bits[31:28] number of VMID comparators for tracing */ - drvdata->numvmidc = FIELD_GET(TRCIDR4_NUMVMIDC_MASK, etmidr4); + caps->numvmidc = FIELD_GET(TRCIDR4_NUMVMIDC_MASK, etmidr4); etmidr5 = etm4x_relaxed_read32(csa, TRCIDR5); /* NUMEXTIN, bits[8:0] number of external inputs implemented */ - drvdata->nr_ext_inp = FIELD_GET(TRCIDR5_NUMEXTIN_MASK, etmidr5); - drvdata->numextinsel = FIELD_GET(TRCIDR5_NUMEXTINSEL_MASK, etmidr5); + caps->nr_ext_inp = FIELD_GET(TRCIDR5_NUMEXTIN_MASK, etmidr5); + caps->numextinsel = FIELD_GET(TRCIDR5_NUMEXTINSEL_MASK, etmidr5); /* TRACEIDSIZE, bits[21:16] indicates the trace ID width */ - drvdata->trcid_size = FIELD_GET(TRCIDR5_TRACEIDSIZE_MASK, etmidr5); + caps->trcid_size = FIELD_GET(TRCIDR5_TRACEIDSIZE_MASK, etmidr5); /* ATBTRIG, bit[22] implementation can support ATB triggers? */ - drvdata->atbtrig = !!(etmidr5 & TRCIDR5_ATBTRIG); + caps->atbtrig = !!(etmidr5 & TRCIDR5_ATBTRIG); /* * LPOVERRIDE, bit[23] implementation supports * low-power state override */ - drvdata->lpoverride = (etmidr5 & TRCIDR5_LPOVERRIDE) && (!drvdata->skip_power_up); + caps->lpoverride = (etmidr5 & TRCIDR5_LPOVERRIDE) && (!caps->skip_power_up); /* NUMSEQSTATE, bits[27:25] number of sequencer states implemented */ - drvdata->nrseqstate = FIELD_GET(TRCIDR5_NUMSEQSTATE_MASK, etmidr5); - if (drvdata->nrseqstate) - drvdata->nr_seq_ctrls = ETM_MAX_SEQ_TRANSITIONS; + caps->nrseqstate = FIELD_GET(TRCIDR5_NUMSEQSTATE_MASK, etmidr5); + if (caps->nrseqstate) + caps->nr_seq_ctrls = ETM_MAX_SEQ_TRANSITIONS; /* NUMCNTR, bits[30:28] number of counters available for tracing */ - drvdata->nr_cntr = FIELD_GET(TRCIDR5_NUMCNTR_MASK, etmidr5); + caps->nr_cntr = FIELD_GET(TRCIDR5_NUMCNTR_MASK, etmidr5); coresight_clear_self_claim_tag_unlocked(csa); etm4_cs_lock(drvdata, csa); cpu_detect_trace_filtering(drvdata); } -static u32 etm4_get_victlr_access_type(struct etmv4_config *config) +static u32 etm4_get_victlr_access_type(struct etmv4_config *config, + const struct etmv4_caps *caps) { - return etm4_get_access_type(config) << __bf_shf(TRCVICTLR_EXLEVEL_MASK); + return etm4_get_access_type(config, caps) << __bf_shf(TRCVICTLR_EXLEVEL_MASK); } /* Set ELx trace filter access in the TRCVICTLR register */ -static void etm4_set_victlr_access(struct etmv4_config *config) +static void etm4_set_victlr_access(struct etmv4_config *config, + const struct etmv4_caps *caps) { config->vinst_ctrl &= ~TRCVICTLR_EXLEVEL_MASK; - config->vinst_ctrl |= etm4_get_victlr_access_type(config); + config->vinst_ctrl |= etm4_get_victlr_access_type(config, caps); } -static void etm4_set_default_config(struct etmv4_config *config) +static void etm4_set_default_config(struct etmv4_config *config, + const struct etmv4_caps *caps) { /* disable all events tracing */ config->eventctrl0 = 0x0; @@ -1558,7 +1592,7 @@ static void etm4_set_default_config(struct etmv4_config *config) config->vinst_ctrl = FIELD_PREP(TRCVICTLR_EVENT_MASK, 0x01); /* TRCVICTLR::EXLEVEL_NS:EXLEVELS: Set kernel / user filtering */ - etm4_set_victlr_access(config); + etm4_set_victlr_access(config, caps); } static u64 etm4_get_ns_access_type(struct etmv4_config *config) @@ -1590,21 +1624,24 @@ static u64 etm4_get_ns_access_type(struct etmv4_config *config) * This must be shifted to the corresponding register field * for usage. */ -static u64 etm4_get_access_type(struct etmv4_config *config) +static u64 etm4_get_access_type(struct etmv4_config *config, + const struct etmv4_caps *caps) { /* All Secure exception levels are excluded from the trace */ - return etm4_get_ns_access_type(config) | (u64)config->s_ex_level; + return etm4_get_ns_access_type(config) | (u64)caps->s_ex_level; } -static u64 etm4_get_comparator_access_type(struct etmv4_config *config) +static u64 etm4_get_comparator_access_type(struct etmv4_config *config, + const struct etmv4_caps *caps) { - return etm4_get_access_type(config) << TRCACATR_EXLEVEL_SHIFT; + return etm4_get_access_type(config, caps) << TRCACATR_EXLEVEL_SHIFT; } static void etm4_set_comparator_filter(struct etmv4_config *config, + const struct etmv4_caps *caps, u64 start, u64 stop, int comparator) { - u64 access_type = etm4_get_comparator_access_type(config); + u64 access_type = etm4_get_comparator_access_type(config, caps); /* First half of default address comparator */ config->addr_val[comparator] = start; @@ -1635,11 +1672,12 @@ static void etm4_set_comparator_filter(struct etmv4_config *config, } static void etm4_set_start_stop_filter(struct etmv4_config *config, + const struct etmv4_caps *caps, u64 address, int comparator, enum etm_addr_type type) { int shift; - u64 access_type = etm4_get_comparator_access_type(config); + u64 access_type = etm4_get_comparator_access_type(config, caps); /* Configure the comparator */ config->addr_val[comparator] = address; @@ -1671,7 +1709,8 @@ static void etm4_set_default_filter(struct etmv4_config *config) config->vissctlr = 0x0; } -static void etm4_set_default(struct etmv4_config *config) +static void etm4_set_default(struct etmv4_config *config, + const struct etmv4_caps *caps) { if (WARN_ON_ONCE(!config)) return; @@ -1683,27 +1722,27 @@ static void etm4_set_default(struct etmv4_config *config) * full instruction trace - with a default filter for trace all * achieved by having no filtering. */ - etm4_set_default_config(config); + etm4_set_default_config(config, caps); etm4_set_default_filter(config); } static int etm4_get_next_comparator(struct etmv4_drvdata *drvdata, u32 type) { int nr_comparator, index = 0; - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *curr_config = &drvdata->curr_config; /* * nr_addr_cmp holds the number of comparator _pair_, so time 2 * for the total number of comparators. */ - nr_comparator = drvdata->nr_addr_cmp * 2; + nr_comparator = drvdata->caps.nr_addr_cmp * 2; /* Go through the tally of comparators looking for a free one. */ while (index < nr_comparator) { switch (type) { case ETM_ADDR_TYPE_RANGE: - if (config->addr_type[index] == ETM_ADDR_TYPE_NONE && - config->addr_type[index + 1] == ETM_ADDR_TYPE_NONE) + if (curr_config->addr_type[index] == ETM_ADDR_TYPE_NONE && + curr_config->addr_type[index + 1] == ETM_ADDR_TYPE_NONE) return index; /* Address range comparators go in pairs */ @@ -1711,7 +1750,7 @@ static int etm4_get_next_comparator(struct etmv4_drvdata *drvdata, u32 type) break; case ETM_ADDR_TYPE_START: case ETM_ADDR_TYPE_STOP: - if (config->addr_type[index] == ETM_ADDR_TYPE_NONE) + if (curr_config->addr_type[index] == ETM_ADDR_TYPE_NONE) return index; /* Start/stop address can have odd indexes */ @@ -1731,7 +1770,8 @@ static int etm4_set_event_filters(struct etmv4_drvdata *drvdata, { int i, comparator, ret = 0; u64 address; - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *curr_config = &drvdata->curr_config; struct etm_filters *filters = event->hw.addr_filters; if (!filters) @@ -1760,7 +1800,7 @@ static int etm4_set_event_filters(struct etmv4_drvdata *drvdata, switch (type) { case ETM_ADDR_TYPE_RANGE: - etm4_set_comparator_filter(config, + etm4_set_comparator_filter(curr_config, caps, filter->start_addr, filter->stop_addr, comparator); @@ -1768,10 +1808,10 @@ static int etm4_set_event_filters(struct etmv4_drvdata *drvdata, * TRCVICTLR::SSSTATUS == 1, the start-stop logic is * in the started state */ - config->vinst_ctrl |= TRCVICTLR_SSSTATUS; + curr_config->vinst_ctrl |= TRCVICTLR_SSSTATUS; /* No start-stop filtering for ViewInst */ - config->vissctlr = 0x0; + curr_config->vissctlr = 0x0; break; case ETM_ADDR_TYPE_START: case ETM_ADDR_TYPE_STOP: @@ -1781,7 +1821,7 @@ static int etm4_set_event_filters(struct etmv4_drvdata *drvdata, filter->stop_addr); /* Configure comparator */ - etm4_set_start_stop_filter(config, address, + etm4_set_start_stop_filter(curr_config, caps, address, comparator, type); /* @@ -1796,10 +1836,10 @@ static int etm4_set_event_filters(struct etmv4_drvdata *drvdata, * etm4_disable_perf(). */ if (filters->ssstatus) - config->vinst_ctrl |= TRCVICTLR_SSSTATUS; + curr_config->vinst_ctrl |= TRCVICTLR_SSSTATUS; /* No include/exclude filtering for ViewInst */ - config->viiectlr = 0x0; + curr_config->viiectlr = 0x0; break; default: ret = -EINVAL; @@ -1811,13 +1851,14 @@ static int etm4_set_event_filters(struct etmv4_drvdata *drvdata, default_filter: - etm4_set_default_filter(config); + etm4_set_default_filter(curr_config); out: return ret; } -void etm4_config_trace_mode(struct etmv4_config *config) +void etm4_config_trace_mode(struct etmv4_config *config, + const struct etmv4_caps *caps) { u32 mode; @@ -1831,7 +1872,7 @@ void etm4_config_trace_mode(struct etmv4_config *config) if (!(mode & ETM_MODE_EXCL_KERN) && !(mode & ETM_MODE_EXCL_USER)) return; - etm4_set_victlr_access(config); + etm4_set_victlr_access(config, caps); } static int etm4_online_cpu(unsigned int cpu) @@ -1853,6 +1894,7 @@ static int etm4_cpu_save(struct coresight_device *csdev) { int i, ret = 0; struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + const struct etmv4_caps *caps = &drvdata->caps; struct etmv4_save_state *state; struct csdev_access *csa; struct device *etm_dev; @@ -1885,57 +1927,56 @@ static int etm4_cpu_save(struct coresight_device *csdev) state = drvdata->save_state; - if (drvdata->nr_pe) + if (caps->nr_pe) state->trcprocselr = etm4x_read32(csa, TRCPROCSELR); state->trcconfigr = etm4x_read32(csa, TRCCONFIGR); state->trcauxctlr = etm4x_read32(csa, TRCAUXCTLR); state->trceventctl0r = etm4x_read32(csa, TRCEVENTCTL0R); state->trceventctl1r = etm4x_read32(csa, TRCEVENTCTL1R); - if (drvdata->stallctl) + if (caps->stallctl) state->trcstallctlr = etm4x_read32(csa, TRCSTALLCTLR); state->trctsctlr = etm4x_read32(csa, TRCTSCTLR); state->trcsyncpr = etm4x_read32(csa, TRCSYNCPR); state->trcccctlr = etm4x_read32(csa, TRCCCCTLR); state->trcbbctlr = etm4x_read32(csa, TRCBBCTLR); state->trctraceidr = etm4x_read32(csa, TRCTRACEIDR); - if (drvdata->q_filt) + if (caps->q_filt) state->trcqctlr = etm4x_read32(csa, TRCQCTLR); state->trcvictlr = etm4x_read32(csa, TRCVICTLR); state->trcviiectlr = etm4x_read32(csa, TRCVIIECTLR); state->trcvissctlr = etm4x_read32(csa, TRCVISSCTLR); - if (drvdata->nr_pe_cmp) + if (caps->nr_pe_cmp) state->trcvipcssctlr = etm4x_read32(csa, TRCVIPCSSCTLR); - for (i = 0; i < drvdata->nr_seq_ctrls; i++) + for (i = 0; i < caps->nr_seq_ctrls; i++) state->trcseqevr[i] = etm4x_read32(csa, TRCSEQEVRn(i)); - - if (drvdata->nrseqstate) { + if (caps->nrseqstate) { state->trcseqrstevr = etm4x_read32(csa, TRCSEQRSTEVR); state->trcseqstr = etm4x_read32(csa, TRCSEQSTR); } - if (drvdata->numextinsel) + if (caps->numextinsel) state->trcextinselr = etm4x_read32(csa, TRCEXTINSELR); - for (i = 0; i < drvdata->nr_cntr; i++) { + for (i = 0; i < caps->nr_cntr; i++) { state->trccntrldvr[i] = etm4x_read32(csa, TRCCNTRLDVRn(i)); state->trccntctlr[i] = etm4x_read32(csa, TRCCNTCTLRn(i)); state->trccntvr[i] = etm4x_read32(csa, TRCCNTVRn(i)); } /* Resource selector pair 0 is reserved */ - for (i = 2; i < drvdata->nr_resource * 2; i++) + for (i = 2; i < caps->nr_resource * 2; i++) state->trcrsctlr[i] = etm4x_read32(csa, TRCRSCTLRn(i)); - for (i = 0; i < drvdata->nr_ss_cmp; i++) { + for (i = 0; i < caps->nr_ss_cmp; i++) { state->trcssccr[i] = etm4x_read32(csa, TRCSSCCRn(i)); state->trcsscsr[i] = etm4x_read32(csa, TRCSSCSRn(i)); if (etm4x_sspcicrn_present(drvdata, i)) state->trcsspcicr[i] = etm4x_read32(csa, TRCSSPCICRn(i)); } - for (i = 0; i < drvdata->nr_addr_cmp * 2; i++) { + for (i = 0; i < caps->nr_addr_cmp * 2; i++) { state->trcacvr[i] = etm4x_read64(csa, TRCACVRn(i)); state->trcacatr[i] = etm4x_read64(csa, TRCACATRn(i)); } @@ -1947,23 +1988,23 @@ static int etm4_cpu_save(struct coresight_device *csdev) * unit") of ARM IHI 0064D. */ - for (i = 0; i < drvdata->numcidc; i++) + for (i = 0; i < caps->numcidc; i++) state->trccidcvr[i] = etm4x_read64(csa, TRCCIDCVRn(i)); - for (i = 0; i < drvdata->numvmidc; i++) + for (i = 0; i < caps->numvmidc; i++) state->trcvmidcvr[i] = etm4x_read64(csa, TRCVMIDCVRn(i)); state->trccidcctlr0 = etm4x_read32(csa, TRCCIDCCTLR0); - if (drvdata->numcidc > 4) + if (caps->numcidc > 4) state->trccidcctlr1 = etm4x_read32(csa, TRCCIDCCTLR1); state->trcvmidcctlr0 = etm4x_read32(csa, TRCVMIDCCTLR0); - if (drvdata->numvmidc > 4) + if (caps->numvmidc > 4) state->trcvmidcctlr1 = etm4x_read32(csa, TRCVMIDCCTLR1); state->trcclaimset = etm4x_read32(csa, TRCCLAIMCLR); - if (!drvdata->skip_power_up) + if (!caps->skip_power_up) state->trcpdcr = etm4x_read32(csa, TRCPDCR); /* wait for TRCSTATR.IDLE to go up */ @@ -1980,7 +2021,7 @@ static int etm4_cpu_save(struct coresight_device *csdev) * potentially save power on systems that respect the TRCPDCR_PU * despite requesting software to save/restore state. */ - if (!drvdata->skip_power_up) + if (!caps->skip_power_up) etm4x_relaxed_write32(csa, (state->trcpdcr & ~TRCPDCR_PU), TRCPDCR); out: @@ -1992,83 +2033,84 @@ static void etm4_cpu_restore(struct coresight_device *csdev) { int i; struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + const struct etmv4_caps *caps = &drvdata->caps; struct etmv4_save_state *state = drvdata->save_state; struct csdev_access *csa = &drvdata->csdev->access; etm4_cs_unlock(drvdata, csa); etm4x_relaxed_write32(csa, state->trcclaimset, TRCCLAIMSET); - if (drvdata->nr_pe) + if (caps->nr_pe) etm4x_relaxed_write32(csa, state->trcprocselr, TRCPROCSELR); etm4x_relaxed_write32(csa, state->trcconfigr, TRCCONFIGR); etm4x_relaxed_write32(csa, state->trcauxctlr, TRCAUXCTLR); etm4x_relaxed_write32(csa, state->trceventctl0r, TRCEVENTCTL0R); etm4x_relaxed_write32(csa, state->trceventctl1r, TRCEVENTCTL1R); - if (drvdata->stallctl) + if (caps->stallctl) etm4x_relaxed_write32(csa, state->trcstallctlr, TRCSTALLCTLR); etm4x_relaxed_write32(csa, state->trctsctlr, TRCTSCTLR); etm4x_relaxed_write32(csa, state->trcsyncpr, TRCSYNCPR); etm4x_relaxed_write32(csa, state->trcccctlr, TRCCCCTLR); etm4x_relaxed_write32(csa, state->trcbbctlr, TRCBBCTLR); etm4x_relaxed_write32(csa, state->trctraceidr, TRCTRACEIDR); - if (drvdata->q_filt) + if (caps->q_filt) etm4x_relaxed_write32(csa, state->trcqctlr, TRCQCTLR); etm4x_relaxed_write32(csa, state->trcvictlr, TRCVICTLR); etm4x_relaxed_write32(csa, state->trcviiectlr, TRCVIIECTLR); etm4x_relaxed_write32(csa, state->trcvissctlr, TRCVISSCTLR); - if (drvdata->nr_pe_cmp) + if (caps->nr_pe_cmp) etm4x_relaxed_write32(csa, state->trcvipcssctlr, TRCVIPCSSCTLR); - for (i = 0; i < drvdata->nr_seq_ctrls; i++) + for (i = 0; i < caps->nr_seq_ctrls; i++) etm4x_relaxed_write32(csa, state->trcseqevr[i], TRCSEQEVRn(i)); - if (drvdata->nrseqstate) { + if (caps->nrseqstate) { etm4x_relaxed_write32(csa, state->trcseqrstevr, TRCSEQRSTEVR); etm4x_relaxed_write32(csa, state->trcseqstr, TRCSEQSTR); } - if (drvdata->numextinsel) + if (caps->numextinsel) etm4x_relaxed_write32(csa, state->trcextinselr, TRCEXTINSELR); - for (i = 0; i < drvdata->nr_cntr; i++) { + for (i = 0; i < caps->nr_cntr; i++) { etm4x_relaxed_write32(csa, state->trccntrldvr[i], TRCCNTRLDVRn(i)); etm4x_relaxed_write32(csa, state->trccntctlr[i], TRCCNTCTLRn(i)); etm4x_relaxed_write32(csa, state->trccntvr[i], TRCCNTVRn(i)); } /* Resource selector pair 0 is reserved */ - for (i = 2; i < drvdata->nr_resource * 2; i++) + for (i = 2; i < caps->nr_resource * 2; i++) etm4x_relaxed_write32(csa, state->trcrsctlr[i], TRCRSCTLRn(i)); - for (i = 0; i < drvdata->nr_ss_cmp; i++) { + for (i = 0; i < caps->nr_ss_cmp; i++) { etm4x_relaxed_write32(csa, state->trcssccr[i], TRCSSCCRn(i)); etm4x_relaxed_write32(csa, state->trcsscsr[i], TRCSSCSRn(i)); if (etm4x_sspcicrn_present(drvdata, i)) etm4x_relaxed_write32(csa, state->trcsspcicr[i], TRCSSPCICRn(i)); } - for (i = 0; i < drvdata->nr_addr_cmp * 2; i++) { + for (i = 0; i < caps->nr_addr_cmp * 2; i++) { etm4x_relaxed_write64(csa, state->trcacvr[i], TRCACVRn(i)); etm4x_relaxed_write64(csa, state->trcacatr[i], TRCACATRn(i)); } - for (i = 0; i < drvdata->numcidc; i++) + for (i = 0; i < caps->numcidc; i++) etm4x_relaxed_write64(csa, state->trccidcvr[i], TRCCIDCVRn(i)); - for (i = 0; i < drvdata->numvmidc; i++) + for (i = 0; i < caps->numvmidc; i++) etm4x_relaxed_write64(csa, state->trcvmidcvr[i], TRCVMIDCVRn(i)); etm4x_relaxed_write32(csa, state->trccidcctlr0, TRCCIDCCTLR0); - if (drvdata->numcidc > 4) + if (caps->numcidc > 4) etm4x_relaxed_write32(csa, state->trccidcctlr1, TRCCIDCCTLR1); etm4x_relaxed_write32(csa, state->trcvmidcctlr0, TRCVMIDCCTLR0); - if (drvdata->numvmidc > 4) + if (caps->numvmidc > 4) etm4x_relaxed_write32(csa, state->trcvmidcctlr1, TRCVMIDCCTLR1); etm4x_relaxed_write32(csa, state->trcclaimset, TRCCLAIMSET); - if (!drvdata->skip_power_up) + if (!caps->skip_power_up) etm4x_relaxed_write32(csa, state->trcpdcr, TRCPDCR); /* @@ -2142,6 +2184,7 @@ static int etm4_add_coresight_dev(struct etm4_init_arg *init_arg) struct coresight_platform_data *pdata = NULL; struct device *dev = init_arg->dev; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev); + const struct etmv4_caps *caps = &drvdata->caps; struct coresight_desc desc = { 0 }; u8 major, minor; char *type_name; @@ -2171,7 +2214,7 @@ static int etm4_add_coresight_dev(struct etm4_init_arg *init_arg) if (!desc.name) return -ENOMEM; - etm4_set_default(&drvdata->config); + etm4_set_default(&drvdata->sysfs_config, caps); if (etm4x_always_pm_save(dev, init_arg->csa)) pm_save = true; diff --git a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c index cc6cdd3ae29d..db9e603f97ba 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c @@ -16,15 +16,15 @@ static int etm4_set_mode_exclude(struct etmv4_drvdata *drvdata, bool exclude) { u8 idx; - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - idx = config->addr_idx; + idx = sysfs_config->addr_idx; /* * TRCACATRn.TYPE bit[1:0]: type of comparison * the trace unit performs */ - if (FIELD_GET(TRCACATRn_TYPE_MASK, config->addr_acc[idx]) == TRCACATRn_TYPE_ADDR) { + if (FIELD_GET(TRCACATRn_TYPE_MASK, sysfs_config->addr_acc[idx]) == TRCACATRn_TYPE_ADDR) { if (idx % 2 != 0) return -EINVAL; @@ -33,8 +33,8 @@ static int etm4_set_mode_exclude(struct etmv4_drvdata *drvdata, bool exclude) * relevant bit of ViewInst Include/Exclude Control register * for corresponding address comparator pair. */ - if (config->addr_type[idx] != ETM_ADDR_TYPE_RANGE || - config->addr_type[idx + 1] != ETM_ADDR_TYPE_RANGE) + if (sysfs_config->addr_type[idx] != ETM_ADDR_TYPE_RANGE || + sysfs_config->addr_type[idx + 1] != ETM_ADDR_TYPE_RANGE) return -EINVAL; if (exclude == true) { @@ -42,15 +42,15 @@ static int etm4_set_mode_exclude(struct etmv4_drvdata *drvdata, bool exclude) * Set exclude bit and unset the include bit * corresponding to comparator pair */ - config->viiectlr |= BIT(idx / 2 + 16); - config->viiectlr &= ~BIT(idx / 2); + sysfs_config->viiectlr |= BIT(idx / 2 + 16); + sysfs_config->viiectlr &= ~BIT(idx / 2); } else { /* * Set include bit and unset exclude bit * corresponding to comparator pair */ - config->viiectlr |= BIT(idx / 2); - config->viiectlr &= ~BIT(idx / 2 + 16); + sysfs_config->viiectlr |= BIT(idx / 2); + sysfs_config->viiectlr &= ~BIT(idx / 2 + 16); } } return 0; @@ -62,8 +62,9 @@ static ssize_t nr_pe_cmp_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); + const struct etmv4_caps *caps = &drvdata->caps; - val = drvdata->nr_pe_cmp; + val = caps->nr_pe_cmp; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } static DEVICE_ATTR_RO(nr_pe_cmp); @@ -74,8 +75,9 @@ static ssize_t nr_addr_cmp_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); + const struct etmv4_caps *caps = &drvdata->caps; - val = drvdata->nr_addr_cmp; + val = caps->nr_addr_cmp; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } static DEVICE_ATTR_RO(nr_addr_cmp); @@ -86,8 +88,9 @@ static ssize_t nr_cntr_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); + const struct etmv4_caps *caps = &drvdata->caps; - val = drvdata->nr_cntr; + val = caps->nr_cntr; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } static DEVICE_ATTR_RO(nr_cntr); @@ -98,8 +101,9 @@ static ssize_t nr_ext_inp_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); + const struct etmv4_caps *caps = &drvdata->caps; - val = drvdata->nr_ext_inp; + val = caps->nr_ext_inp; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } static DEVICE_ATTR_RO(nr_ext_inp); @@ -110,8 +114,9 @@ static ssize_t numcidc_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); + const struct etmv4_caps *caps = &drvdata->caps; - val = drvdata->numcidc; + val = caps->numcidc; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } static DEVICE_ATTR_RO(numcidc); @@ -122,8 +127,9 @@ static ssize_t numvmidc_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); + const struct etmv4_caps *caps = &drvdata->caps; - val = drvdata->numvmidc; + val = caps->numvmidc; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } static DEVICE_ATTR_RO(numvmidc); @@ -134,8 +140,9 @@ static ssize_t nrseqstate_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); + const struct etmv4_caps *caps = &drvdata->caps; - val = drvdata->nrseqstate; + val = caps->nrseqstate; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } static DEVICE_ATTR_RO(nrseqstate); @@ -146,8 +153,9 @@ static ssize_t nr_resource_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); + const struct etmv4_caps *caps = &drvdata->caps; - val = drvdata->nr_resource; + val = caps->nr_resource; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } static DEVICE_ATTR_RO(nr_resource); @@ -158,8 +166,9 @@ static ssize_t nr_ss_cmp_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); + const struct etmv4_caps *caps = &drvdata->caps; - val = drvdata->nr_ss_cmp; + val = caps->nr_ss_cmp; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } static DEVICE_ATTR_RO(nr_ss_cmp); @@ -171,102 +180,103 @@ static ssize_t reset_store(struct device *dev, int i; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); if (val) - config->mode = 0x0; + sysfs_config->mode = 0x0; /* Disable data tracing: do not trace load and store data transfers */ - config->mode &= ~(ETM_MODE_LOAD | ETM_MODE_STORE); - config->cfg &= ~(TRCCONFIGR_INSTP0_LOAD | TRCCONFIGR_INSTP0_STORE); + sysfs_config->mode &= ~(ETM_MODE_LOAD | ETM_MODE_STORE); + sysfs_config->cfg &= ~(TRCCONFIGR_INSTP0_LOAD | TRCCONFIGR_INSTP0_STORE); /* Disable data value and data address tracing */ - config->mode &= ~(ETM_MODE_DATA_TRACE_ADDR | + sysfs_config->mode &= ~(ETM_MODE_DATA_TRACE_ADDR | ETM_MODE_DATA_TRACE_VAL); - config->cfg &= ~(TRCCONFIGR_DA | TRCCONFIGR_DV); + sysfs_config->cfg &= ~(TRCCONFIGR_DA | TRCCONFIGR_DV); /* Disable all events tracing */ - config->eventctrl0 = 0x0; - config->eventctrl1 = 0x0; + sysfs_config->eventctrl0 = 0x0; + sysfs_config->eventctrl1 = 0x0; /* Disable timestamp event */ - config->ts_ctrl = 0x0; + sysfs_config->ts_ctrl = 0x0; /* Disable stalling */ - config->stall_ctrl = 0x0; + sysfs_config->stall_ctrl = 0x0; /* Reset trace synchronization period to 2^8 = 256 bytes*/ - if (drvdata->syncpr == false) - config->syncfreq = 0x8; + if (!caps->syncpr) + sysfs_config->syncfreq = 0x8; /* * Enable ViewInst to trace everything with start-stop logic in * started state. ARM recommends start-stop logic is set before * each trace run. */ - config->vinst_ctrl = FIELD_PREP(TRCVICTLR_EVENT_MASK, 0x01); - if (drvdata->nr_addr_cmp > 0) { - config->mode |= ETM_MODE_VIEWINST_STARTSTOP; + sysfs_config->vinst_ctrl = FIELD_PREP(TRCVICTLR_EVENT_MASK, 0x01); + if (caps->nr_addr_cmp > 0) { + sysfs_config->mode |= ETM_MODE_VIEWINST_STARTSTOP; /* SSSTATUS, bit[9] */ - config->vinst_ctrl |= TRCVICTLR_SSSTATUS; + sysfs_config->vinst_ctrl |= TRCVICTLR_SSSTATUS; } /* No address range filtering for ViewInst */ - config->viiectlr = 0x0; + sysfs_config->viiectlr = 0x0; /* No start-stop filtering for ViewInst */ - config->vissctlr = 0x0; - config->vipcssctlr = 0x0; + sysfs_config->vissctlr = 0x0; + sysfs_config->vipcssctlr = 0x0; /* Disable seq events */ - for (i = 0; i < drvdata->nr_seq_ctrls; i++) - config->seq_ctrl[i] = 0x0; - config->seq_rst = 0x0; - config->seq_state = 0x0; + for (i = 0; i < caps->nr_seq_ctrls; i++) + sysfs_config->seq_ctrl[i] = 0x0; + sysfs_config->seq_rst = 0x0; + sysfs_config->seq_state = 0x0; /* Disable external input events */ - config->ext_inp = 0x0; + sysfs_config->ext_inp = 0x0; - config->cntr_idx = 0x0; - for (i = 0; i < drvdata->nr_cntr; i++) { - config->cntrldvr[i] = 0x0; - config->cntr_ctrl[i] = 0x0; - config->cntr_val[i] = 0x0; + sysfs_config->cntr_idx = 0x0; + for (i = 0; i < caps->nr_cntr; i++) { + sysfs_config->cntrldvr[i] = 0x0; + sysfs_config->cntr_ctrl[i] = 0x0; + sysfs_config->cntr_val[i] = 0x0; } - config->res_idx = 0x0; - for (i = 2; i < 2 * drvdata->nr_resource; i++) - config->res_ctrl[i] = 0x0; + sysfs_config->res_idx = 0x0; + for (i = 2; i < 2 * caps->nr_resource; i++) + sysfs_config->res_ctrl[i] = 0x0; - config->ss_idx = 0x0; - for (i = 0; i < drvdata->nr_ss_cmp; i++) { - config->ss_ctrl[i] = 0x0; - config->ss_pe_cmp[i] = 0x0; + sysfs_config->ss_idx = 0x0; + for (i = 0; i < caps->nr_ss_cmp; i++) { + sysfs_config->ss_ctrl[i] = 0x0; + sysfs_config->ss_pe_cmp[i] = 0x0; } - config->addr_idx = 0x0; - for (i = 0; i < drvdata->nr_addr_cmp * 2; i++) { - config->addr_val[i] = 0x0; - config->addr_acc[i] = 0x0; - config->addr_type[i] = ETM_ADDR_TYPE_NONE; + sysfs_config->addr_idx = 0x0; + for (i = 0; i < caps->nr_addr_cmp * 2; i++) { + sysfs_config->addr_val[i] = 0x0; + sysfs_config->addr_acc[i] = 0x0; + sysfs_config->addr_type[i] = ETM_ADDR_TYPE_NONE; } - config->ctxid_idx = 0x0; - for (i = 0; i < drvdata->numcidc; i++) - config->ctxid_pid[i] = 0x0; + sysfs_config->ctxid_idx = 0x0; + for (i = 0; i < caps->numcidc; i++) + sysfs_config->ctxid_pid[i] = 0x0; - config->ctxid_mask0 = 0x0; - config->ctxid_mask1 = 0x0; + sysfs_config->ctxid_mask0 = 0x0; + sysfs_config->ctxid_mask1 = 0x0; - config->vmid_idx = 0x0; - for (i = 0; i < drvdata->numvmidc; i++) - config->vmid_val[i] = 0x0; - config->vmid_mask0 = 0x0; - config->vmid_mask1 = 0x0; + sysfs_config->vmid_idx = 0x0; + for (i = 0; i < caps->numvmidc; i++) + sysfs_config->vmid_val[i] = 0x0; + sysfs_config->vmid_mask0 = 0x0; + sysfs_config->vmid_mask1 = 0x0; raw_spin_unlock(&drvdata->spinlock); @@ -285,9 +295,9 @@ static ssize_t mode_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->mode; + val = sysfs_config->mode; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -297,147 +307,143 @@ static ssize_t mode_store(struct device *dev, { unsigned long val, mode; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - config->mode = val & ETMv4_MODE_ALL; + sysfs_config->mode = val & ETMv4_MODE_ALL; - if (drvdata->instrp0 == true) { + if (caps->instrp0) { /* start by clearing instruction P0 field */ - config->cfg &= ~TRCCONFIGR_INSTP0_LOAD_STORE; - if (config->mode & ETM_MODE_LOAD) + sysfs_config->cfg &= ~TRCCONFIGR_INSTP0_LOAD_STORE; + if (sysfs_config->mode & ETM_MODE_LOAD) /* 0b01 Trace load instructions as P0 instructions */ - config->cfg |= TRCCONFIGR_INSTP0_LOAD; - if (config->mode & ETM_MODE_STORE) + sysfs_config->cfg |= TRCCONFIGR_INSTP0_LOAD; + if (sysfs_config->mode & ETM_MODE_STORE) /* 0b10 Trace store instructions as P0 instructions */ - config->cfg |= TRCCONFIGR_INSTP0_STORE; - if (config->mode & ETM_MODE_LOAD_STORE) + sysfs_config->cfg |= TRCCONFIGR_INSTP0_STORE; + if (sysfs_config->mode & ETM_MODE_LOAD_STORE) /* * 0b11 Trace load and store instructions * as P0 instructions */ - config->cfg |= TRCCONFIGR_INSTP0_LOAD_STORE; + sysfs_config->cfg |= TRCCONFIGR_INSTP0_LOAD_STORE; } /* bit[3], Branch broadcast mode */ - if ((config->mode & ETM_MODE_BB) && (drvdata->trcbb == true)) - config->cfg |= TRCCONFIGR_BB; + if ((sysfs_config->mode & ETM_MODE_BB) && (caps->trcbb)) + sysfs_config->cfg |= TRCCONFIGR_BB; else - config->cfg &= ~TRCCONFIGR_BB; + sysfs_config->cfg &= ~TRCCONFIGR_BB; /* bit[4], Cycle counting instruction trace bit */ - if ((config->mode & ETMv4_MODE_CYCACC) && - (drvdata->trccci == true)) - config->cfg |= TRCCONFIGR_CCI; + if ((sysfs_config->mode & ETMv4_MODE_CYCACC) && + (caps->trccci == true)) + sysfs_config->cfg |= TRCCONFIGR_CCI; else - config->cfg &= ~TRCCONFIGR_CCI; + sysfs_config->cfg &= ~TRCCONFIGR_CCI; /* bit[6], Context ID tracing bit */ - if ((config->mode & ETMv4_MODE_CTXID) && (drvdata->ctxid_size)) - config->cfg |= TRCCONFIGR_CID; + if ((sysfs_config->mode & ETMv4_MODE_CTXID) && (caps->ctxid_size)) + sysfs_config->cfg |= TRCCONFIGR_CID; else - config->cfg &= ~TRCCONFIGR_CID; + sysfs_config->cfg &= ~TRCCONFIGR_CID; - if ((config->mode & ETM_MODE_VMID) && (drvdata->vmid_size)) - config->cfg |= TRCCONFIGR_VMID; + if ((sysfs_config->mode & ETM_MODE_VMID) && (caps->vmid_size)) + sysfs_config->cfg |= TRCCONFIGR_VMID; else - config->cfg &= ~TRCCONFIGR_VMID; + sysfs_config->cfg &= ~TRCCONFIGR_VMID; /* bits[10:8], Conditional instruction tracing bit */ - mode = ETM_MODE_COND(config->mode); - if (drvdata->trccond == true) { - config->cfg &= ~TRCCONFIGR_COND_MASK; - config->cfg |= mode << __bf_shf(TRCCONFIGR_COND_MASK); + mode = ETM_MODE_COND(sysfs_config->mode); + if (caps->trccond) { + sysfs_config->cfg &= ~TRCCONFIGR_COND_MASK; + sysfs_config->cfg |= mode << __bf_shf(TRCCONFIGR_COND_MASK); } /* bit[11], Global timestamp tracing bit */ - if ((config->mode & ETMv4_MODE_TIMESTAMP) && (drvdata->ts_size)) - config->cfg |= TRCCONFIGR_TS; + if ((sysfs_config->mode & ETMv4_MODE_TIMESTAMP) && (caps->ts_size)) + sysfs_config->cfg |= TRCCONFIGR_TS; else - config->cfg &= ~TRCCONFIGR_TS; + sysfs_config->cfg &= ~TRCCONFIGR_TS; /* bit[12], Return stack enable bit */ - if ((config->mode & ETM_MODE_RETURNSTACK) && - (drvdata->retstack == true)) - config->cfg |= TRCCONFIGR_RS; + if ((sysfs_config->mode & ETM_MODE_RETURNSTACK) && (caps->retstack)) + sysfs_config->cfg |= TRCCONFIGR_RS; else - config->cfg &= ~TRCCONFIGR_RS; + sysfs_config->cfg &= ~TRCCONFIGR_RS; /* bits[14:13], Q element enable field */ - mode = ETM_MODE_QELEM(config->mode); + mode = ETM_MODE_QELEM(sysfs_config->mode); /* start by clearing QE bits */ - config->cfg &= ~(TRCCONFIGR_QE_W_COUNTS | TRCCONFIGR_QE_WO_COUNTS); + sysfs_config->cfg &= ~(TRCCONFIGR_QE_W_COUNTS | TRCCONFIGR_QE_WO_COUNTS); /* * if supported, Q elements with instruction counts are enabled. * Always set the low bit for any requested mode. Valid combos are * 0b00, 0b01 and 0b11. */ - if (mode && drvdata->q_support) - config->cfg |= TRCCONFIGR_QE_W_COUNTS; + if (mode && caps->q_support) + sysfs_config->cfg |= TRCCONFIGR_QE_W_COUNTS; /* * if supported, Q elements with and without instruction * counts are enabled */ - if ((mode & BIT(1)) && (drvdata->q_support & BIT(1))) - config->cfg |= TRCCONFIGR_QE_WO_COUNTS; + if ((mode & BIT(1)) && (caps->q_support & BIT(1))) + sysfs_config->cfg |= TRCCONFIGR_QE_WO_COUNTS; /* bit[11], AMBA Trace Bus (ATB) trigger enable bit */ - if ((config->mode & ETM_MODE_ATB_TRIGGER) && - (drvdata->atbtrig == true)) - config->eventctrl1 |= TRCEVENTCTL1R_ATB; + if ((sysfs_config->mode & ETM_MODE_ATB_TRIGGER) && (caps->atbtrig)) + sysfs_config->eventctrl1 |= TRCEVENTCTL1R_ATB; else - config->eventctrl1 &= ~TRCEVENTCTL1R_ATB; + sysfs_config->eventctrl1 &= ~TRCEVENTCTL1R_ATB; /* bit[12], Low-power state behavior override bit */ - if ((config->mode & ETM_MODE_LPOVERRIDE) && - (drvdata->lpoverride == true)) - config->eventctrl1 |= TRCEVENTCTL1R_LPOVERRIDE; + if ((sysfs_config->mode & ETM_MODE_LPOVERRIDE) && (caps->lpoverride)) + sysfs_config->eventctrl1 |= TRCEVENTCTL1R_LPOVERRIDE; else - config->eventctrl1 &= ~TRCEVENTCTL1R_LPOVERRIDE; + sysfs_config->eventctrl1 &= ~TRCEVENTCTL1R_LPOVERRIDE; /* bit[8], Instruction stall bit */ - if ((config->mode & ETM_MODE_ISTALL_EN) && (drvdata->stallctl == true)) - config->stall_ctrl |= TRCSTALLCTLR_ISTALL; + if ((sysfs_config->mode & ETM_MODE_ISTALL_EN) && (caps->stallctl)) + sysfs_config->stall_ctrl |= TRCSTALLCTLR_ISTALL; else - config->stall_ctrl &= ~TRCSTALLCTLR_ISTALL; + sysfs_config->stall_ctrl &= ~TRCSTALLCTLR_ISTALL; /* bit[10], Prioritize instruction trace bit */ - if (config->mode & ETM_MODE_INSTPRIO) - config->stall_ctrl |= TRCSTALLCTLR_INSTPRIORITY; + if (sysfs_config->mode & ETM_MODE_INSTPRIO) + sysfs_config->stall_ctrl |= TRCSTALLCTLR_INSTPRIORITY; else - config->stall_ctrl &= ~TRCSTALLCTLR_INSTPRIORITY; + sysfs_config->stall_ctrl &= ~TRCSTALLCTLR_INSTPRIORITY; /* bit[13], Trace overflow prevention bit */ - if ((config->mode & ETM_MODE_NOOVERFLOW) && - (drvdata->nooverflow == true)) - config->stall_ctrl |= TRCSTALLCTLR_NOOVERFLOW; + if ((sysfs_config->mode & ETM_MODE_NOOVERFLOW) && (caps->nooverflow)) + sysfs_config->stall_ctrl |= TRCSTALLCTLR_NOOVERFLOW; else - config->stall_ctrl &= ~TRCSTALLCTLR_NOOVERFLOW; + sysfs_config->stall_ctrl &= ~TRCSTALLCTLR_NOOVERFLOW; /* bit[9] Start/stop logic control bit */ - if (config->mode & ETM_MODE_VIEWINST_STARTSTOP) - config->vinst_ctrl |= TRCVICTLR_SSSTATUS; + if (sysfs_config->mode & ETM_MODE_VIEWINST_STARTSTOP) + sysfs_config->vinst_ctrl |= TRCVICTLR_SSSTATUS; else - config->vinst_ctrl &= ~TRCVICTLR_SSSTATUS; + sysfs_config->vinst_ctrl &= ~TRCVICTLR_SSSTATUS; /* bit[10], Whether a trace unit must trace a Reset exception */ - if (config->mode & ETM_MODE_TRACE_RESET) - config->vinst_ctrl |= TRCVICTLR_TRCRESET; + if (sysfs_config->mode & ETM_MODE_TRACE_RESET) + sysfs_config->vinst_ctrl |= TRCVICTLR_TRCRESET; else - config->vinst_ctrl &= ~TRCVICTLR_TRCRESET; + sysfs_config->vinst_ctrl &= ~TRCVICTLR_TRCRESET; /* bit[11], Whether a trace unit must trace a system error exception */ - if ((config->mode & ETM_MODE_TRACE_ERR) && - (drvdata->trc_error == true)) - config->vinst_ctrl |= TRCVICTLR_TRCERR; + if ((sysfs_config->mode & ETM_MODE_TRACE_ERR) && (caps->trc_error)) + sysfs_config->vinst_ctrl |= TRCVICTLR_TRCERR; else - config->vinst_ctrl &= ~TRCVICTLR_TRCERR; + sysfs_config->vinst_ctrl &= ~TRCVICTLR_TRCERR; - if (config->mode & (ETM_MODE_EXCL_KERN | ETM_MODE_EXCL_USER)) - etm4_config_trace_mode(config); + if (sysfs_config->mode & (ETM_MODE_EXCL_KERN | ETM_MODE_EXCL_USER)) + etm4_config_trace_mode(sysfs_config, caps); raw_spin_unlock(&drvdata->spinlock); @@ -451,9 +457,9 @@ static ssize_t pe_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->pe_sel; + val = sysfs_config->pe_sel; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -463,18 +469,19 @@ static ssize_t pe_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - if (val > drvdata->nr_pe) { + if (val > caps->nr_pe) { raw_spin_unlock(&drvdata->spinlock); return -EINVAL; } - config->pe_sel = val; + sysfs_config->pe_sel = val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -486,9 +493,9 @@ static ssize_t event_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->eventctrl0; + val = sysfs_config->eventctrl0; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -498,28 +505,29 @@ static ssize_t event_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - switch (drvdata->nr_event) { + switch (caps->nr_event) { case 0x0: /* EVENT0, bits[7:0] */ - config->eventctrl0 = val & 0xFF; + sysfs_config->eventctrl0 = val & 0xFF; break; case 0x1: /* EVENT1, bits[15:8] */ - config->eventctrl0 = val & 0xFFFF; + sysfs_config->eventctrl0 = val & 0xFFFF; break; case 0x2: /* EVENT2, bits[23:16] */ - config->eventctrl0 = val & 0xFFFFFF; + sysfs_config->eventctrl0 = val & 0xFFFFFF; break; case 0x3: /* EVENT3, bits[31:24] */ - config->eventctrl0 = val; + sysfs_config->eventctrl0 = val; break; default: break; @@ -535,9 +543,9 @@ static ssize_t event_instren_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = FIELD_GET(TRCEVENTCTL1R_INSTEN_MASK, config->eventctrl1); + val = FIELD_GET(TRCEVENTCTL1R_INSTEN_MASK, sysfs_config->eventctrl1); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -547,32 +555,34 @@ static ssize_t event_instren_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); /* start by clearing all instruction event enable bits */ - config->eventctrl1 &= ~TRCEVENTCTL1R_INSTEN_MASK; - switch (drvdata->nr_event) { + sysfs_config->eventctrl1 &= ~TRCEVENTCTL1R_INSTEN_MASK; + switch (caps->nr_event) { case 0x0: /* generate Event element for event 1 */ - config->eventctrl1 |= val & TRCEVENTCTL1R_INSTEN_1; + sysfs_config->eventctrl1 |= val & TRCEVENTCTL1R_INSTEN_1; break; case 0x1: /* generate Event element for event 1 and 2 */ - config->eventctrl1 |= val & (TRCEVENTCTL1R_INSTEN_0 | TRCEVENTCTL1R_INSTEN_1); + sysfs_config->eventctrl1 |= val & (TRCEVENTCTL1R_INSTEN_0 | + TRCEVENTCTL1R_INSTEN_1); break; case 0x2: /* generate Event element for event 1, 2 and 3 */ - config->eventctrl1 |= val & (TRCEVENTCTL1R_INSTEN_0 | + sysfs_config->eventctrl1 |= val & (TRCEVENTCTL1R_INSTEN_0 | TRCEVENTCTL1R_INSTEN_1 | TRCEVENTCTL1R_INSTEN_2); break; case 0x3: /* generate Event element for all 4 events */ - config->eventctrl1 |= val & (TRCEVENTCTL1R_INSTEN_0 | + sysfs_config->eventctrl1 |= val & (TRCEVENTCTL1R_INSTEN_0 | TRCEVENTCTL1R_INSTEN_1 | TRCEVENTCTL1R_INSTEN_2 | TRCEVENTCTL1R_INSTEN_3); @@ -591,9 +601,9 @@ static ssize_t event_ts_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->ts_ctrl; + val = sysfs_config->ts_ctrl; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -603,14 +613,15 @@ static ssize_t event_ts_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if (!drvdata->ts_size) + if (!caps->ts_size) return -EINVAL; - config->ts_ctrl = val & ETMv4_EVENT_MASK; + sysfs_config->ts_ctrl = val & ETMv4_EVENT_MASK; return size; } static DEVICE_ATTR_RW(event_ts); @@ -621,9 +632,9 @@ static ssize_t syncfreq_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->syncfreq; + val = sysfs_config->syncfreq; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -633,14 +644,15 @@ static ssize_t syncfreq_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if (drvdata->syncpr == true) + if (caps->syncpr) return -EINVAL; - config->syncfreq = val & ETMv4_SYNC_MASK; + sysfs_config->syncfreq = val & ETMv4_SYNC_MASK; return size; } static DEVICE_ATTR_RW(syncfreq); @@ -651,9 +663,9 @@ static ssize_t cyc_threshold_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->ccctlr; + val = sysfs_config->ccctlr; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -663,17 +675,18 @@ static ssize_t cyc_threshold_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; /* mask off max threshold before checking min value */ val &= ETM_CYC_THRESHOLD_MASK; - if (val < drvdata->ccitmin) + if (val < caps->ccitmin) return -EINVAL; - config->ccctlr = val; + sysfs_config->ccctlr = val; return size; } static DEVICE_ATTR_RW(cyc_threshold); @@ -684,9 +697,9 @@ static ssize_t bb_ctrl_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->bb_ctrl; + val = sysfs_config->bb_ctrl; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -696,13 +709,14 @@ static ssize_t bb_ctrl_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if (drvdata->trcbb == false) + if (!caps->trcbb) return -EINVAL; - if (!drvdata->nr_addr_cmp) + if (!caps->nr_addr_cmp) return -EINVAL; /* @@ -713,7 +727,7 @@ static ssize_t bb_ctrl_store(struct device *dev, if ((val & TRCBBCTLR_MODE) && (FIELD_GET(TRCBBCTLR_RANGE_MASK, val) == 0)) return -EINVAL; - config->bb_ctrl = val & (TRCBBCTLR_MODE | TRCBBCTLR_RANGE_MASK); + sysfs_config->bb_ctrl = val & (TRCBBCTLR_MODE | TRCBBCTLR_RANGE_MASK); return size; } static DEVICE_ATTR_RW(bb_ctrl); @@ -724,9 +738,9 @@ static ssize_t event_vinst_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = FIELD_GET(TRCVICTLR_EVENT_MASK, config->vinst_ctrl); + val = FIELD_GET(TRCVICTLR_EVENT_MASK, sysfs_config->vinst_ctrl); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -736,15 +750,15 @@ static ssize_t event_vinst_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); val &= TRCVICTLR_EVENT_MASK >> __bf_shf(TRCVICTLR_EVENT_MASK); - config->vinst_ctrl &= ~TRCVICTLR_EVENT_MASK; - config->vinst_ctrl |= FIELD_PREP(TRCVICTLR_EVENT_MASK, val); + sysfs_config->vinst_ctrl &= ~TRCVICTLR_EVENT_MASK; + sysfs_config->vinst_ctrl |= FIELD_PREP(TRCVICTLR_EVENT_MASK, val); raw_spin_unlock(&drvdata->spinlock); return size; } @@ -756,9 +770,9 @@ static ssize_t s_exlevel_vinst_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = FIELD_GET(TRCVICTLR_EXLEVEL_S_MASK, config->vinst_ctrl); + val = FIELD_GET(TRCVICTLR_EXLEVEL_S_MASK, sysfs_config->vinst_ctrl); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -768,17 +782,18 @@ static ssize_t s_exlevel_vinst_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); /* clear all EXLEVEL_S bits */ - config->vinst_ctrl &= ~TRCVICTLR_EXLEVEL_S_MASK; + sysfs_config->vinst_ctrl &= ~TRCVICTLR_EXLEVEL_S_MASK; /* enable instruction tracing for corresponding exception level */ - val &= drvdata->s_ex_level; - config->vinst_ctrl |= val << __bf_shf(TRCVICTLR_EXLEVEL_S_MASK); + val &= caps->s_ex_level; + sysfs_config->vinst_ctrl |= val << __bf_shf(TRCVICTLR_EXLEVEL_S_MASK); raw_spin_unlock(&drvdata->spinlock); return size; } @@ -790,10 +805,10 @@ static ssize_t ns_exlevel_vinst_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; /* EXLEVEL_NS, bits[23:20] */ - val = FIELD_GET(TRCVICTLR_EXLEVEL_NS_MASK, config->vinst_ctrl); + val = FIELD_GET(TRCVICTLR_EXLEVEL_NS_MASK, sysfs_config->vinst_ctrl); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -803,17 +818,18 @@ static ssize_t ns_exlevel_vinst_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); /* clear EXLEVEL_NS bits */ - config->vinst_ctrl &= ~TRCVICTLR_EXLEVEL_NS_MASK; + sysfs_config->vinst_ctrl &= ~TRCVICTLR_EXLEVEL_NS_MASK; /* enable instruction tracing for corresponding exception level */ - val &= drvdata->ns_ex_level; - config->vinst_ctrl |= val << __bf_shf(TRCVICTLR_EXLEVEL_NS_MASK); + val &= caps->ns_ex_level; + sysfs_config->vinst_ctrl |= val << __bf_shf(TRCVICTLR_EXLEVEL_NS_MASK); raw_spin_unlock(&drvdata->spinlock); return size; } @@ -825,9 +841,9 @@ static ssize_t addr_idx_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->addr_idx; + val = sysfs_config->addr_idx; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -837,11 +853,12 @@ static ssize_t addr_idx_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if (val >= drvdata->nr_addr_cmp * 2) + if (val >= caps->nr_addr_cmp * 2) return -EINVAL; /* @@ -849,7 +866,7 @@ static ssize_t addr_idx_store(struct device *dev, * dereferenced multiple times within a spinlock block elsewhere. */ raw_spin_lock(&drvdata->spinlock); - config->addr_idx = val; + sysfs_config->addr_idx = val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -862,11 +879,11 @@ static ssize_t addr_instdatatype_show(struct device *dev, ssize_t len; u8 val, idx; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; - val = FIELD_GET(TRCACATRn_TYPE_MASK, config->addr_acc[idx]); + idx = sysfs_config->addr_idx; + val = FIELD_GET(TRCACATRn_TYPE_MASK, sysfs_config->addr_acc[idx]); len = scnprintf(buf, PAGE_SIZE, "%s\n", val == TRCACATRn_TYPE_ADDR ? "instr" : (val == TRCACATRn_TYPE_DATA_LOAD_ADDR ? "data_load" : @@ -883,7 +900,7 @@ static ssize_t addr_instdatatype_store(struct device *dev, u8 idx; char str[20] = ""; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (strlen(buf) >= 20) return -EINVAL; @@ -891,10 +908,10 @@ static ssize_t addr_instdatatype_store(struct device *dev, return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; + idx = sysfs_config->addr_idx; if (!strcmp(str, "instr")) /* TYPE, bits[1:0] */ - config->addr_acc[idx] &= ~TRCACATRn_TYPE_MASK; + sysfs_config->addr_acc[idx] &= ~TRCACATRn_TYPE_MASK; raw_spin_unlock(&drvdata->spinlock); return size; @@ -908,16 +925,16 @@ static ssize_t addr_single_show(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - idx = config->addr_idx; + idx = sysfs_config->addr_idx; raw_spin_lock(&drvdata->spinlock); - if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || - config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { + if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE || + sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { raw_spin_unlock(&drvdata->spinlock); return -EPERM; } - val = (unsigned long)config->addr_val[idx]; + val = (unsigned long)sysfs_config->addr_val[idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -929,21 +946,21 @@ static ssize_t addr_single_store(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; - if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || - config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { + idx = sysfs_config->addr_idx; + if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE || + sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { raw_spin_unlock(&drvdata->spinlock); return -EPERM; } - config->addr_val[idx] = (u64)val; - config->addr_type[idx] = ETM_ADDR_TYPE_SINGLE; + sysfs_config->addr_val[idx] = (u64)val; + sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_SINGLE; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -956,24 +973,24 @@ static ssize_t addr_range_show(struct device *dev, u8 idx; unsigned long val1, val2; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; + idx = sysfs_config->addr_idx; if (idx % 2 != 0) { raw_spin_unlock(&drvdata->spinlock); return -EPERM; } - if (!((config->addr_type[idx] == ETM_ADDR_TYPE_NONE && - config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || - (config->addr_type[idx] == ETM_ADDR_TYPE_RANGE && - config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { + if (!((sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE && + sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || + (sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_RANGE && + sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { raw_spin_unlock(&drvdata->spinlock); return -EPERM; } - val1 = (unsigned long)config->addr_val[idx]; - val2 = (unsigned long)config->addr_val[idx + 1]; + val1 = (unsigned long)sysfs_config->addr_val[idx]; + val2 = (unsigned long)sysfs_config->addr_val[idx + 1]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx %#lx\n", val1, val2); } @@ -985,7 +1002,7 @@ static ssize_t addr_range_store(struct device *dev, u8 idx; unsigned long val1, val2; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; int elements, exclude; elements = sscanf(buf, "%lx %lx %x", &val1, &val2, &exclude); @@ -998,31 +1015,31 @@ static ssize_t addr_range_store(struct device *dev, return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; + idx = sysfs_config->addr_idx; if (idx % 2 != 0) { raw_spin_unlock(&drvdata->spinlock); return -EPERM; } - if (!((config->addr_type[idx] == ETM_ADDR_TYPE_NONE && - config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || - (config->addr_type[idx] == ETM_ADDR_TYPE_RANGE && - config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { + if (!((sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE && + sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || + (sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_RANGE && + sysfs_config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { raw_spin_unlock(&drvdata->spinlock); return -EPERM; } - config->addr_val[idx] = (u64)val1; - config->addr_type[idx] = ETM_ADDR_TYPE_RANGE; - config->addr_val[idx + 1] = (u64)val2; - config->addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE; + sysfs_config->addr_val[idx] = (u64)val1; + sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_RANGE; + sysfs_config->addr_val[idx + 1] = (u64)val2; + sysfs_config->addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE; /* * Program include or exclude control bits for vinst or vdata * whenever we change addr comparators to ETM_ADDR_TYPE_RANGE * use supplied value, or default to bit set in 'mode' */ if (elements != 3) - exclude = config->mode & ETM_MODE_EXCLUDE; + exclude = sysfs_config->mode & ETM_MODE_EXCLUDE; etm4_set_mode_exclude(drvdata, exclude ? true : false); raw_spin_unlock(&drvdata->spinlock); @@ -1037,18 +1054,18 @@ static ssize_t addr_start_show(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; + idx = sysfs_config->addr_idx; - if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || - config->addr_type[idx] == ETM_ADDR_TYPE_START)) { + if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE || + sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_START)) { raw_spin_unlock(&drvdata->spinlock); return -EPERM; } - val = (unsigned long)config->addr_val[idx]; + val = (unsigned long)sysfs_config->addr_val[idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1060,26 +1077,27 @@ static ssize_t addr_start_store(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; - if (!drvdata->nr_addr_cmp) { + idx = sysfs_config->addr_idx; + if (!caps->nr_addr_cmp) { raw_spin_unlock(&drvdata->spinlock); return -EINVAL; } - if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || - config->addr_type[idx] == ETM_ADDR_TYPE_START)) { + if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE || + sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_START)) { raw_spin_unlock(&drvdata->spinlock); return -EPERM; } - config->addr_val[idx] = (u64)val; - config->addr_type[idx] = ETM_ADDR_TYPE_START; - config->vissctlr |= BIT(idx); + sysfs_config->addr_val[idx] = (u64)val; + sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_START; + sysfs_config->vissctlr |= BIT(idx); raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1092,18 +1110,18 @@ static ssize_t addr_stop_show(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; + idx = sysfs_config->addr_idx; - if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || - config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { + if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE || + sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { raw_spin_unlock(&drvdata->spinlock); return -EPERM; } - val = (unsigned long)config->addr_val[idx]; + val = (unsigned long)sysfs_config->addr_val[idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1115,26 +1133,27 @@ static ssize_t addr_stop_store(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; - if (!drvdata->nr_addr_cmp) { + idx = sysfs_config->addr_idx; + if (!caps->nr_addr_cmp) { raw_spin_unlock(&drvdata->spinlock); return -EINVAL; } - if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || - config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { + if (!(sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_NONE || + sysfs_config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { raw_spin_unlock(&drvdata->spinlock); return -EPERM; } - config->addr_val[idx] = (u64)val; - config->addr_type[idx] = ETM_ADDR_TYPE_STOP; - config->vissctlr |= BIT(idx + 16); + sysfs_config->addr_val[idx] = (u64)val; + sysfs_config->addr_type[idx] = ETM_ADDR_TYPE_STOP; + sysfs_config->vissctlr |= BIT(idx + 16); raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1147,12 +1166,12 @@ static ssize_t addr_ctxtype_show(struct device *dev, ssize_t len; u8 idx, val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; + idx = sysfs_config->addr_idx; /* CONTEXTTYPE, bits[3:2] */ - val = FIELD_GET(TRCACATRn_CONTEXTTYPE_MASK, config->addr_acc[idx]); + val = FIELD_GET(TRCACATRn_CONTEXTTYPE_MASK, sysfs_config->addr_acc[idx]); len = scnprintf(buf, PAGE_SIZE, "%s\n", val == ETM_CTX_NONE ? "none" : (val == ETM_CTX_CTXID ? "ctxid" : (val == ETM_CTX_VMID ? "vmid" : "all"))); @@ -1167,7 +1186,8 @@ static ssize_t addr_ctxtype_store(struct device *dev, u8 idx; char str[10] = ""; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (strlen(buf) >= 10) return -EINVAL; @@ -1175,31 +1195,31 @@ static ssize_t addr_ctxtype_store(struct device *dev, return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; + idx = sysfs_config->addr_idx; if (!strcmp(str, "none")) /* start by clearing context type bits */ - config->addr_acc[idx] &= ~TRCACATRn_CONTEXTTYPE_MASK; + sysfs_config->addr_acc[idx] &= ~TRCACATRn_CONTEXTTYPE_MASK; else if (!strcmp(str, "ctxid")) { /* 0b01 The trace unit performs a Context ID */ - if (drvdata->numcidc) { - config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_CTXID; - config->addr_acc[idx] &= ~TRCACATRn_CONTEXTTYPE_VMID; + if (caps->numcidc) { + sysfs_config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_CTXID; + sysfs_config->addr_acc[idx] &= ~TRCACATRn_CONTEXTTYPE_VMID; } } else if (!strcmp(str, "vmid")) { /* 0b10 The trace unit performs a VMID */ - if (drvdata->numvmidc) { - config->addr_acc[idx] &= ~TRCACATRn_CONTEXTTYPE_CTXID; - config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_VMID; + if (caps->numvmidc) { + sysfs_config->addr_acc[idx] &= ~TRCACATRn_CONTEXTTYPE_CTXID; + sysfs_config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_VMID; } } else if (!strcmp(str, "all")) { /* * 0b11 The trace unit performs a Context ID * comparison and a VMID */ - if (drvdata->numcidc) - config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_CTXID; - if (drvdata->numvmidc) - config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_VMID; + if (caps->numcidc) + sysfs_config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_CTXID; + if (caps->numvmidc) + sysfs_config->addr_acc[idx] |= TRCACATRn_CONTEXTTYPE_VMID; } raw_spin_unlock(&drvdata->spinlock); return size; @@ -1213,12 +1233,12 @@ static ssize_t addr_context_show(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; + idx = sysfs_config->addr_idx; /* context ID comparator bits[6:4] */ - val = FIELD_GET(TRCACATRn_CONTEXT_MASK, config->addr_acc[idx]); + val = FIELD_GET(TRCACATRn_CONTEXT_MASK, sysfs_config->addr_acc[idx]); raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1230,21 +1250,22 @@ static ssize_t addr_context_store(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if ((drvdata->numcidc <= 1) && (drvdata->numvmidc <= 1)) + if ((caps->numcidc <= 1) && (caps->numvmidc <= 1)) return -EINVAL; - if (val >= (drvdata->numcidc >= drvdata->numvmidc ? - drvdata->numcidc : drvdata->numvmidc)) + if (val >= (caps->numcidc >= caps->numvmidc ? + caps->numcidc : caps->numvmidc)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; + idx = sysfs_config->addr_idx; /* clear context ID comparator bits[6:4] */ - config->addr_acc[idx] &= ~TRCACATRn_CONTEXT_MASK; - config->addr_acc[idx] |= val << __bf_shf(TRCACATRn_CONTEXT_MASK); + sysfs_config->addr_acc[idx] &= ~TRCACATRn_CONTEXT_MASK; + sysfs_config->addr_acc[idx] |= val << __bf_shf(TRCACATRn_CONTEXT_MASK); raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1257,11 +1278,11 @@ static ssize_t addr_exlevel_s_ns_show(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; - val = FIELD_GET(TRCACATRn_EXLEVEL_MASK, config->addr_acc[idx]); + idx = sysfs_config->addr_idx; + val = FIELD_GET(TRCACATRn_EXLEVEL_MASK, sysfs_config->addr_acc[idx]); raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1273,7 +1294,7 @@ static ssize_t addr_exlevel_s_ns_store(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 0, &val)) return -EINVAL; @@ -1282,10 +1303,10 @@ static ssize_t addr_exlevel_s_ns_store(struct device *dev, return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; + idx = sysfs_config->addr_idx; /* clear Exlevel_ns & Exlevel_s bits[14:12, 11:8], bit[15] is res0 */ - config->addr_acc[idx] &= ~TRCACATRn_EXLEVEL_MASK; - config->addr_acc[idx] |= val << __bf_shf(TRCACATRn_EXLEVEL_MASK); + sysfs_config->addr_acc[idx] &= ~TRCACATRn_EXLEVEL_MASK; + sysfs_config->addr_acc[idx] |= val << __bf_shf(TRCACATRn_EXLEVEL_MASK); raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1305,24 +1326,24 @@ static ssize_t addr_cmp_view_show(struct device *dev, u8 idx, addr_type; unsigned long addr_v, addr_v2, addr_ctrl; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; int size = 0; bool exclude = false; raw_spin_lock(&drvdata->spinlock); - idx = config->addr_idx; - addr_v = config->addr_val[idx]; - addr_ctrl = config->addr_acc[idx]; - addr_type = config->addr_type[idx]; + idx = sysfs_config->addr_idx; + addr_v = sysfs_config->addr_val[idx]; + addr_ctrl = sysfs_config->addr_acc[idx]; + addr_type = sysfs_config->addr_type[idx]; if (addr_type == ETM_ADDR_TYPE_RANGE) { if (idx & 0x1) { idx -= 1; addr_v2 = addr_v; - addr_v = config->addr_val[idx]; + addr_v = sysfs_config->addr_val[idx]; } else { - addr_v2 = config->addr_val[idx + 1]; + addr_v2 = sysfs_config->addr_val[idx + 1]; } - exclude = config->viiectlr & BIT(idx / 2 + 16); + exclude = sysfs_config->viiectlr & BIT(idx / 2 + 16); } raw_spin_unlock(&drvdata->spinlock); if (addr_type) { @@ -1348,11 +1369,12 @@ static ssize_t vinst_pe_cmp_start_stop_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - if (!drvdata->nr_pe_cmp) + if (!caps->nr_pe_cmp) return -EINVAL; - val = config->vipcssctlr; + val = sysfs_config->vipcssctlr; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } static ssize_t vinst_pe_cmp_start_stop_store(struct device *dev, @@ -1361,15 +1383,16 @@ static ssize_t vinst_pe_cmp_start_stop_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if (!drvdata->nr_pe_cmp) + if (!caps->nr_pe_cmp) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - config->vipcssctlr = val; + sysfs_config->vipcssctlr = val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1381,9 +1404,9 @@ static ssize_t seq_idx_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->seq_idx; + val = sysfs_config->seq_idx; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1393,13 +1416,14 @@ static ssize_t seq_idx_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - if (!drvdata->nr_seq_ctrls) + if (!caps->nr_seq_ctrls) return -ENOTSUPP; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if (val >= drvdata->nr_seq_ctrls) + if (val >= caps->nr_seq_ctrls) return -EINVAL; /* @@ -1407,7 +1431,7 @@ static ssize_t seq_idx_store(struct device *dev, * dereferenced multiple times within a spinlock block elsewhere. */ raw_spin_lock(&drvdata->spinlock); - config->seq_idx = val; + sysfs_config->seq_idx = val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1419,9 +1443,9 @@ static ssize_t seq_state_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->seq_state; + val = sysfs_config->seq_state; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1431,14 +1455,18 @@ static ssize_t seq_state_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if (val >= drvdata->nrseqstate) + if (IS_ERR_OR_NULL(drvdata->csdev) || + coresight_get_mode(drvdata->csdev) == CS_MODE_SYSFS) + return -EBUSY; + if (val >= caps->nrseqstate) return -EINVAL; - config->seq_state = val; + sysfs_config->seq_state = val; return size; } static DEVICE_ATTR_RW(seq_state); @@ -1450,11 +1478,11 @@ static ssize_t seq_event_show(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - idx = config->seq_idx; - val = config->seq_ctrl[idx]; + idx = sysfs_config->seq_idx; + val = sysfs_config->seq_ctrl[idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1466,15 +1494,15 @@ static ssize_t seq_event_store(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->seq_idx; + idx = sysfs_config->seq_idx; /* Seq control has two masks B[15:8] F[7:0] */ - config->seq_ctrl[idx] = val & 0xFFFF; + sysfs_config->seq_ctrl[idx] = val & 0xFFFF; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1486,9 +1514,9 @@ static ssize_t seq_reset_event_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->seq_rst; + val = sysfs_config->seq_rst; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1498,14 +1526,15 @@ static ssize_t seq_reset_event_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if (!(drvdata->nrseqstate)) + if (!(caps->nrseqstate)) return -EINVAL; - config->seq_rst = val & ETMv4_EVENT_MASK; + sysfs_config->seq_rst = val & ETMv4_EVENT_MASK; return size; } static DEVICE_ATTR_RW(seq_reset_event); @@ -1516,9 +1545,9 @@ static ssize_t cntr_idx_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->cntr_idx; + val = sysfs_config->cntr_idx; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1528,11 +1557,12 @@ static ssize_t cntr_idx_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if (val >= drvdata->nr_cntr) + if (val >= caps->nr_cntr) return -EINVAL; /* @@ -1540,7 +1570,7 @@ static ssize_t cntr_idx_store(struct device *dev, * dereferenced multiple times within a spinlock block elsewhere. */ raw_spin_lock(&drvdata->spinlock); - config->cntr_idx = val; + sysfs_config->cntr_idx = val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1553,11 +1583,11 @@ static ssize_t cntrldvr_show(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - idx = config->cntr_idx; - val = config->cntrldvr[idx]; + idx = sysfs_config->cntr_idx; + val = sysfs_config->cntrldvr[idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1569,7 +1599,7 @@ static ssize_t cntrldvr_store(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; @@ -1577,8 +1607,8 @@ static ssize_t cntrldvr_store(struct device *dev, return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->cntr_idx; - config->cntrldvr[idx] = val; + idx = sysfs_config->cntr_idx; + sysfs_config->cntrldvr[idx] = val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1591,11 +1621,11 @@ static ssize_t cntr_val_show(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - idx = config->cntr_idx; - val = config->cntr_val[idx]; + idx = sysfs_config->cntr_idx; + val = sysfs_config->cntr_val[idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1607,16 +1637,19 @@ static ssize_t cntr_val_store(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; if (val > ETM_CNTR_MAX_VAL) return -EINVAL; + if (IS_ERR_OR_NULL(drvdata->csdev) || + coresight_get_mode(drvdata->csdev) == CS_MODE_SYSFS) + return -EBUSY; raw_spin_lock(&drvdata->spinlock); - idx = config->cntr_idx; - config->cntr_val[idx] = val; + idx = sysfs_config->cntr_idx; + sysfs_config->cntr_val[idx] = val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1629,11 +1662,11 @@ static ssize_t cntr_ctrl_show(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - idx = config->cntr_idx; - val = config->cntr_ctrl[idx]; + idx = sysfs_config->cntr_idx; + val = sysfs_config->cntr_ctrl[idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1645,14 +1678,14 @@ static ssize_t cntr_ctrl_store(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->cntr_idx; - config->cntr_ctrl[idx] = val; + idx = sysfs_config->cntr_idx; + sysfs_config->cntr_ctrl[idx] = val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1664,9 +1697,9 @@ static ssize_t res_idx_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->res_idx; + val = sysfs_config->res_idx; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1676,7 +1709,8 @@ static ssize_t res_idx_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; @@ -1684,7 +1718,7 @@ static ssize_t res_idx_store(struct device *dev, * Resource selector pair 0 is always implemented and reserved, * namely an idx with 0 and 1 is illegal. */ - if ((val < 2) || (val >= 2 * drvdata->nr_resource)) + if ((val < 2) || (val >= 2 * caps->nr_resource)) return -EINVAL; /* @@ -1692,7 +1726,7 @@ static ssize_t res_idx_store(struct device *dev, * dereferenced multiple times within a spinlock block elsewhere. */ raw_spin_lock(&drvdata->spinlock); - config->res_idx = val; + sysfs_config->res_idx = val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1705,11 +1739,11 @@ static ssize_t res_ctrl_show(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - idx = config->res_idx; - val = config->res_ctrl[idx]; + idx = sysfs_config->res_idx; + val = sysfs_config->res_ctrl[idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1721,18 +1755,18 @@ static ssize_t res_ctrl_store(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->res_idx; + idx = sysfs_config->res_idx; /* For odd idx pair inversal bit is RES0 */ if (idx % 2 != 0) /* PAIRINV, bit[21] */ val &= ~TRCRSCTLRn_PAIRINV; - config->res_ctrl[idx] = val & (TRCRSCTLRn_PAIRINV | + sysfs_config->res_ctrl[idx] = val & (TRCRSCTLRn_PAIRINV | TRCRSCTLRn_INV | TRCRSCTLRn_GROUP_MASK | TRCRSCTLRn_SELECT_MASK); @@ -1746,9 +1780,9 @@ static ssize_t sshot_idx_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->ss_idx; + val = sysfs_config->ss_idx; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1758,15 +1792,16 @@ static ssize_t sshot_idx_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if (val >= drvdata->nr_ss_cmp) + if (val >= caps->nr_ss_cmp) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - config->ss_idx = val; + sysfs_config->ss_idx = val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1778,10 +1813,10 @@ static ssize_t sshot_ctrl_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - val = config->ss_ctrl[config->ss_idx]; + val = sysfs_config->ss_ctrl[sysfs_config->ss_idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1793,16 +1828,16 @@ static ssize_t sshot_ctrl_store(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->ss_idx; - config->ss_ctrl[idx] = FIELD_PREP(TRCSSCCRn_SAC_ARC_RST_MASK, val); - /* must clear bit 31 in related status register on programming */ - config->ss_status[idx] &= ~TRCSSCSRn_STATUS; + idx = sysfs_config->ss_idx; + sysfs_config->ss_ctrl[idx] = FIELD_PREP(TRCSSCCRn_SAC_ARC_RST_MASK, val); + /* must clear bit 31 and 30 in related status register on programming */ + drvdata->ss_status[idx] &= ~(TRCSSCSRn_STATUS | TRCSSCSRn_PENDING); raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1813,10 +1848,10 @@ static ssize_t sshot_status_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - val = config->ss_status[config->ss_idx]; + val = drvdata->ss_status[sysfs_config->ss_idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1828,10 +1863,10 @@ static ssize_t sshot_pe_ctrl_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; raw_spin_lock(&drvdata->spinlock); - val = config->ss_pe_cmp[config->ss_idx]; + val = sysfs_config->ss_pe_cmp[sysfs_config->ss_idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1843,16 +1878,16 @@ static ssize_t sshot_pe_ctrl_store(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->ss_idx; - config->ss_pe_cmp[idx] = FIELD_PREP(TRCSSPCICRn_PC_MASK, val); - /* must clear bit 31 in related status register on programming */ - config->ss_status[idx] &= ~TRCSSCSRn_STATUS; + idx = sysfs_config->ss_idx; + sysfs_config->ss_pe_cmp[idx] = FIELD_PREP(TRCSSPCICRn_PC_MASK, val); + /* must clear bit 31 and 30 in related status register on programming */ + drvdata->ss_status[idx] &= ~(TRCSSCSRn_STATUS | TRCSSCSRn_PENDING); raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1864,9 +1899,9 @@ static ssize_t ctxid_idx_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->ctxid_idx; + val = sysfs_config->ctxid_idx; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1876,11 +1911,12 @@ static ssize_t ctxid_idx_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if (val >= drvdata->numcidc) + if (val >= caps->numcidc) return -EINVAL; /* @@ -1888,7 +1924,7 @@ static ssize_t ctxid_idx_store(struct device *dev, * dereferenced multiple times within a spinlock block elsewhere. */ raw_spin_lock(&drvdata->spinlock); - config->ctxid_idx = val; + sysfs_config->ctxid_idx = val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1901,7 +1937,7 @@ static ssize_t ctxid_pid_show(struct device *dev, u8 idx; unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; /* * Don't use contextID tracing if coming from a PID namespace. See @@ -1911,8 +1947,8 @@ static ssize_t ctxid_pid_show(struct device *dev, return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->ctxid_idx; - val = (unsigned long)config->ctxid_pid[idx]; + idx = sysfs_config->ctxid_idx; + val = (unsigned long)sysfs_config->ctxid_pid[idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1924,7 +1960,8 @@ static ssize_t ctxid_pid_store(struct device *dev, u8 idx; unsigned long pid; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; /* * When contextID tracing is enabled the tracers will insert the @@ -1943,14 +1980,14 @@ static ssize_t ctxid_pid_store(struct device *dev, * ctxid comparator is implemented and ctxid is greater than 0 bits * in length */ - if (!drvdata->ctxid_size || !drvdata->numcidc) + if (!caps->ctxid_size || !caps->numcidc) return -EINVAL; if (kstrtoul(buf, 16, &pid)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - idx = config->ctxid_idx; - config->ctxid_pid[idx] = (u64)pid; + idx = sysfs_config->ctxid_idx; + sysfs_config->ctxid_pid[idx] = (u64)pid; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -1962,7 +1999,7 @@ static ssize_t ctxid_masks_show(struct device *dev, { unsigned long val1, val2; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; /* * Don't use contextID tracing if coming from a PID namespace. See @@ -1972,8 +2009,8 @@ static ssize_t ctxid_masks_show(struct device *dev, return -EINVAL; raw_spin_lock(&drvdata->spinlock); - val1 = config->ctxid_mask0; - val2 = config->ctxid_mask1; + val1 = sysfs_config->ctxid_mask0; + val2 = sysfs_config->ctxid_mask1; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx %#lx\n", val1, val2); } @@ -1985,7 +2022,8 @@ static ssize_t ctxid_masks_store(struct device *dev, u8 i, j, maskbyte; unsigned long val1, val2, mask; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; int nr_inputs; /* @@ -2000,11 +2038,11 @@ static ssize_t ctxid_masks_store(struct device *dev, * ctxid comparator is implemented and ctxid is greater than 0 bits * in length */ - if (!drvdata->ctxid_size || !drvdata->numcidc) + if (!caps->ctxid_size || !caps->numcidc) return -EINVAL; /* one mask if <= 4 comparators, two for up to 8 */ nr_inputs = sscanf(buf, "%lx %lx", &val1, &val2); - if ((drvdata->numcidc > 4) && (nr_inputs != 2)) + if ((caps->numcidc > 4) && (nr_inputs != 2)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); @@ -2012,42 +2050,42 @@ static ssize_t ctxid_masks_store(struct device *dev, * each byte[0..3] controls mask value applied to ctxid * comparator[0..3] */ - switch (drvdata->numcidc) { + switch (caps->numcidc) { case 0x1: /* COMP0, bits[7:0] */ - config->ctxid_mask0 = val1 & 0xFF; + sysfs_config->ctxid_mask0 = val1 & 0xFF; break; case 0x2: /* COMP1, bits[15:8] */ - config->ctxid_mask0 = val1 & 0xFFFF; + sysfs_config->ctxid_mask0 = val1 & 0xFFFF; break; case 0x3: /* COMP2, bits[23:16] */ - config->ctxid_mask0 = val1 & 0xFFFFFF; + sysfs_config->ctxid_mask0 = val1 & 0xFFFFFF; break; case 0x4: /* COMP3, bits[31:24] */ - config->ctxid_mask0 = val1; + sysfs_config->ctxid_mask0 = val1; break; case 0x5: /* COMP4, bits[7:0] */ - config->ctxid_mask0 = val1; - config->ctxid_mask1 = val2 & 0xFF; + sysfs_config->ctxid_mask0 = val1; + sysfs_config->ctxid_mask1 = val2 & 0xFF; break; case 0x6: /* COMP5, bits[15:8] */ - config->ctxid_mask0 = val1; - config->ctxid_mask1 = val2 & 0xFFFF; + sysfs_config->ctxid_mask0 = val1; + sysfs_config->ctxid_mask1 = val2 & 0xFFFF; break; case 0x7: /* COMP6, bits[23:16] */ - config->ctxid_mask0 = val1; - config->ctxid_mask1 = val2 & 0xFFFFFF; + sysfs_config->ctxid_mask0 = val1; + sysfs_config->ctxid_mask1 = val2 & 0xFFFFFF; break; case 0x8: /* COMP7, bits[31:24] */ - config->ctxid_mask0 = val1; - config->ctxid_mask1 = val2; + sysfs_config->ctxid_mask0 = val1; + sysfs_config->ctxid_mask1 = val2; break; default: break; @@ -2058,8 +2096,8 @@ static ssize_t ctxid_masks_store(struct device *dev, * For example, if bit[3] of ctxid_mask0 is 1, we must clear bits[31:24] * of ctxid comparator0 value (corresponding to byte 0) register. */ - mask = config->ctxid_mask0; - for (i = 0; i < drvdata->numcidc; i++) { + mask = sysfs_config->ctxid_mask0; + for (i = 0; i < caps->numcidc; i++) { /* mask value of corresponding ctxid comparator */ maskbyte = mask & ETMv4_EVENT_MASK; /* @@ -2068,13 +2106,13 @@ static ssize_t ctxid_masks_store(struct device *dev, */ for (j = 0; j < 8; j++) { if (maskbyte & 1) - config->ctxid_pid[i] &= ~(0xFFUL << (j * 8)); + sysfs_config->ctxid_pid[i] &= ~(0xFFUL << (j * 8)); maskbyte >>= 1; } /* Select the next ctxid comparator mask value */ if (i == 3) /* ctxid comparators[4-7] */ - mask = config->ctxid_mask1; + mask = sysfs_config->ctxid_mask1; else mask >>= 0x8; } @@ -2090,9 +2128,9 @@ static ssize_t vmid_idx_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; - val = config->vmid_idx; + val = sysfs_config->vmid_idx; return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -2102,11 +2140,12 @@ static ssize_t vmid_idx_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; if (kstrtoul(buf, 16, &val)) return -EINVAL; - if (val >= drvdata->numvmidc) + if (val >= caps->numvmidc) return -EINVAL; /* @@ -2114,7 +2153,7 @@ static ssize_t vmid_idx_store(struct device *dev, * dereferenced multiple times within a spinlock block elsewhere. */ raw_spin_lock(&drvdata->spinlock); - config->vmid_idx = val; + sysfs_config->vmid_idx = val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -2126,7 +2165,7 @@ static ssize_t vmid_val_show(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; /* * Don't use virtual contextID tracing if coming from a PID namespace. @@ -2136,7 +2175,7 @@ static ssize_t vmid_val_show(struct device *dev, return -EINVAL; raw_spin_lock(&drvdata->spinlock); - val = (unsigned long)config->vmid_val[config->vmid_idx]; + val = (unsigned long)sysfs_config->vmid_val[sysfs_config->vmid_idx]; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -2147,7 +2186,8 @@ static ssize_t vmid_val_store(struct device *dev, { unsigned long val; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; /* * Don't use virtual contextID tracing if coming from a PID namespace. @@ -2160,13 +2200,13 @@ static ssize_t vmid_val_store(struct device *dev, * only implemented when vmid tracing is enabled, i.e. at least one * vmid comparator is implemented and at least 8 bit vmid size */ - if (!drvdata->vmid_size || !drvdata->numvmidc) + if (!caps->vmid_size || !caps->numvmidc) return -EINVAL; if (kstrtoul(buf, 16, &val)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); - config->vmid_val[config->vmid_idx] = (u64)val; + sysfs_config->vmid_val[sysfs_config->vmid_idx] = (u64)val; raw_spin_unlock(&drvdata->spinlock); return size; } @@ -2177,7 +2217,7 @@ static ssize_t vmid_masks_show(struct device *dev, { unsigned long val1, val2; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; /* * Don't use virtual contextID tracing if coming from a PID namespace. @@ -2187,8 +2227,8 @@ static ssize_t vmid_masks_show(struct device *dev, return -EINVAL; raw_spin_lock(&drvdata->spinlock); - val1 = config->vmid_mask0; - val2 = config->vmid_mask1; + val1 = sysfs_config->vmid_mask0; + val2 = sysfs_config->vmid_mask1; raw_spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx %#lx\n", val1, val2); } @@ -2200,7 +2240,8 @@ static ssize_t vmid_masks_store(struct device *dev, u8 i, j, maskbyte; unsigned long val1, val2, mask; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); - struct etmv4_config *config = &drvdata->config; + const struct etmv4_caps *caps = &drvdata->caps; + struct etmv4_config *sysfs_config = &drvdata->sysfs_config; int nr_inputs; /* @@ -2214,11 +2255,11 @@ static ssize_t vmid_masks_store(struct device *dev, * only implemented when vmid tracing is enabled, i.e. at least one * vmid comparator is implemented and at least 8 bit vmid size */ - if (!drvdata->vmid_size || !drvdata->numvmidc) + if (!caps->vmid_size || !caps->numvmidc) return -EINVAL; /* one mask if <= 4 comparators, two for up to 8 */ nr_inputs = sscanf(buf, "%lx %lx", &val1, &val2); - if ((drvdata->numvmidc > 4) && (nr_inputs != 2)) + if ((caps->numvmidc > 4) && (nr_inputs != 2)) return -EINVAL; raw_spin_lock(&drvdata->spinlock); @@ -2227,42 +2268,42 @@ static ssize_t vmid_masks_store(struct device *dev, * each byte[0..3] controls mask value applied to vmid * comparator[0..3] */ - switch (drvdata->numvmidc) { + switch (caps->numvmidc) { case 0x1: /* COMP0, bits[7:0] */ - config->vmid_mask0 = val1 & 0xFF; + sysfs_config->vmid_mask0 = val1 & 0xFF; break; case 0x2: /* COMP1, bits[15:8] */ - config->vmid_mask0 = val1 & 0xFFFF; + sysfs_config->vmid_mask0 = val1 & 0xFFFF; break; case 0x3: /* COMP2, bits[23:16] */ - config->vmid_mask0 = val1 & 0xFFFFFF; + sysfs_config->vmid_mask0 = val1 & 0xFFFFFF; break; case 0x4: /* COMP3, bits[31:24] */ - config->vmid_mask0 = val1; + sysfs_config->vmid_mask0 = val1; break; case 0x5: /* COMP4, bits[7:0] */ - config->vmid_mask0 = val1; - config->vmid_mask1 = val2 & 0xFF; + sysfs_config->vmid_mask0 = val1; + sysfs_config->vmid_mask1 = val2 & 0xFF; break; case 0x6: /* COMP5, bits[15:8] */ - config->vmid_mask0 = val1; - config->vmid_mask1 = val2 & 0xFFFF; + sysfs_config->vmid_mask0 = val1; + sysfs_config->vmid_mask1 = val2 & 0xFFFF; break; case 0x7: /* COMP6, bits[23:16] */ - config->vmid_mask0 = val1; - config->vmid_mask1 = val2 & 0xFFFFFF; + sysfs_config->vmid_mask0 = val1; + sysfs_config->vmid_mask1 = val2 & 0xFFFFFF; break; case 0x8: /* COMP7, bits[31:24] */ - config->vmid_mask0 = val1; - config->vmid_mask1 = val2; + sysfs_config->vmid_mask0 = val1; + sysfs_config->vmid_mask1 = val2; break; default: break; @@ -2274,8 +2315,8 @@ static ssize_t vmid_masks_store(struct device *dev, * For example, if bit[3] of vmid_mask0 is 1, we must clear bits[31:24] * of vmid comparator0 value (corresponding to byte 0) register. */ - mask = config->vmid_mask0; - for (i = 0; i < drvdata->numvmidc; i++) { + mask = sysfs_config->vmid_mask0; + for (i = 0; i < caps->numvmidc; i++) { /* mask value of corresponding vmid comparator */ maskbyte = mask & ETMv4_EVENT_MASK; /* @@ -2284,13 +2325,13 @@ static ssize_t vmid_masks_store(struct device *dev, */ for (j = 0; j < 8; j++) { if (maskbyte & 1) - config->vmid_val[i] &= ~(0xFFUL << (j * 8)); + sysfs_config->vmid_val[i] &= ~(0xFFUL << (j * 8)); maskbyte >>= 1; } /* Select the next vmid comparator mask value */ if (i == 3) /* vmid comparators[4-7] */ - mask = config->vmid_mask1; + mask = sysfs_config->vmid_mask1; else mask >>= 0x8; } diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h index df9e0748d71e..5417280cdb27 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.h +++ b/drivers/hwtracing/coresight/coresight-etm4x.h @@ -213,6 +213,11 @@ #define TRCACATRn_EXLEVEL_MASK GENMASK(14, 8) #define TRCSSCSRn_STATUS BIT(31) +/* + * TRCSSCSR.PENDING is only implemented by ETE and is therefore not + * described in DEN0117. See Arm ARM (DDI0487) M.c, section D24.4.59. + */ +#define TRCSSCSRn_PENDING BIT(30) #define TRCSSCCRn_SAC_ARC_RST_MASK GENMASK(24, 0) #define TRCSSPCICRn_PC_MASK GENMASK(7, 0) @@ -814,14 +819,105 @@ enum etm_impdef_type { }; /** + * struct etmv4_caps - specifics ETM capabilities + * @nr_pe: The number of processing entity available for tracing. + * @nr_pe_cmp: The number of processing entity comparator inputs that are + * available for tracing. + * @nr_addr_cmp:Number of pairs of address comparators available + * as found in ETMIDR4 0-3. + * @nr_cntr: Number of counters as found in ETMIDR5 bit 28-30. + * @nr_ext_inp: Number of external input. + * @numcidc: Number of contextID comparators. + * @numextinsel: Number of external input selector resources. + * @numvmidc: Number of VMID comparators. + * @nrseqstate: The number of sequencer states that are implemented. + * @nr_seq_ctrls: The number of sequence state transition control registers. + * @nr_event: Indicates how many events the trace unit support. + * @nr_resource:The number of resource selection pairs available for tracing. + * @nr_ss_cmp: Number of single-shot comparator controls that are available. + * @trcid_size: Indicates the trace ID width. + * @ts_size: Global timestamp size field. + * @ctxid_size: Size of the context ID field to consider. + * @vmid_size: Size of the VM ID comparator to consider. + * @ccsize: Indicates the size of the cycle counter in bits. + * @ccitmin: minimum value that can be programmed in + * @s_ex_level: In secure state, indicates whether instruction tracing is + * supported for the corresponding Exception level. + * @ns_ex_level:In non-secure state, indicates whether instruction tracing is + * supported for the corresponding Exception level. + * @q_support: Q element support characteristics. + * @os_lock_model: OSLock model. + * @instrp0: Tracing of load and store instructions + * as P0 elements is supported. + * @q_filt: Q element filtering support, if Q elements are supported. + * @trcbb: Indicates if the trace unit supports branch broadcast tracing. + * @trccond: If the trace unit supports conditional + * instruction tracing. + * @retstack: Indicates if the implementation supports a return stack. + * @trccci: Indicates if the trace unit supports cycle counting + * for instruction. + * @trc_error: Whether a trace unit can trace a system + * error exception. + * @syncpr: Indicates if an implementation has a fixed + * synchronization period. + * @stallctl: If functionality that prevents trace unit buffer overflows + * is available. + * @sysstall: Does the system support stall control of the PE? + * @nooverflow: Indicate if overflow prevention is supported. + * @atbtrig: If the implementation can support ATB triggers + * @lpoverride: If the implementation can support low-power state over. + * @skip_power_up: Indicates if an implementation can skip powering up + * the trace unit. + */ +struct etmv4_caps { + u8 nr_pe; + u8 nr_pe_cmp; + u8 nr_addr_cmp; + u8 nr_cntr; + u8 nr_ext_inp; + u8 numcidc; + u8 numextinsel; + u8 numvmidc; + u8 nrseqstate; + u8 nr_seq_ctrls; + u8 nr_event; + u8 nr_resource; + u8 nr_ss_cmp; + u8 trcid_size; + u8 ts_size; + u8 ctxid_size; + u8 vmid_size; + u8 ccsize; + u16 ccitmin; + u8 s_ex_level; + u8 ns_ex_level; + u8 q_support; + u8 os_lock_model; + bool instrp0 : 1; + bool q_filt : 1; + bool trcbb : 1; + bool trccond : 1; + bool retstack : 1; + bool trccci : 1; + bool trc_error : 1; + bool syncpr : 1; + bool stallctl : 1; + bool sysstall : 1; + bool nooverflow : 1; + bool atbtrig : 1; + bool lpoverride : 1; + bool skip_power_up : 1; +}; + +/** * struct etmv4_config - configuration information related to an ETMv4 * @mode: Controls various modes supported by this ETM. * @pe_sel: Controls which PE to trace. * @cfg: Controls the tracing options. * @eventctrl0: Controls the tracing of arbitrary events. * @eventctrl1: Controls the behavior of the events that @event_ctrl0 selects. - * @stallctl: If functionality that prevents trace unit buffer overflows - * is available. + * @stall_ctrl: Enables trace unit functionality that prevents trace + * unit buffer overflows. * @ts_ctrl: Controls the insertion of global timestamps in the * trace streams. * @syncfreq: Controls how often trace synchronization requests occur. @@ -860,7 +956,6 @@ enum etm_impdef_type { * @vmid_mask0: VM ID comparator mask for comparator 0-3. * @vmid_mask1: VM ID comparator mask for comparator 4-7. * @ext_inp: External input selection. - * @s_ex_level: Secure ELs where tracing is supported. */ struct etmv4_config { u64 mode; @@ -889,7 +984,6 @@ struct etmv4_config { u32 res_ctrl[ETM_MAX_RES_SEL]; /* TRCRSCTLRn */ u8 ss_idx; u32 ss_ctrl[ETM_MAX_SS_CMP]; - u32 ss_status[ETM_MAX_SS_CMP]; u32 ss_pe_cmp[ETM_MAX_SS_CMP]; u8 addr_idx; u64 addr_val[ETM_MAX_SINGLE_ADDR_CMP]; @@ -904,7 +998,6 @@ struct etmv4_config { u32 vmid_mask0; u32 vmid_mask1; u32 ext_inp; - u8 s_ex_level; }; /** @@ -967,63 +1060,20 @@ struct etmv4_save_state { * @mode: This tracer's mode, i.e sysFS, Perf or disabled. * @cpu: The cpu this component is affined to. * @arch: ETM architecture version. - * @nr_pe: The number of processing entity available for tracing. - * @nr_pe_cmp: The number of processing entity comparator inputs that are - * available for tracing. - * @nr_addr_cmp:Number of pairs of address comparators available - * as found in ETMIDR4 0-3. - * @nr_cntr: Number of counters as found in ETMIDR5 bit 28-30. - * @nr_ext_inp: Number of external input. - * @numcidc: Number of contextID comparators. - * @numvmidc: Number of VMID comparators. - * @nrseqstate: The number of sequencer states that are implemented. - * @nr_seq_ctrls: The number of sequence state transition control registers. - * @nr_event: Indicates how many events the trace unit support. - * @nr_resource:The number of resource selection pairs available for tracing. - * @nr_ss_cmp: Number of single-shot comparator controls that are available. + * @caps: ETM capabilities. * @trcid: value of the current ID for this component. - * @trcid_size: Indicates the trace ID width. - * @ts_size: Global timestamp size field. - * @ctxid_size: Size of the context ID field to consider. - * @vmid_size: Size of the VM ID comparator to consider. - * @ccsize: Indicates the size of the cycle counter in bits. - * @ccitmin: minimum value that can be programmed in - * @s_ex_level: In secure state, indicates whether instruction tracing is - * supported for the corresponding Exception level. - * @ns_ex_level:In non-secure state, indicates whether instruction tracing is - * supported for the corresponding Exception level. * @sticky_enable: true if ETM base configuration has been done. * @boot_enable:True if we should start tracing at boot time. * @os_unlock: True if access to management registers is allowed. - * @instrp0: Tracing of load and store instructions - * as P0 elements is supported. - * @q_filt: Q element filtering support, if Q elements are supported. - * @trcbb: Indicates if the trace unit supports branch broadcast tracing. - * @trccond: If the trace unit supports conditional - * instruction tracing. - * @retstack: Indicates if the implementation supports a return stack. - * @trccci: Indicates if the trace unit supports cycle counting - * for instruction. - * @q_support: Q element support characteristics. - * @trc_error: Whether a trace unit can trace a system - * error exception. - * @syncpr: Indicates if an implementation has a fixed - * synchronization period. - * @stall_ctrl: Enables trace unit functionality that prevents trace - * unit buffer overflows. - * @sysstall: Does the system support stall control of the PE? - * @nooverflow: Indicate if overflow prevention is supported. - * @atbtrig: If the implementation can support ATB triggers - * @lpoverride: If the implementation can support low-power state over. * @trfcr: If the CPU supports FEAT_TRF, value of the TRFCR_ELx that * allows tracing at all ELs. We don't want to compute this * at runtime, due to the additional setting of TRFCR_CX when * in EL2. Otherwise, 0. - * @config: structure holding configuration parameters. + * @curr_config: structure holding current applied configuration. + * @sysfs_config: structure holding sysfs mode configuration. * @save_state: State to be preserved across power loss - * @skip_power_up: Indicates if an implementation can skip powering up - * the trace unit. * @paused: Indicates if the trace unit is paused. + * @ss_status: The status of the corresponding single-shot comparator. * @arch_features: Bitmap of arch features of etmv4 devices. */ struct etmv4_drvdata { @@ -1034,51 +1084,17 @@ struct etmv4_drvdata { raw_spinlock_t spinlock; int cpu; u8 arch; - u8 nr_pe; - u8 nr_pe_cmp; - u8 nr_addr_cmp; - u8 nr_cntr; - u8 nr_ext_inp; - u8 numcidc; - u8 numextinsel; - u8 numvmidc; - u8 nrseqstate; - u8 nr_seq_ctrls; - u8 nr_event; - u8 nr_resource; - u8 nr_ss_cmp; + struct etmv4_caps caps; u8 trcid; - u8 trcid_size; - u8 ts_size; - u8 ctxid_size; - u8 vmid_size; - u8 ccsize; - u16 ccitmin; - u8 s_ex_level; - u8 ns_ex_level; - u8 q_support; - u8 os_lock_model; bool sticky_enable : 1; bool boot_enable : 1; bool os_unlock : 1; - bool instrp0 : 1; - bool q_filt : 1; - bool trcbb : 1; - bool trccond : 1; - bool retstack : 1; - bool trccci : 1; - bool trc_error : 1; - bool syncpr : 1; - bool stallctl : 1; - bool sysstall : 1; - bool nooverflow : 1; - bool atbtrig : 1; - bool lpoverride : 1; - bool skip_power_up : 1; bool paused : 1; u64 trfcr; - struct etmv4_config config; + struct etmv4_config curr_config; + struct etmv4_config sysfs_config; struct etmv4_save_state *save_state; + u32 ss_status[ETM_MAX_SS_CMP]; DECLARE_BITMAP(arch_features, ETM4_IMPDEF_FEATURE_MAX); }; @@ -1099,7 +1115,8 @@ enum etm_addr_ctxtype { }; extern const struct attribute_group *coresight_etmv4_groups[]; -void etm4_config_trace_mode(struct etmv4_config *config); +void etm4_config_trace_mode(struct etmv4_config *config, + const struct etmv4_caps *caps); u64 etm4x_sysreg_read(u32 offset, bool _relaxed, bool _64bit); void etm4x_sysreg_write(u64 val, u32 offset, bool _relaxed, bool _64bit); diff --git a/drivers/hwtracing/coresight/coresight-sysfs.c b/drivers/hwtracing/coresight/coresight-sysfs.c index 4b010f8bc4c0..75000fe64fda 100644 --- a/drivers/hwtracing/coresight/coresight-sysfs.c +++ b/drivers/hwtracing/coresight/coresight-sysfs.c @@ -346,16 +346,19 @@ static ssize_t label_show(struct device *dev, } static DEVICE_ATTR_RO(label); -static umode_t label_is_visible(struct kobject *kobj, - struct attribute *attr, int n) +static umode_t coresight_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) { struct device *dev = kobj_to_dev(kobj); + struct coresight_device *csdev = to_coresight_device(dev); if (attr == &dev_attr_label.attr) { if (fwnode_property_present(dev_fwnode(dev), "label")) return attr->mode; else return 0; + } else if (csdev->flags & CORESIGHT_DESC_NO_SYSFS_MODE) { + return 0; } return attr->mode; @@ -369,7 +372,7 @@ static struct attribute *coresight_sink_attrs[] = { static struct attribute_group coresight_sink_group = { .attrs = coresight_sink_attrs, - .is_visible = label_is_visible, + .is_visible = coresight_attr_is_visible, }; __ATTRIBUTE_GROUPS(coresight_sink); @@ -381,7 +384,7 @@ static struct attribute *coresight_source_attrs[] = { static struct attribute_group coresight_source_group = { .attrs = coresight_source_attrs, - .is_visible = label_is_visible, + .is_visible = coresight_attr_is_visible, }; __ATTRIBUTE_GROUPS(coresight_source); @@ -392,7 +395,7 @@ static struct attribute *coresight_link_attrs[] = { static struct attribute_group coresight_link_group = { .attrs = coresight_link_attrs, - .is_visible = label_is_visible, + .is_visible = coresight_attr_is_visible, }; __ATTRIBUTE_GROUPS(coresight_link); @@ -403,7 +406,7 @@ static struct attribute *coresight_helper_attrs[] = { static struct attribute_group coresight_helper_group = { .attrs = coresight_helper_attrs, - .is_visible = label_is_visible, + .is_visible = coresight_attr_is_visible, }; __ATTRIBUTE_GROUPS(coresight_helper); diff --git a/drivers/hwtracing/coresight/coresight-tnoc.c b/drivers/hwtracing/coresight/coresight-tnoc.c index 9e8de4323d28..929d316b4b36 100644 --- a/drivers/hwtracing/coresight/coresight-tnoc.c +++ b/drivers/hwtracing/coresight/coresight-tnoc.c @@ -255,7 +255,7 @@ static void trace_noc_remove(struct amba_device *adev) coresight_trace_id_put_system_id(drvdata->atid); } -static struct amba_id trace_noc_ids[] = { +static const struct amba_id trace_noc_ids[] = { { .id = 0x000f0c00, .mask = 0x00ffff00, diff --git a/drivers/hwtracing/coresight/coresight-trbe.c b/drivers/hwtracing/coresight/coresight-trbe.c index c7cbca45f2de..eca8e13a9744 100644 --- a/drivers/hwtracing/coresight/coresight-trbe.c +++ b/drivers/hwtracing/coresight/coresight-trbe.c @@ -1348,6 +1348,13 @@ static void arm_trbe_register_coresight_cpu(struct trbe_drvdata *drvdata, int cp desc.dev = dev; desc.cpu = cpu; desc.flags = CORESIGHT_DESC_CPU_BOUND; + /* + * ETE isn't connected to TRBE with a link like other Coresight devices + * and the TRBE driver has been written to always assume Perf mode, so + * Prevent sysfs from being used. + */ + desc.flags |= CORESIGHT_DESC_NO_SYSFS_MODE; + trbe_csdev = coresight_register(&desc); if (IS_ERR(trbe_csdev)) goto cpu_clear; diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 4094299e2ed8..141ac984247c 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -7,13 +7,14 @@ menu "Accelerometers" config ADIS16201 - tristate "Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer" + tristate "ADIS16201 Digital Inclinometer and similar" depends on SPI select IIO_ADIS_LIB select IIO_ADIS_LIB_BUFFER if IIO_BUFFER help - Say Y here to build support for Analog Devices adis16201 dual-axis - digital inclinometer and accelerometer. + Say Y here to build support for Analog Devices Digital Inclinometers: + - ADIS16201 + - ADIS16203 To compile this driver as a module, say M here: the module will be called adis16201. diff --git a/drivers/iio/accel/adis16201.c b/drivers/iio/accel/adis16201.c index ba0f97944c6d..6d5a7b696112 100644 --- a/drivers/iio/accel/adis16201.c +++ b/drivers/iio/accel/adis16201.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer + * ADIS16201 Digital Inclinometer and similar * * Copyright 2010 Analog Devices Inc. */ @@ -63,6 +63,7 @@ #define ADIS16201_DIAG_STAT_REG 0x3C #define ADIS16201_DIAG_STAT_ALARM2 BIT(9) #define ADIS16201_DIAG_STAT_ALARM1 BIT(8) +#define ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT 5 #define ADIS16201_DIAG_STAT_SPI_FAIL_BIT 3 #define ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT 2 /* Power supply above 3.625 V */ @@ -87,6 +88,21 @@ enum adis16201_scan { ADIS16201_SCAN_TEMP, }; +struct adis16201_chip_info { + const char *name; + const struct iio_chan_spec *arr_channels; + const struct adis_data *data; + unsigned int incli_scale_val2; + unsigned int read_bits_incli; + unsigned int num_channels; + u16 write_mask_incli; +}; + +struct adis16201_state { + struct adis adis; + const struct adis16201_chip_info *info; +}; + static const u8 adis16201_addresses[] = { [ADIS16201_SCAN_ACC_X] = ADIS16201_XACCL_OFFS_REG, [ADIS16201_SCAN_ACC_Y] = ADIS16201_YACCL_OFFS_REG, @@ -99,7 +115,7 @@ static int adis16201_read_raw(struct iio_dev *indio_dev, int *val, int *val2, long mask) { - struct adis *st = iio_priv(indio_dev); + struct adis16201_state *st = iio_priv(indio_dev); int ret; int bits; u8 addr; @@ -137,7 +153,7 @@ static int adis16201_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT_PLUS_NANO; case IIO_INCLI: *val = 0; - *val2 = 100000; + *val2 = st->info->incli_scale_val2; return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; @@ -157,13 +173,13 @@ static int adis16201_read_raw(struct iio_dev *indio_dev, bits = 12; break; case IIO_INCLI: - bits = 9; + bits = st->info->read_bits_incli; break; default: return -EINVAL; } addr = adis16201_addresses[chan->scan_index]; - ret = adis_read_reg_16(st, addr, &val16); + ret = adis_read_reg_16(&st->adis, addr, &val16); if (ret) return ret; @@ -180,41 +196,49 @@ static int adis16201_write_raw(struct iio_dev *indio_dev, int val2, long mask) { - struct adis *st = iio_priv(indio_dev); - int m; + struct adis16201_state *st = iio_priv(indio_dev); if (mask != IIO_CHAN_INFO_CALIBBIAS) return -EINVAL; switch (chan->type) { case IIO_ACCEL: - m = GENMASK(11, 0); + val &= GENMASK(11, 0); break; case IIO_INCLI: - m = GENMASK(8, 0); + val &= st->info->write_mask_incli; break; default: return -EINVAL; } - return adis_write_reg_16(st, adis16201_addresses[chan->scan_index], - val & m); + return adis_write_reg_16(&st->adis, adis16201_addresses[chan->scan_index], + val); } static const struct iio_chan_spec adis16201_channels[] = { - ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT_REG, ADIS16201_SCAN_SUPPLY, 0, - 12), - ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT_REG, ADIS16201_SCAN_TEMP, 0, 12), ADIS_ACCEL_CHAN(X, ADIS16201_XACCL_OUT_REG, ADIS16201_SCAN_ACC_X, BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), ADIS_ACCEL_CHAN(Y, ADIS16201_YACCL_OUT_REG, ADIS16201_SCAN_ACC_Y, BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), - ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC_REG, ADIS16201_SCAN_AUX_ADC, 0, 12), ADIS_INCLI_CHAN(X, ADIS16201_XINCL_OUT_REG, ADIS16201_SCAN_INCLI_X, BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 12), ADIS_INCLI_CHAN(Y, ADIS16201_YINCL_OUT_REG, ADIS16201_SCAN_INCLI_Y, BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 12), - IIO_CHAN_SOFT_TIMESTAMP(7) + ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT_REG, ADIS16201_SCAN_SUPPLY, 0, + 12), + ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC_REG, ADIS16201_SCAN_AUX_ADC, 0, 12), + ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT_REG, ADIS16201_SCAN_TEMP, 0, 12), + IIO_CHAN_SOFT_TIMESTAMP(7), +}; + +static const struct iio_chan_spec adis16203_channels[] = { + ADIS_INCLI_CHAN(X, ADIS16201_XINCL_OUT_REG, ADIS16201_SCAN_INCLI_X, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT_REG, ADIS16201_SCAN_SUPPLY, 0, 12), + ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC_REG, ADIS16201_SCAN_AUX_ADC, 0, 12), + ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT_REG, ADIS16201_SCAN_TEMP, 0, 12), + IIO_CHAN_SOFT_TIMESTAMP(7), }; static const struct iio_info adis16201_info = { @@ -224,6 +248,7 @@ static const struct iio_info adis16201_info = { }; static const char * const adis16201_status_error_msgs[] = { + [ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT] = "Self test failure", [ADIS16201_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", [ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT] = "Flash update failed", [ADIS16201_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", @@ -254,10 +279,49 @@ static const struct adis_data adis16201_data = { BIT(ADIS16201_DIAG_STAT_POWER_LOW_BIT), }; +static const struct adis_data adis16203_data = { + .read_delay = 20, + .msc_ctrl_reg = ADIS16201_MSC_CTRL_REG, + .glob_cmd_reg = ADIS16201_GLOB_CMD_REG, + .diag_stat_reg = ADIS16201_DIAG_STAT_REG, + + .self_test_mask = ADIS16201_MSC_CTRL_SELF_TEST_EN, + .self_test_reg = ADIS16201_MSC_CTRL_REG, + .self_test_no_autoclear = true, + .timeouts = &adis16201_timeouts, + + .status_error_msgs = adis16201_status_error_msgs, + .status_error_mask = BIT(ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT) | + BIT(ADIS16201_DIAG_STAT_SPI_FAIL_BIT) | + BIT(ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT) | + BIT(ADIS16201_DIAG_STAT_POWER_HIGH_BIT) | + BIT(ADIS16201_DIAG_STAT_POWER_LOW_BIT), +}; + +static const struct adis16201_chip_info adis16201_chip_data = { + .arr_channels = adis16201_channels, + .incli_scale_val2 = 100000, + .write_mask_incli = GENMASK(8, 0), + .read_bits_incli = 9, + .num_channels = ARRAY_SIZE(adis16201_channels), + .name = "adis16201", + .data = &adis16201_data, +}; + +static const struct adis16201_chip_info adis16203_chip_data = { + .arr_channels = adis16203_channels, + .incli_scale_val2 = 25000, + .write_mask_incli = GENMASK(13, 0), + .read_bits_incli = 14, + .num_channels = ARRAY_SIZE(adis16203_channels), + .name = "adis16203", + .data = &adis16203_data, +}; + static int adis16201_probe(struct spi_device *spi) { struct iio_dev *indio_dev; - struct adis *st; + struct adis16201_state *st; int ret; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); @@ -266,38 +330,57 @@ static int adis16201_probe(struct spi_device *spi) st = iio_priv(indio_dev); - indio_dev->name = spi->dev.driver->name; + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODATA; + + indio_dev->name = st->info->name; indio_dev->info = &adis16201_info; - indio_dev->channels = adis16201_channels; - indio_dev->num_channels = ARRAY_SIZE(adis16201_channels); + indio_dev->channels = st->info->arr_channels; + indio_dev->num_channels = st->info->num_channels; indio_dev->modes = INDIO_DIRECT_MODE; - ret = adis_init(st, indio_dev, spi, &adis16201_data); + ret = adis_init(&st->adis, indio_dev, spi, st->info->data); if (ret) return ret; - ret = devm_adis_setup_buffer_and_trigger(st, indio_dev, NULL); + ret = devm_adis_setup_buffer_and_trigger(&st->adis, indio_dev, NULL); if (ret) return ret; - ret = __adis_initial_startup(st); + ret = __adis_initial_startup(&st->adis); if (ret) return ret; return devm_iio_device_register(&spi->dev, indio_dev); } +static const struct of_device_id adis16201_of_match[] = { + { .compatible = "adi,adis16201", .data = &adis16201_chip_data }, + { .compatible = "adi,adis16203", .data = &adis16203_chip_data }, + { } +}; +MODULE_DEVICE_TABLE(of, adis16201_of_match); + +static const struct spi_device_id adis16201_ids[] = { + { .name = "adis16201", .driver_data = (kernel_ulong_t)&adis16201_chip_data }, + { .name = "adis16203", .driver_data = (kernel_ulong_t)&adis16203_chip_data }, + { } +}; +MODULE_DEVICE_TABLE(spi, adis16201_ids); + static struct spi_driver adis16201_driver = { .driver = { .name = "adis16201", + .of_match_table = adis16201_of_match, }, .probe = adis16201_probe, + .id_table = adis16201_ids, }; module_spi_driver(adis16201_driver); MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); -MODULE_DESCRIPTION("Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer"); +MODULE_DESCRIPTION("Analog Devices ADIS16201 Inclinometer and similar"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("spi:adis16201"); MODULE_IMPORT_NS("IIO_ADISLIB"); diff --git a/drivers/iio/accel/adxl313_spi.c b/drivers/iio/accel/adxl313_spi.c index a61295de104f..0f0da44461f7 100644 --- a/drivers/iio/accel/adxl313_spi.c +++ b/drivers/iio/accel/adxl313_spi.c @@ -79,6 +79,8 @@ static int adxl313_spi_probe(struct spi_device *spi) return ret; chip_data = spi_get_device_match_data(spi); + if (!chip_data) + return -ENODATA; regmap = devm_regmap_init_spi(spi, &adxl31x_spi_regmap_config[chip_data->type]); diff --git a/drivers/iio/accel/adxl367.c b/drivers/iio/accel/adxl367.c index 4ff1c7a0988b..026db73057c5 100644 --- a/drivers/iio/accel/adxl367.c +++ b/drivers/iio/accel/adxl367.c @@ -13,6 +13,7 @@ #include <linux/iio/sysfs.h> #include <linux/interrupt.h> #include <linux/irq.h> +#include <linux/property.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/unaligned.h> @@ -81,6 +82,7 @@ #define ADXL367_SAMPLES_L_MASK GENMASK(7, 0) #define ADXL367_REG_INT1_MAP 0x2A +#define ADXL367_REG_INT2_MAP 0x2B #define ADXL367_INT_INACT_MASK BIT(5) #define ADXL367_INT_ACT_MASK BIT(4) #define ADXL367_INT_FIFO_WATERMARK_MASK BIT(2) @@ -168,6 +170,8 @@ struct adxl367_state { enum adxl367_odr odr; enum adxl367_range range; + u8 int_map_reg; + unsigned int act_threshold; unsigned int act_time_ms; unsigned int inact_threshold; @@ -366,8 +370,7 @@ static int adxl367_set_act_interrupt_en(struct adxl367_state *st, { unsigned int mask = adxl367_act_int_mask_tbl[act]; - return regmap_update_bits(st->regmap, ADXL367_REG_INT1_MAP, - mask, en ? mask : 0); + return regmap_assign_bits(st->regmap, st->int_map_reg, mask, en); } static int adxl367_get_act_interrupt_en(struct adxl367_state *st, @@ -378,7 +381,7 @@ static int adxl367_get_act_interrupt_en(struct adxl367_state *st, unsigned int val; int ret; - ret = regmap_read(st->regmap, ADXL367_REG_INT1_MAP, &val); + ret = regmap_read(st->regmap, st->int_map_reg, &val); if (ret) return ret; @@ -401,9 +404,8 @@ static int adxl367_set_act_en(struct adxl367_state *st, static int adxl367_set_fifo_watermark_interrupt_en(struct adxl367_state *st, bool en) { - return regmap_update_bits(st->regmap, ADXL367_REG_INT1_MAP, - ADXL367_INT_FIFO_WATERMARK_MASK, - en ? ADXL367_INT_FIFO_WATERMARK_MASK : 0); + return regmap_assign_bits(st->regmap, st->int_map_reg, + ADXL367_INT_FIFO_WATERMARK_MASK, en); } static int adxl367_get_fifo_mode(struct adxl367_state *st, @@ -631,8 +633,7 @@ static int adxl367_set_odr(struct iio_dev *indio_dev, enum adxl367_odr odr) static int adxl367_set_temp_adc_en(struct adxl367_state *st, unsigned int reg, bool en) { - return regmap_update_bits(st->regmap, reg, ADXL367_ADC_EN_MASK, - en ? ADXL367_ADC_EN_MASK : 0); + return regmap_assign_bits(st->regmap, reg, ADXL367_ADC_EN_MASK, en); } static int adxl367_set_temp_adc_reg_en(struct adxl367_state *st, @@ -787,6 +788,13 @@ static bool adxl367_push_fifo_data(struct iio_dev *indio_dev, u8 status, if (!FIELD_GET(ADXL367_STATUS_FIFO_FULL_MASK, status)) return false; + if (fifo_entries > ADXL367_FIFO_SIZE) { + dev_err_ratelimited(st->dev, + "FIFO entry count %u exceeds FIFO size %u\n", + fifo_entries, ADXL367_FIFO_SIZE); + return true; + } + fifo_entries -= fifo_entries % st->fifo_set_size; ret = st->ops->read_fifo(st->context, st->fifo_buf, fifo_entries); @@ -1426,6 +1434,36 @@ static int adxl367_setup(struct adxl367_state *st) return adxl367_set_measure_en(st, true); } +static int adxl367_set_int_map_reg(struct adxl367_state *st, int irq) +{ + int ret; + + /* + * Route the mappings to whichever of INT1/INT2 is named in the + * interrupt-names property. -EINVAL (property absent) and -ENODATA + * (name not listed) are not errors; anything else is. + */ + ret = fwnode_irq_get_byname(dev_fwnode(st->dev), "INT1"); + if (ret > 0) { + st->int_map_reg = ADXL367_REG_INT1_MAP; + return ret; + } + if (ret != -ENODATA && ret != -EINVAL) + return ret; + + ret = fwnode_irq_get_byname(dev_fwnode(st->dev), "INT2"); + if (ret > 0) { + st->int_map_reg = ADXL367_REG_INT2_MAP; + return ret; + } + if (ret != -ENODATA && ret != -EINVAL) + return ret; + + /* No INT1/INT2 named: default to INT1 and the bus interrupt. */ + st->int_map_reg = ADXL367_REG_INT1_MAP; + return irq; +} + int adxl367_probe(struct device *dev, const struct adxl367_ops *ops, void *context, struct regmap *regmap, int irq) { @@ -1455,6 +1493,11 @@ int adxl367_probe(struct device *dev, const struct adxl367_ops *ops, indio_dev->info = &adxl367_info; indio_dev->modes = INDIO_DIRECT_MODE; + ret = adxl367_set_int_map_reg(st, irq); + if (ret < 0) + return dev_err_probe(st->dev, ret, "Failed to get interrupt\n"); + irq = ret; + ret = devm_regulator_bulk_get_enable(st->dev, ARRAY_SIZE(regulator_names), regulator_names); diff --git a/drivers/iio/accel/adxl372_spi.c b/drivers/iio/accel/adxl372_spi.c index 128e148c4e96..d54544d6df6f 100644 --- a/drivers/iio/accel/adxl372_spi.c +++ b/drivers/iio/accel/adxl372_spi.c @@ -25,6 +25,8 @@ static int adxl372_spi_probe(struct spi_device *spi) struct regmap *regmap; chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return -ENODATA; regmap = devm_regmap_init_spi(spi, &adxl372_spi_regmap_config); if (IS_ERR(regmap)) diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c index 7dca5523091f..8518ee23e114 100644 --- a/drivers/iio/accel/adxl380.c +++ b/drivers/iio/accel/adxl380.c @@ -966,6 +966,13 @@ static irqreturn_t adxl380_irq_handler(int irq, void *p) if (ret) return IRQ_HANDLED; + if (fifo_entries > ADXL380_FIFO_SAMPLES) { + dev_err_ratelimited(st->dev, + "FIFO entry count %u exceeds FIFO size %lu\n", + fifo_entries, ADXL380_FIFO_SAMPLES); + return IRQ_HANDLED; + } + fifo_entries = rounddown(fifo_entries, st->fifo_set_size); ret = regmap_noinc_read(st->regmap, ADXL380_FIFO_DATA, &st->fifo_buf, sizeof(*st->fifo_buf) * fifo_entries); diff --git a/drivers/iio/accel/adxl380_spi.c b/drivers/iio/accel/adxl380_spi.c index ae8467f4e6c0..8ffb3a8690a6 100644 --- a/drivers/iio/accel/adxl380_spi.c +++ b/drivers/iio/accel/adxl380_spi.c @@ -25,6 +25,8 @@ static int adxl380_spi_probe(struct spi_device *spi) struct regmap *regmap; chip_data = spi_get_device_match_data(spi); + if (!chip_data) + return -ENODATA; regmap = devm_regmap_init_spi(spi, &adxl380_spi_regmap_config); if (IS_ERR(regmap)) diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index d6c1087cc3c0..d7a95289df48 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -28,12 +28,13 @@ static const struct spi_device_id bma220_spi_id[] = { { .name = "bma220" }, { } }; +MODULE_DEVICE_TABLE(spi, bma220_spi_id); static const struct acpi_device_id bma220_acpi_id[] = { { "BMA0220", 0 }, { } }; -MODULE_DEVICE_TABLE(spi, bma220_spi_id); +MODULE_DEVICE_TABLE(acpi, bma220_acpi_id); static const struct of_device_id bma220_of_spi_match[] = { { .compatible = "bosch,bma220" }, diff --git a/drivers/iio/accel/bma400_core.c b/drivers/iio/accel/bma400_core.c index 7e3cff4cca91..6bc64ca87f4b 100644 --- a/drivers/iio/accel/bma400_core.c +++ b/drivers/iio/accel/bma400_core.c @@ -6,8 +6,6 @@ * * TODO: * - Support for power management - * - Support events and interrupts - * - Create channel for step count * - Create channel for sensor time */ diff --git a/drivers/iio/accel/kionix-kx022a.c b/drivers/iio/accel/kionix-kx022a.c index 9ff70a373141..add89512af79 100644 --- a/drivers/iio/accel/kionix-kx022a.c +++ b/drivers/iio/accel/kionix-kx022a.c @@ -301,11 +301,10 @@ struct kx022a_data { __le16 *fifo_buffer; /* 3 x 16bit accel data + timestamp */ - __le16 buffer[8] __aligned(IIO_DMA_MINALIGN); struct { __le16 channels[3]; aligned_s64 ts; - } scan; + } scan __aligned(IIO_DMA_MINALIGN); }; static const struct iio_mount_matrix * @@ -611,14 +610,15 @@ static int kx022a_get_axis(struct kx022a_data *data, struct iio_chan_spec const *chan, int *val) { + __le16 *buf = &data->scan.channels[0]; int ret; - ret = regmap_bulk_read(data->regmap, chan->address, &data->buffer[0], - sizeof(__le16)); + ret = regmap_bulk_read(data->regmap, chan->address, + buf, sizeof(*buf)); if (ret) return ret; - *val = (s16)le16_to_cpu(data->buffer[0]); + *val = (s16)le16_to_cpup(buf); return IIO_VAL_INT; } @@ -809,7 +809,7 @@ static int __kx022a_fifo_flush(struct iio_dev *idev, unsigned int samples, * the timestamps. If we are ran from IRQ, then the * IRQF_ONESHOT has us covered - but if we are ran by the * user-space read we need to disable the IRQ to be on a safe - * side. We do this usng synchronous disable so that if the + * side. We do this using synchronous disable so that if the * IRQ thread is being ran on other CPU we wait for it to be * finished. */ @@ -864,7 +864,8 @@ static int __kx022a_fifo_flush(struct iio_dev *idev, unsigned int samples, for_each_set_bit(bit, idev->active_scan_mask, AXIS_MAX) chs[bit] = sam[bit]; - iio_push_to_buffers_with_timestamp(idev, &data->scan, tstamp); + iio_push_to_buffers_with_ts(idev, &data->scan, + sizeof(data->scan), tstamp); tstamp += sample_period; } @@ -1047,12 +1048,13 @@ static irqreturn_t kx022a_trigger_handler(int irq, void *p) struct kx022a_data *data = iio_priv(idev); int ret; - ret = regmap_bulk_read(data->regmap, data->chip_info->xout_l, data->buffer, - KX022A_FIFO_SAMPLES_SIZE_BYTES); + ret = regmap_bulk_read(data->regmap, data->chip_info->xout_l, + data->scan.channels, KX022A_FIFO_SAMPLES_SIZE_BYTES); if (ret < 0) goto err_read; - iio_push_to_buffers_with_timestamp(idev, data->buffer, data->timestamp); + iio_push_to_buffers_with_ts(idev, &data->scan, sizeof(data->scan), + data->timestamp); err_read: iio_trigger_notify_done(idev->trig); diff --git a/drivers/iio/accel/kionix-kx022a.h b/drivers/iio/accel/kionix-kx022a.h index 0ed54f584223..a2d122c1e234 100644 --- a/drivers/iio/accel/kionix-kx022a.h +++ b/drivers/iio/accel/kionix-kx022a.h @@ -65,7 +65,7 @@ #define KX022A_MASK_IEN BIT(5) #define KX022A_MASK_IPOL BIT(4) #define KX022A_IPOL_LOW 0 -#define KX022A_IPOL_HIGH KX022A_MASK_IPOL1 +#define KX022A_IPOL_HIGH KX022A_MASK_IPOL #define KX022A_MASK_ITYP BIT(3) #define KX022A_ITYP_PULSE KX022A_MASK_ITYP #define KX022A_ITYP_LEVEL 0 diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c index 8994c9e8e048..b3d753bd9476 100644 --- a/drivers/iio/accel/kxcjk-1013.c +++ b/drivers/iio/accel/kxcjk-1013.c @@ -1547,6 +1547,7 @@ static void kxcjk1013_remove(struct i2c_client *client) iio_device_unregister(indio_dev); + pm_runtime_dont_use_autosuspend(&client->dev); pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index 7d683686dd9d..fe62a903f0e2 100644 --- a/drivers/iio/accel/mma8452.c +++ b/drivers/iio/accel/mma8452.c @@ -18,6 +18,8 @@ * TODO: orientation events */ +#include <linux/array_size.h> +#include <linux/cleanup.h> #include <linux/delay.h> #include <linux/i2c.h> #include <linux/module.h> @@ -110,8 +112,7 @@ struct mma8452_data { u8 data_cfg; const struct mma_chip_info *chip_info; int sleep_val; - struct regulator *vdd_reg; - struct regulator *vddio_reg; + struct regulator_bulk_data regs[2]; /* Ensure correct alignment of time stamp when present */ struct { @@ -222,15 +223,15 @@ static int mma8452_drdy(struct mma8452_data *data) static int mma8452_set_runtime_pm_state(struct i2c_client *client, bool on) { #ifdef CONFIG_PM + struct device *dev = &client->dev; int ret; if (on) - ret = pm_runtime_resume_and_get(&client->dev); + ret = pm_runtime_resume_and_get(dev); else - ret = pm_runtime_put_autosuspend(&client->dev); + ret = pm_runtime_put_autosuspend(dev); if (ret < 0) { - dev_err(&client->dev, - "failed to change power state to %d\n", on); + dev_err(dev, "failed to change power state to %d\n", on); return ret; } @@ -495,14 +496,14 @@ static int mma8452_read_raw(struct iio_dev *indio_dev, int i, ret; switch (mask) { - case IIO_CHAN_INFO_RAW: - if (!iio_device_claim_direct(indio_dev)) + case IIO_CHAN_INFO_RAW: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; - mutex_lock(&data->lock); + guard(mutex)(&data->lock); + ret = mma8452_read(data, buffer); - mutex_unlock(&data->lock); - iio_device_release_direct(indio_dev); if (ret < 0) return ret; @@ -511,6 +512,7 @@ static int mma8452_read_raw(struct iio_dev *indio_dev, chan->scan_type.realbits - 1); return IIO_VAL_INT; + } case IIO_CHAN_INFO_SCALE: i = data->data_cfg & MMA8452_DATA_CFG_FS_MASK; *val = data->chip_info->mma_scales[i][0]; @@ -599,36 +601,30 @@ static int mma8452_change_config(struct mma8452_data *data, u8 reg, u8 val) int ret; int is_active; - mutex_lock(&data->lock); + guard(mutex)(&data->lock); is_active = mma8452_is_active(data); - if (is_active < 0) { - ret = is_active; - goto fail; - } + if (is_active < 0) + return is_active; /* config can only be changed when in standby */ if (is_active > 0) { ret = mma8452_standby(data); if (ret < 0) - goto fail; + return ret; } ret = i2c_smbus_write_byte_data(data->client, reg, val); if (ret < 0) - goto fail; + return ret; if (is_active > 0) { ret = mma8452_active(data); if (ret < 0) - goto fail; + return ret; } - ret = 0; -fail: - mutex_unlock(&data->lock); - - return ret; + return 0; } static int mma8452_set_power_mode(struct mma8452_data *data, u8 mode) @@ -777,14 +773,11 @@ static int mma8452_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { - int ret; - - if (!iio_device_claim_direct(indio_dev)) + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; - ret = __mma8452_write_raw(indio_dev, chan, val, val2, mask); - iio_device_release_direct(indio_dev); - return ret; + return __mma8452_write_raw(indio_dev, chan, val, val2, mask); } static int mma8452_get_event_regs(struct mma8452_data *data, @@ -1552,7 +1545,7 @@ static int mma8452_probe(struct i2c_client *client) struct iio_dev *indio_dev; int ret; - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; @@ -1562,32 +1555,21 @@ static int mma8452_probe(struct i2c_client *client) data->chip_info = i2c_get_match_data(client); if (!data->chip_info) - return dev_err_probe(&client->dev, -ENODEV, - "unknown device model\n"); + return dev_err_probe(dev, -ENODEV, "unknown device model\n"); - ret = iio_read_mount_matrix(&client->dev, &data->orientation); + ret = iio_read_mount_matrix(dev, &data->orientation); if (ret) return ret; - data->vdd_reg = devm_regulator_get(&client->dev, "vdd"); - if (IS_ERR(data->vdd_reg)) - return dev_err_probe(&client->dev, PTR_ERR(data->vdd_reg), - "failed to get VDD regulator!\n"); - - data->vddio_reg = devm_regulator_get(&client->dev, "vddio"); - if (IS_ERR(data->vddio_reg)) - return dev_err_probe(&client->dev, PTR_ERR(data->vddio_reg), - "failed to get VDDIO regulator!\n"); - - ret = regulator_enable(data->vdd_reg); + data->regs[0].supply = "vdd"; + data->regs[1].supply = "vddio"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->regs), data->regs); if (ret) - return dev_err_probe(dev, ret, "failed to enable VDD regulator!\n"); + return dev_err_probe(dev, ret, "failed to get regulators\n"); - ret = regulator_enable(data->vddio_reg); - if (ret) { - dev_err_probe(dev, ret, "failed to enable VDDIO regulator!\n"); - goto disable_regulator_vdd; - } + ret = regulator_bulk_enable(ARRAY_SIZE(data->regs), data->regs); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulators\n"); ret = i2c_smbus_read_byte_data(client, MMA8452_WHO_AM_I); if (ret < 0) @@ -1608,7 +1590,7 @@ static int mma8452_probe(struct i2c_client *client) goto disable_regulators; } - dev_info(&client->dev, "registering %s accelerometer; ID 0x%x\n", + dev_info(dev, "registering %s accelerometer; ID 0x%x\n", data->chip_info->name, data->chip_info->chip_id); i2c_set_clientdata(client, indio_dev); @@ -1641,10 +1623,10 @@ static int mma8452_probe(struct i2c_client *client) if (client->irq) { int irq2; - irq2 = fwnode_irq_get_byname(dev_fwnode(&client->dev), "INT2"); + irq2 = fwnode_irq_get_byname(dev_fwnode(dev), "INT2"); if (irq2 == client->irq) { - dev_dbg(&client->dev, "using interrupt line INT2\n"); + dev_dbg(dev, "using interrupt line INT2\n"); } else { ret = i2c_smbus_write_byte_data(client, MMA8452_CTRL_REG5, @@ -1652,7 +1634,7 @@ static int mma8452_probe(struct i2c_client *client) if (ret < 0) goto disable_regulators; - dev_dbg(&client->dev, "using interrupt line INT1\n"); + dev_dbg(dev, "using interrupt line INT1\n"); } ret = i2c_smbus_write_byte_data(client, @@ -1689,14 +1671,13 @@ static int mma8452_probe(struct i2c_client *client) goto buffer_cleanup; } - ret = pm_runtime_set_active(&client->dev); + ret = pm_runtime_set_active(dev); if (ret < 0) goto free_irq; - pm_runtime_enable(&client->dev); - pm_runtime_set_autosuspend_delay(&client->dev, - MMA8452_AUTO_SUSPEND_DELAY_MS); - pm_runtime_use_autosuspend(&client->dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, MMA8452_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); ret = iio_device_register(indio_dev); if (ret < 0) @@ -1722,10 +1703,7 @@ trigger_cleanup: mma8452_trigger_cleanup(indio_dev); disable_regulators: - regulator_disable(data->vddio_reg); - -disable_regulator_vdd: - regulator_disable(data->vdd_reg); + regulator_bulk_disable(ARRAY_SIZE(data->regs), data->regs); return ret; } @@ -1734,11 +1712,12 @@ static void mma8452_remove(struct i2c_client *client) { struct iio_dev *indio_dev = i2c_get_clientdata(client); struct mma8452_data *data = iio_priv(indio_dev); + struct device *dev = &client->dev; iio_device_unregister(indio_dev); - pm_runtime_disable(&client->dev); - pm_runtime_set_suspended(&client->dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); if (client->irq) free_irq(client->irq, indio_dev); @@ -1747,8 +1726,7 @@ static void mma8452_remove(struct i2c_client *client) mma8452_trigger_cleanup(indio_dev); mma8452_standby(iio_priv(indio_dev)); - regulator_disable(data->vddio_reg); - regulator_disable(data->vdd_reg); + regulator_bulk_disable(ARRAY_SIZE(data->regs), data->regs); } #ifdef CONFIG_PM @@ -1758,23 +1736,16 @@ static int mma8452_runtime_suspend(struct device *dev) struct mma8452_data *data = iio_priv(indio_dev); int ret; - mutex_lock(&data->lock); - ret = mma8452_standby(data); - mutex_unlock(&data->lock); + scoped_guard(mutex, &data->lock) + ret = mma8452_standby(data); if (ret < 0) { - dev_err(&data->client->dev, "powering off device failed\n"); + dev_err(dev, "powering off device failed\n"); return -EAGAIN; } - ret = regulator_disable(data->vddio_reg); - if (ret) { - dev_err(dev, "failed to disable VDDIO regulator\n"); - return ret; - } - - ret = regulator_disable(data->vdd_reg); + ret = regulator_bulk_disable(ARRAY_SIZE(data->regs), data->regs); if (ret) { - dev_err(dev, "failed to disable VDD regulator\n"); + dev_err(dev, "failed to disable regulators\n"); return ret; } @@ -1787,16 +1758,9 @@ static int mma8452_runtime_resume(struct device *dev) struct mma8452_data *data = iio_priv(indio_dev); int ret, sleep_val; - ret = regulator_enable(data->vdd_reg); - if (ret) { - dev_err(dev, "failed to enable VDD regulator\n"); - return ret; - } - - ret = regulator_enable(data->vddio_reg); + ret = regulator_bulk_enable(ARRAY_SIZE(data->regs), data->regs); if (ret) { - dev_err(dev, "failed to enable VDDIO regulator\n"); - regulator_disable(data->vdd_reg); + dev_err(dev, "failed to enable regulators\n"); return ret; } @@ -1814,8 +1778,7 @@ static int mma8452_runtime_resume(struct device *dev) return 0; runtime_resume_failed: - regulator_disable(data->vddio_reg); - regulator_disable(data->vdd_reg); + regulator_bulk_disable(ARRAY_SIZE(data->regs), data->regs); return ret; } diff --git a/drivers/iio/accel/mma9551.c b/drivers/iio/accel/mma9551.c index 7e01427fd9c4..182f488edab1 100644 --- a/drivers/iio/accel/mma9551.c +++ b/drivers/iio/accel/mma9551.c @@ -500,6 +500,7 @@ static void mma9551_remove(struct i2c_client *client) iio_device_unregister(indio_dev); + pm_runtime_dont_use_autosuspend(&client->dev); pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); diff --git a/drivers/iio/accel/mma9553.c b/drivers/iio/accel/mma9553.c index 8e7aaac89d47..5a36d415a837 100644 --- a/drivers/iio/accel/mma9553.c +++ b/drivers/iio/accel/mma9553.c @@ -1136,6 +1136,7 @@ static void mma9553_remove(struct i2c_client *client) iio_device_unregister(indio_dev); + pm_runtime_dont_use_autosuspend(&client->dev); pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c index 8f1ac1d3305e..acd0890c78a5 100644 --- a/drivers/iio/accel/sca3000.c +++ b/drivers/iio/accel/sca3000.c @@ -1462,6 +1462,8 @@ static int sca3000_probe(struct spi_device *spi) st->us = spi; mutex_init(&st->lock); st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODATA; indio_dev->name = st->info->name; indio_dev->info = &sca3000_info; diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 415e519ad4eb..a802d8ee4dd9 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -105,7 +105,7 @@ config AD4080 called ad4080. config AD4130 - tristate "Analog Device AD4130 ADC Driver" + tristate "Analog Devices AD4130 ADC Driver" depends on SPI depends on GPIOLIB select IIO_BUFFER @@ -121,7 +121,7 @@ config AD4130 called ad4130. config AD4134 - tristate "Analog Device AD4134 ADC Driver" + tristate "Analog Devices AD4134 ADC Driver" depends on SPI select REGMAP_SPI select CRC8 @@ -133,7 +133,7 @@ config AD4134 called ad4134_spi. config AD4170_4 - tristate "Analog Device AD4170-4 ADC Driver" + tristate "Analog Devices AD4170-4 ADC Driver" depends on SPI select REGMAP_SPI select IIO_BUFFER @@ -164,7 +164,7 @@ config AD4691 called ad4691. config AD4695 - tristate "Analog Device AD4695 ADC Driver" + tristate "Analog Devices AD4695 ADC Driver" depends on SPI select IIO_BUFFER select IIO_BUFFER_DMAENGINE @@ -179,7 +179,7 @@ config AD4695 called ad4695. config AD4851 - tristate "Analog Device AD4851 DAS Driver" + tristate "Analog Devices AD4851 DAS Driver" depends on SPI depends on PWM select REGMAP_SPI @@ -347,7 +347,7 @@ config AD7380 called ad7380. config AD7405 - tristate "Analog Device AD7405 ADC Driver" + tristate "Analog Devices AD7405 ADC Driver" depends on IIO_BACKEND help Say yes here to build support for Analog Devices AD7405, ADUM7701, @@ -431,6 +431,21 @@ config AD7766 To compile this driver as a module, choose M here: the module will be called ad7766. +config AD7768 + tristate "Analog Devices AD7768/AD7768-4 ADC driver" + depends on SPI + select REGULATOR + select AUXILIARY_BUS + select IIO_BUFFER + select IIO_BACKEND + select REGMAP + help + Say yes here to build support for Analog Devices AD7768 and AD7768-4 + SPI analog to digital converters. + + To compile this driver as a module, choose M here: the module will be + called ad7768. + config AD7768_1 tristate "Analog Devices AD7768-1 ADC driver" depends on SPI @@ -576,20 +591,22 @@ config AD9467 called ad9467. config ADE9000 - tristate "Analog Devices ADE9000 Multiphase Energy, and Power Quality Monitoring IC Driver" + tristate "Analog Devices Multiphase Energy and Power Quality Monitoring IC Driver" depends on SPI select REGMAP_SPI select IIO_BUFFER select IIO_KFIFO_BUF help - Say yes here to build support for the Analog Devices ADE9000, - a highly accurate, multiphase energy and power quality monitoring - integrated circuit. + Say yes here to build support for the following Analog Devices + highly accurate, multiphase energy and power quality monitoring + integrated circuits: + - ADE9000 + - ADE9078 - The device features high-precision analog-to-digital converters + The devices feature high-precision analog-to-digital converters and digital signal processing to compute RMS values, power factor, - frequency, and harmonic analysis. It supports SPI communication - and provides buffered data output through the IIO framework. + frequency, and harmonic analysis. They support SPI communication + and provide buffered data output through the IIO framework. To compile this driver as a module, choose M here: the module will be called ade9000. @@ -653,6 +670,17 @@ config AT91_SAMA5D2_ADC To compile this driver as a module, choose M here: the module will be called at91-sama5d2_adc. +config AXIADO_SARADC + tristate "Axiado SARADC driver" + depends on ARCH_AXIADO || COMPILE_TEST + select REGMAP_MMIO + help + Say yes here to build support for the SARADC found in Axiado + SoCs. + + To compile this driver as a module, choose M here: the module + will be called axiado_saradc. + config AXP20X_ADC tristate "X-Powers AXP20X and AXP22X ADC driver" depends on MFD_AXP20X @@ -1116,6 +1144,17 @@ config MAX34408 To compile this driver as a module, choose M here: the module will be called max34408. +config MAX40080 + tristate "Analog Devices MAX40080 Current Sense Amplifier" + depends on I2C + help + Say yes here to build support for the Analog Devices MAX40080 + bidirectional current-sense amplifier with a 12-bit ADC and an I2C + interface. + + To compile this driver as a module, choose M here: the module will be + called max40080. + config MAX77541_ADC tristate "Analog Devices MAX77541 ADC driver" depends on MFD_MAX77541 @@ -1551,6 +1590,8 @@ config RZT2H_ADC tristate "Renesas RZ/T2H / RZ/N2H ADC driver" depends on ARCH_RENESAS || COMPILE_TEST select IIO_ADC_HELPER + select IIO_BUFFER + select IIO_KFIFO_BUF help Say yes here to build support for the ADC found in Renesas RZ/T2H / RZ/N2H SoCs. @@ -1801,11 +1842,14 @@ config TI_ADS1018 called ti-ads1018. config TI_ADS1100 - tristate "Texas Instruments ADS1100 and ADS1000 ADC" + tristate "Texas Instruments ADS1100 and similar single channel I2C ADC" depends on I2C help - If you say yes here you get support for Texas Instruments ADS1100 and - ADS1000 ADC chips. + If you say yes here you get support for TI single channel I2C Analog + Devices. + * ADS1000 12-Bit, 128 SPS Analog-to-Digital Converter + * ADS1100 16-Bit, 128 SPS Analog-to-Digital Converter + * ADS1110 16-Bit, 240 SPS Analog-to-Digital Converter This driver can also be built as a module. If so, the module will be called ti-ads1100. @@ -1822,6 +1866,16 @@ config TI_ADS1119 This driver can also be built as a module. If so, the module will be called ti-ads1119. +config TI_ADS112C04 + tristate "Texas Instruments ADS112C04 ADC" + depends on I2C + help + If you say yes here you get support for Texas Instruments + ADS112C04 (16-bit) I2C analog to digital converters. + + This driver can also be built as a module. If so, the module will be + called ti-ads112c04. + config TI_ADS112C14 tristate "Texas Instruments ADS112C14/ADS122C14" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index dcec0abb03b7..9e82c21dbd37 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_AD7606_IFACE_SPI) += ad7606_spi.o obj-$(CONFIG_AD7606) += ad7606.o obj-$(CONFIG_AD7625) += ad7625.o obj-$(CONFIG_AD7766) += ad7766.o +obj-$(CONFIG_AD7768) += ad7768.o obj-$(CONFIG_AD7768_1) += ad7768-1.o obj-$(CONFIG_AD7779) += ad7779.o obj-$(CONFIG_AD7780) += ad7780.o @@ -55,6 +56,7 @@ obj-$(CONFIG_ADI_AXI_ADC) += adi-axi-adc.o obj-$(CONFIG_ASPEED_ADC) += aspeed_adc.o obj-$(CONFIG_AT91_ADC) += at91_adc.o obj-$(CONFIG_AT91_SAMA5D2_ADC) += at91-sama5d2_adc.o +obj-$(CONFIG_AXIADO_SARADC) += axiado_saradc.o obj-$(CONFIG_AXP20X_ADC) += axp20x_adc.o obj-$(CONFIG_AXP288_ADC) += axp288_adc.o obj-$(CONFIG_BCM_IPROC_ADC) += bcm_iproc_adc.o @@ -95,6 +97,7 @@ obj-$(CONFIG_MAX1241) += max1241.o obj-$(CONFIG_MAX1363) += max1363.o obj-$(CONFIG_MAX14001) += max14001.o obj-$(CONFIG_MAX34408) += max34408.o +obj-$(CONFIG_MAX40080) += max40080.o obj-$(CONFIG_MAX77541_ADC) += max77541-adc.o obj-$(CONFIG_MAX9611) += max9611.o obj-$(CONFIG_MCP320X) += mcp320x.o @@ -155,6 +158,7 @@ obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o obj-$(CONFIG_TI_ADS1018) += ti-ads1018.o obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o +obj-$(CONFIG_TI_ADS112C04) += ti-ads112c04.o obj-$(CONFIG_TI_ADS112C14) += ti-ads112c14.o obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c index 04cd6628ebff..4ed82aa42400 100644 --- a/drivers/iio/adc/ad4080.c +++ b/drivers/iio/adc/ad4080.c @@ -137,6 +137,7 @@ #define AD4880_CHIP_ID 0x0059 #define AD4883_CHIP_ID 0x005B #define AD4884_CHIP_ID 0x005C +#define AD4885_CHIP_ID 0x005D #define AD4080_MAX_CHANNELS 2 @@ -145,6 +146,10 @@ #define AD4080_MAX_SAMP_FREQ 40000000 #define AD4080_MIN_SAMP_FREQ 1250000 +/* debugfs direct_reg_access channel windowing: 0x0RR = ch0, 0x1RR = ch1 */ +#define AD4080_DEBUGFS_REG_CH_MSK GENMASK(15, 8) +#define AD4080_DEBUGFS_REG_OFFSET_MSK GENMASK(7, 0) + enum ad4080_filter_type { FILTER_NONE, SINC_1, @@ -210,11 +215,16 @@ static int ad4080_reg_access(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) { struct ad4080_state *st = iio_priv(indio_dev); + unsigned int ch = FIELD_GET(AD4080_DEBUGFS_REG_CH_MSK, reg); + unsigned int offset = FIELD_GET(AD4080_DEBUGFS_REG_OFFSET_MSK, reg); + + if (ch >= st->info->num_channels) + return -EINVAL; if (readval) - return regmap_read(st->regmap[0], reg, readval); + return regmap_read(st->regmap[ch], offset, readval); - return regmap_write(st->regmap[0], reg, writeval); + return regmap_write(st->regmap[ch], offset, writeval); } static int ad4080_get_scale(struct ad4080_state *st, int *val, int *val2) @@ -552,6 +562,11 @@ static const struct iio_chan_spec ad4884_channels[] = { AD4880_CHANNEL_DEFINE(16, 16, 1), }; +static const struct iio_chan_spec ad4885_channels[] = { + AD4880_CHANNEL_DEFINE(16, 16, 0), + AD4880_CHANNEL_DEFINE(16, 16, 1), +}; + static const struct ad4080_chip_info ad4080_chip_info = { .name = "ad4080", .product_id = AD4080_CHIP_ID, @@ -672,6 +687,16 @@ static const struct ad4080_chip_info ad4884_chip_info = { .lvds_cnv_clk_cnt_max = 2, }; +static const struct ad4080_chip_info ad4885_chip_info = { + .name = "ad4885", + .product_id = AD4885_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 2, + .channels = ad4885_channels, + .lvds_cnv_clk_cnt_max = 8, +}; + static int ad4080_setup_channel(struct ad4080_state *st, unsigned int ch) { struct device *dev = regmap_get_device(st->regmap[ch]); @@ -685,7 +710,8 @@ static int ad4080_setup_channel(struct ad4080_state *st, unsigned int ch) return ret; ret = regmap_write(st->regmap[ch], AD4080_REG_INTERFACE_CONFIG_A, - AD4080_INTERFACE_CONFIG_A_SDO_ENABLE); + AD4080_INTERFACE_CONFIG_A_SDO_ENABLE | + AD4080_INTERFACE_CONFIG_A_ADDR_ASC); if (ret) return ret; @@ -881,6 +907,7 @@ static const struct spi_device_id ad4080_id[] = { { .name = "ad4880", .driver_data = (kernel_ulong_t)&ad4880_chip_info }, { .name = "ad4883", .driver_data = (kernel_ulong_t)&ad4883_chip_info }, { .name = "ad4884", .driver_data = (kernel_ulong_t)&ad4884_chip_info }, + { .name = "ad4885", .driver_data = (kernel_ulong_t)&ad4885_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ad4080_id); @@ -898,6 +925,7 @@ static const struct of_device_id ad4080_of_match[] = { { .compatible = "adi,ad4880", &ad4880_chip_info }, { .compatible = "adi,ad4883", &ad4883_chip_info }, { .compatible = "adi,ad4884", &ad4884_chip_info }, + { .compatible = "adi,ad4885", &ad4885_chip_info }, { } }; MODULE_DEVICE_TABLE(of, ad4080_of_match); diff --git a/drivers/iio/adc/ad4134.c b/drivers/iio/adc/ad4134.c index 0490218bb0e9..70f80bf1825f 100644 --- a/drivers/iio/adc/ad4134.c +++ b/drivers/iio/adc/ad4134.c @@ -496,4 +496,3 @@ module_spi_driver(ad4134_driver); MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>"); MODULE_DESCRIPTION("Analog Devices AD4134 SPI driver"); MODULE_LICENSE("GPL"); -MODULE_IMPORT_NS("IIO_AD4134"); diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c index faebb7b34a15..24904be5090a 100644 --- a/drivers/iio/adc/ad7173.c +++ b/drivers/iio/adc/ad7173.c @@ -1801,8 +1801,7 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) indio_dev->num_channels = num_channels; st->num_channels = num_channels; - chan_arr = devm_kcalloc(dev, sizeof(*indio_dev->channels), - st->num_channels, GFP_KERNEL); + chan_arr = devm_kcalloc(dev, sizeof(*chan_arr), st->num_channels, GFP_KERNEL); if (!chan_arr) return -ENOMEM; diff --git a/drivers/iio/adc/ad7192.c b/drivers/iio/adc/ad7192.c index d8f5886fcf69..8bd810546311 100644 --- a/drivers/iio/adc/ad7192.c +++ b/drivers/iio/adc/ad7192.c @@ -1406,6 +1406,9 @@ static int ad7192_probe(struct spi_device *spi) st->int_vref_mv = ret == -ENODEV ? avdd_mv : ret / MILLI; st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODATA; + indio_dev->name = st->chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = st->chip_info->info; diff --git a/drivers/iio/adc/ad7606_spi.c b/drivers/iio/adc/ad7606_spi.c index 1ebdfb8580ab..8c479fa50a23 100644 --- a/drivers/iio/adc/ad7606_spi.c +++ b/drivers/iio/adc/ad7606_spi.c @@ -456,7 +456,11 @@ static const struct ad7606_bus_info ad7616_bus_info = { static int ad7606_spi_probe(struct spi_device *spi) { - const struct ad7606_bus_info *bus_info = spi_get_device_match_data(spi); + const struct ad7606_bus_info *bus_info; + + bus_info = spi_get_device_match_data(spi); + if (!bus_info) + return -ENODATA; return ad7606_probe(&spi->dev, spi->irq, NULL, bus_info->chip_info, bus_info->bops); diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c index e9060c1bbe6f..3972821c295a 100644 --- a/drivers/iio/adc/ad7768-1.c +++ b/drivers/iio/adc/ad7768-1.c @@ -1829,6 +1829,9 @@ static int ad7768_probe(struct spi_device *spi) } st->chip = spi_get_device_match_data(spi); + if (!st->chip) + return -ENODATA; + st->spi = spi; st->regmap = devm_regmap_init_spi(spi, &ad7768_regmap_config); diff --git a/drivers/iio/adc/ad7768.c b/drivers/iio/adc/ad7768.c new file mode 100644 index 000000000000..aa2db87a1b9f --- /dev/null +++ b/drivers/iio/adc/ad7768.c @@ -0,0 +1,1921 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD7768 ADC driver + * + * Copyright 2018-2026 Analog Devices Inc. + */ + +#include <linux/array_size.h> +#include <linux/auxiliary_bus.h> +#include <linux/bitfield.h> +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/log2.h> +#include <linux/math.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#include <linux/reset.h> +#include <linux/spi/spi.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/unaligned.h> +#include <linux/units.h> + +#include <linux/iio/backend.h> +#include <linux/iio/iio.h> + +#define AD7768_REG_CH_STANDBY 0x00 + +#define AD7768_REG_CH_MODE(x) (0x01 + (x)) +#define AD7768_CH_MODE_FILTER_TYPE_MSK BIT(3) +#define AD7768_CH_MODE_DEC_RATE_MSK GENMASK(2, 0) + +#define AD7768_REG_CH_MODE_SEL 0x03 + +#define AD7768_REG_POWER_MODE 0x04 +#define AD7768_SLEEP_MODE_MSK BIT(7) +#define AD7768_POWER_MODE_POWER_MODE_MSK GENMASK(5, 4) +#define AD7768_POWER_MODE_POWER_MODE_LOW 0x0 +#define AD7768_POWER_MODE_POWER_MODE_MEDIAN 0x2 +#define AD7768_POWER_MODE_POWER_MODE_FAST 0x3 +#define AD7768_POWER_MODE_LVDS_ENABLE BIT(3) +#define AD7768_POWER_MODE_MCLK_DIV_MSK GENMASK(1, 0) + +#define AD7768_REG_GENERAL_CONFIG 0x05 +#define AD7768_GEN_CONFIG_VCM_SEL_MSK GENMASK(1, 0) +#define AD7768_GEN_CONFIG_VCM_PD BIT(4) + +#define AD7768_REG_DATA_CONTROL 0x06 +#define AD7768_DATA_CONTROL_SPI_RESET_1 0x03 +#define AD7768_DATA_CONTROL_SPI_RESET_2 0x02 +#define AD7768_DATA_CONTROL_SPI_SYNC BIT(7) + +#define AD7768_REG_INTERFACE_CFG 0x07 +#define AD7768_INTERFACE_CFG_CRC_SELECT_MSK GENMASK(3, 2) +#define AD7768_INTERFACE_CFG_CRC_SELECT_4 0x1 +#define AD7768_INTERFACE_CFG_DCLK_DIV_MSK GENMASK(1, 0) +#define AD7768_INTERFACE_CFG_DCLK_DIV_8 0x0 +#define AD7768_INTERFACE_CFG_DCLK_DIV_4 0x1 +#define AD7768_INTERFACE_CFG_DCLK_DIV_2 0x2 +#define AD7768_INTERFACE_CFG_DCLK_DIV_1 0x3 + +#define AD7768_REG_REV_ID 0x0A +#define AD7768_REV_ID_VAL 0x06 + +#define AD7768_REG_GPIO_CONTROL 0x0E +#define AD7768_GPIO_UGPIO_ENABLE BIT(7) +#define AD7768_GPIO4_OUTPUT_ENABLE BIT(4) + +#define AD7768_REG_GPIO_WRITE 0x0F + +#define AD7768_REG_PRECHARGE_BUF1 0x11 +#define AD7768_REG_PRECHARGE_BUF2 0x12 +#define AD7768_PREBUF_POS_EN(ch) BIT((ch) * 2 + 0) +#define AD7768_PREBUF_NEG_EN(ch) BIT((ch) * 2 + 1) + +#define AD7768_REG_REFP_BUF 0x13 +#define AD7768_REG_REFN_BUF 0x14 + +#define AD7768_REG_OFFSET_BASE 0x1E +#define AD7768_REG_GAIN_BASE 0x36 +#define AD7768_REG_PHASE_BASE 0x4E +#define AD7768_REG_OFFSET(ch) (AD7768_REG_OFFSET_BASE + 3 * (ch)) +#define AD7768_REG_GAIN(ch) (AD7768_REG_GAIN_BASE + 3 * (ch)) +#define AD7768_REG_PHASE(ch) (AD7768_REG_PHASE_BASE + (ch)) + +#define AD7768_REG_DIAGNOSTIC_RX 0x56 + +#define AD7768_REG_CHOP_CTRL 0x59 + +#define AD7768_SPI_READ_CMD BIT(15) +#define AD7768_SPI_REG_MASK GENMASK(14, 8) +#define AD7768_SPI_DATA_MASK GENMASK(7, 0) + +#define AD7768_SAMPLE_SIZE 32 +#define AD7768_MAX_DCLK_DIV 8 +#define AD7768_MIN_MCLK_FREQ_HZ (1150 * HZ_PER_KHZ) +#define AD7768_MIN_XTAL_FREQ_HZ (8 * HZ_PER_MHZ) +#define AD7768_MAX_MCLK_FREQ_HZ (34 * HZ_PER_MHZ) +#define AD7768_MAX_FREQ_PER_MODE 6 +#define AD7768_MAX_CHANNEL 8 +#define AD7768_NUM_CHANNEL_MODES 2 +#define AD7768_CALIB_SIGN_BIT 23 +#define AD7768_CALIB_REG_MSK GENMASK(AD7768_CALIB_SIGN_BIT, 0) +#define AD7768_WIDEBAND_SETTLING_SAMPLES 68 +#define AD7768_SINC5_SETTLING_SAMPLES 7 + +enum ad7768_filter_type { + AD7768_FILTER_TYPE_WIDEBAND, + AD7768_FILTER_TYPE_SINC5, +}; + +enum ad7768_clock_source { + AD7768_CLOCK_SOURCE_MCLK, + AD7768_CLOCK_SOURCE_XTAL, + AD7768_CLOCK_SOURCE_LVDS, +}; + +struct ad7768_power_mode_info { + unsigned int mode; + unsigned int mclk_div; +}; + +static const struct ad7768_power_mode_info ad7768_power_modes[] = { + { .mode = AD7768_POWER_MODE_POWER_MODE_LOW, .mclk_div = 32 }, + { .mode = AD7768_POWER_MODE_POWER_MODE_MEDIAN, .mclk_div = 8 }, + { .mode = AD7768_POWER_MODE_POWER_MODE_FAST, .mclk_div = 4 }, +}; + +#define AD7768_MAX_FREQS \ + (AD7768_MAX_FREQ_PER_MODE + ARRAY_SIZE(ad7768_power_modes)) + +struct ad7768_precharge_config { + bool prebufp_en; + bool prebufn_en; + bool refbufp; + bool refbufn; +}; + +struct ad7768_freq_config { + unsigned int freq_hz; + unsigned int dec_rate; +}; + +struct ad7768_convdelay_params { + unsigned int shift; + unsigned int max_raw; + u64 step_ps; +}; + +struct ad7768_avail_freq { + unsigned int n_freqs; + struct ad7768_freq_config freq_cfg[AD7768_MAX_FREQ_PER_MODE]; +}; + +struct ad7768_chip_info { + const char *name; + const struct regmap_config *regmap_config; + const unsigned int *available_datalines; + unsigned int num_datalines; + unsigned int num_channels; + const u8 *chan_map; + u8 prebuf_split; +}; + +struct ad7768_state { + struct regmap *regmap; + /* Protects device register access and configuration. */ + struct mutex lock; + struct clk *mclk; + unsigned int datalines; + enum ad7768_clock_source clock_source; + const struct ad7768_chip_info *chip_info; + struct ad7768_avail_freq avail_freq[ARRAY_SIZE(ad7768_power_modes)]; + unsigned int n_freqs; + int freqs[AD7768_MAX_FREQS]; + u64 ch_convdelay_ps[AD7768_MAX_CHANNEL]; + unsigned int ch_freq[AD7768_MAX_CHANNEL]; + unsigned int power_mode_idx; + enum ad7768_filter_type ch_filter[AD7768_MAX_CHANNEL]; + struct iio_backend *back; + struct regulator_dev *vcm_rdev; + unsigned int avdd1_uV; + unsigned int vref_uV[2]; + + /* Used only in regmap_read() callback, hence guarded by regmap lock. */ + __be16 d16 __aligned(IIO_DMA_MINALIGN); +}; + +static const unsigned int ad7768_vcm_voltage_table[] = { + 0, 1650000, 2500000, 2140000, +}; + +static int ad7768_vcm_list_voltage(struct regulator_dev *rdev, + unsigned int selector) +{ + struct ad7768_state *st = rdev_get_drvdata(rdev); + + if (selector >= ARRAY_SIZE(ad7768_vcm_voltage_table)) + return -EINVAL; + + if (!selector) + return DIV_ROUND_CLOSEST(st->avdd1_uV, 2); + + return ad7768_vcm_voltage_table[selector]; +} + +static int ad7768_vcm_enable(struct regulator_dev *rdev) +{ + struct ad7768_state *st = rdev_get_drvdata(rdev); + int ret; + + ret = pm_runtime_resume_and_get(regmap_get_device(st->regmap)); + if (ret < 0) + return ret; + + ret = regmap_clear_bits(st->regmap, AD7768_REG_GENERAL_CONFIG, + AD7768_GEN_CONFIG_VCM_PD); + if (ret) + pm_runtime_put_autosuspend(regmap_get_device(st->regmap)); + + return ret; +} + +static int ad7768_vcm_disable(struct regulator_dev *rdev) +{ + struct ad7768_state *st = rdev_get_drvdata(rdev); + int ret; + + ret = regmap_set_bits(st->regmap, AD7768_REG_GENERAL_CONFIG, + AD7768_GEN_CONFIG_VCM_PD); + if (ret) + return ret; + + pm_runtime_put_autosuspend(regmap_get_device(st->regmap)); + + return 0; +} + +static int ad7768_vcm_is_enabled(struct regulator_dev *rdev) +{ + struct ad7768_state *st = rdev_get_drvdata(rdev); + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = regmap_test_bits(st->regmap, AD7768_REG_GENERAL_CONFIG, + AD7768_GEN_CONFIG_VCM_PD); + if (ret < 0) + return ret; + + return !ret; +} + +static int ad7768_vcm_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct ad7768_state *st = rdev_get_drvdata(rdev); + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + return regmap_update_bits(st->regmap, AD7768_REG_GENERAL_CONFIG, + AD7768_GEN_CONFIG_VCM_SEL_MSK, + FIELD_PREP(AD7768_GEN_CONFIG_VCM_SEL_MSK, + selector)); +} + +static int ad7768_vcm_get_voltage_sel(struct regulator_dev *rdev) +{ + struct ad7768_state *st = rdev_get_drvdata(rdev); + unsigned int val; + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD7768_REG_GENERAL_CONFIG, &val); + if (ret) + return ret; + + return FIELD_GET(AD7768_GEN_CONFIG_VCM_SEL_MSK, val); +} + +static const struct regulator_ops ad7768_vcm_ops = { + .enable = ad7768_vcm_enable, + .disable = ad7768_vcm_disable, + .is_enabled = ad7768_vcm_is_enabled, + .list_voltage = ad7768_vcm_list_voltage, + .set_voltage_sel = ad7768_vcm_set_voltage_sel, + .get_voltage_sel = ad7768_vcm_get_voltage_sel, +}; + +static const struct regulator_desc ad7768_vcm_desc = { + .name = "vcm", + .of_match = "vcm-output", + .regulators_node = "regulators", + .n_voltages = ARRAY_SIZE(ad7768_vcm_voltage_table), + .ops = &ad7768_vcm_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; + +static int ad7768_register_vcm_regulator(struct device *dev, + struct ad7768_state *st) +{ + struct regulator_config config = { + .dev = dev, + .driver_data = st, + }; + int ret; + + /* + * Start with VCM disabled so each enabled state is paired with the + * runtime PM reference acquired by ad7768_vcm_enable(). + */ + ret = regmap_set_bits(st->regmap, AD7768_REG_GENERAL_CONFIG, + AD7768_GEN_CONFIG_VCM_PD); + if (ret) + return ret; + + st->vcm_rdev = devm_regulator_register(dev, &ad7768_vcm_desc, &config); + return PTR_ERR_OR_ZERO(st->vcm_rdev); +} + +static const unsigned int ad7768_dec_rate[AD7768_MAX_FREQ_PER_MODE] = { + 32, 64, 128, 256, 512, 1024, +}; + +static const unsigned int ad7768_available_datalines[] = { + 1, 2, 8, +}; + +static const unsigned int ad7768_4_available_datalines[] = { + 1, 4, +}; + +static const u8 ad7768_chan_map[] = { + 0, 1, 2, 3, 4, 5, 6, 7, +}; + +static const u8 ad7768_4_chan_map[] = { + 0, 1, 4, 5, +}; + +static const char * const ad7768_supply_names[] = { + "avdd2", "iovdd", +}; + +static const char * const ad7768_vref_supply_names[][2] = { + { "ref1p", "ref1n" }, + { "ref2p", "ref2n" }, +}; + +static const char * const ad7768_clock_names[] = { + [AD7768_CLOCK_SOURCE_MCLK] = "mclk", + [AD7768_CLOCK_SOURCE_XTAL] = "xtal", + [AD7768_CLOCK_SOURCE_LVDS] = "lvds", +}; + +static u8 ad7768_channel_mask(const struct ad7768_state *st, u8 ch) +{ + return BIT(st->chip_info->chan_map[ch]); +} + +static u8 ad7768_channel_mode_mask(const struct ad7768_state *st, u8 ch) +{ + /* + * AD7768-4 channels 2 and 3 use bits 4 and 5 for CH_x_MODE, while + * their CH_x_MDE_EN fields are in bits 2 and 3 and must be programmed + * to the same value. On AD7768, chan_map[] is an identity map, so the + * OR produces a single bit. + */ + return BIT(ch) | ad7768_channel_mask(st, ch); +} + +static u8 ad7768_all_standby_mask(const struct ad7768_state *st) +{ + return GENMASK(st->chip_info->num_channels - 1, 0); +} + +static void ad7768_keep_xtal_channel_active(const struct ad7768_state *st, + unsigned long *standby_mask) +{ + /* + * Crystal excitation requires channel 4 on AD7768 or channel 2 on + * AD7768-4 to remain active. + */ + if (st->clock_source == AD7768_CLOCK_SOURCE_XTAL) + __clear_bit(st->chip_info->num_channels / 2, standby_mask); +} + +static unsigned int ad7768_offset_reg(const struct ad7768_state *st, + unsigned int ch) +{ + return AD7768_REG_OFFSET(st->chip_info->chan_map[ch]); +} + +static unsigned int ad7768_gain_reg(const struct ad7768_state *st, + unsigned int ch) +{ + return AD7768_REG_GAIN(st->chip_info->chan_map[ch]); +} + +static unsigned int ad7768_phase_reg(const struct ad7768_state *st, + unsigned int ch) +{ + return AD7768_REG_PHASE(st->chip_info->chan_map[ch]); +} + +static u8 ad7768_precharge_buf1_mask(const struct ad7768_state *st, u16 val) +{ + return val & GENMASK(st->chip_info->prebuf_split - 1, 0); +} + +static u8 ad7768_precharge_buf2_mask(const struct ad7768_state *st, u16 val) +{ + unsigned int split = st->chip_info->prebuf_split; + + return field_get(GENMASK(2 * split - 1, split), val); +} + +static int ad7768_regmap_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, size_t val_size) +{ + struct ad7768_state *st = spi_get_drvdata(context); + struct spi_device *spi = context; + struct spi_transfer t[] = { + { + .tx_buf = &st->d16, + .len = sizeof(st->d16), + .cs_change = 1, + }, { + /* + * Register responses are delayed by one CS frame. While + * receiving the response to this read, the device also + * decodes another command on SDI. Repeat the read + * command to avoid sending an unspecified dummy + * command. + */ + .tx_buf = &st->d16, + .rx_buf = &st->d16, + .len = sizeof(st->d16), + }, + }; + u8 *data_val = val_buf; + unsigned int reg; + int ret; + + reg = *(const u8 *)reg_buf; + + st->d16 = be16_replace_bits(cpu_to_be16(AD7768_SPI_READ_CMD), reg, + AD7768_SPI_REG_MASK); + + ret = spi_sync_transfer(spi, t, ARRAY_SIZE(t)); + if (ret) + return ret; + + *data_val = be16_get_bits(st->d16, AD7768_SPI_DATA_MASK); + + return 0; +} + +static int ad7768_regmap_write(void *spi, const void *data, size_t count) +{ + return spi_write(spi, data, count); +} + +static const struct regmap_bus ad7768_regmap_bus = { + .read = ad7768_regmap_read, + .write = ad7768_regmap_write, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static bool ad7768_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AD7768_REG_CH_STANDBY ... AD7768_REG_REV_ID: + case AD7768_REG_GPIO_CONTROL ... AD7768_REG_REFN_BUF: + case AD7768_REG_OFFSET(0) ... AD7768_REG_OFFSET(7) + 2: + case AD7768_REG_GAIN(0) ... AD7768_REG_GAIN(7) + 2: + case AD7768_REG_PHASE(0) ... AD7768_REG_PHASE(7): + case AD7768_REG_DIAGNOSTIC_RX ... AD7768_REG_CHOP_CTRL: + return true; + default: + return false; + } +} + +static bool ad7768_4_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AD7768_REG_CH_STANDBY ... AD7768_REG_REV_ID: + case AD7768_REG_GPIO_CONTROL ... AD7768_REG_REFN_BUF: + case AD7768_REG_OFFSET(0) ... AD7768_REG_OFFSET(1) + 2: + case AD7768_REG_OFFSET(4) ... AD7768_REG_OFFSET(5) + 2: + case AD7768_REG_GAIN(0) ... AD7768_REG_GAIN(1) + 2: + case AD7768_REG_GAIN(4) ... AD7768_REG_GAIN(5) + 2: + case AD7768_REG_PHASE(0) ... AD7768_REG_PHASE(1): + case AD7768_REG_PHASE(4) ... AD7768_REG_PHASE(5): + case AD7768_REG_DIAGNOSTIC_RX ... AD7768_REG_CHOP_CTRL: + return true; + default: + return false; + } +} + +static const struct regmap_config ad7768_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AD7768_REG_CHOP_CTRL, + /* + * Regmap bulk transfers use one starting address, while each register + * access requires a separate 16-bit SPI frame and reads use an off-frame + * response. Split bulk transfers into individual register accesses. + */ + .use_single_read = true, + .use_single_write = true, + .readable_reg = ad7768_readable_reg, +}; + +static const struct regmap_config ad7768_4_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AD7768_REG_CHOP_CTRL, + /* + * Regmap bulk transfers use one starting address, while each register + * access requires a separate 16-bit SPI frame and reads use an off-frame + * response. Split bulk transfers into individual register accesses. Do + * not use a register cache because some readable registers report live + * hardware state. + */ + .use_single_read = true, + .use_single_write = true, + .readable_reg = ad7768_4_readable_reg, +}; + +static unsigned int ad7768_get_calib_reg_base(struct ad7768_state *st, + const struct iio_chan_spec *chan, + bool is_gain) +{ + if (is_gain) + return ad7768_gain_reg(st, chan->channel); + + return ad7768_offset_reg(st, chan->channel); +} + +static int ad7768_read_calib_value(struct ad7768_state *st, + unsigned int base_reg, bool is_signed, + int *val) +{ + unsigned int regval; + u8 data[3]; + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + ret = regmap_bulk_read(st->regmap, base_reg, data, sizeof(data)); + if (ret) + return ret; + + regval = get_unaligned_be24(data); + *val = is_signed ? sign_extend32(regval, AD7768_CALIB_SIGN_BIT) : regval; + + return 0; +} + +static int ad7768_write_calib_value(struct ad7768_state *st, + unsigned int base_reg, bool is_signed, + int val) +{ + u8 data[3]; + int ret; + + if (is_signed && + val != sign_extend32(val, AD7768_CALIB_SIGN_BIT)) + return -EINVAL; + + if (!is_signed && (val < 0 || val > AD7768_CALIB_REG_MSK)) + return -EINVAL; + + put_unaligned_be24(val, data); + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + return regmap_bulk_write(st->regmap, base_reg, data, sizeof(data)); +} + +static int ad7768_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(st->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int ad7768_sync(struct ad7768_state *st) +{ + int ret; + + ret = regmap_clear_bits(st->regmap, AD7768_REG_DATA_CONTROL, + AD7768_DATA_CONTROL_SPI_SYNC); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, AD7768_REG_DATA_CONTROL, + AD7768_DATA_CONTROL_SPI_SYNC); +} + +static int ad7768_set_power_mode(struct ad7768_state *st, + unsigned int mode_idx) +{ + const struct ad7768_power_mode_info *mode_info; + int ret; + + mode_info = &ad7768_power_modes[mode_idx]; + ret = regmap_update_bits(st->regmap, AD7768_REG_POWER_MODE, + AD7768_POWER_MODE_POWER_MODE_MSK | + AD7768_POWER_MODE_MCLK_DIV_MSK, + FIELD_PREP(AD7768_POWER_MODE_POWER_MODE_MSK, + mode_info->mode) | + FIELD_PREP(AD7768_POWER_MODE_MCLK_DIV_MSK, + mode_info->mode)); + if (ret) + return ret; + + ret = ad7768_sync(st); + if (ret) + return ret; + + st->power_mode_idx = mode_idx; + + return 0; +} + +static const struct ad7768_freq_config * +ad7768_find_freq_config(const struct ad7768_state *st, + unsigned int mode_idx, unsigned int freq) +{ + const struct ad7768_avail_freq *avail_freq = &st->avail_freq[mode_idx]; + + for (unsigned int i = 0; i < avail_freq->n_freqs; i++) { + if (freq == avail_freq->freq_cfg[i].freq_hz) + return &avail_freq->freq_cfg[i]; + } + + return NULL; +} + +static int ad7768_set_clk_divs(struct ad7768_state *st, unsigned int freq) +{ + const struct ad7768_freq_config *freq_cfg; + unsigned int mclk, dclk, dclk_div; + unsigned int chan_per_doutx; + unsigned int dclk_div_reg; + + freq_cfg = ad7768_find_freq_config(st, st->power_mode_idx, freq); + if (!freq_cfg) + return -EINVAL; + + mclk = clk_get_rate(st->mclk); + chan_per_doutx = st->chip_info->num_channels / st->datalines; + if (!chan_per_doutx) + return -EINVAL; + + dclk = freq_cfg->freq_hz * AD7768_SAMPLE_SIZE * chan_per_doutx; + if (dclk > mclk) + return -EINVAL; + + dclk_div = DIV_ROUND_CLOSEST(mclk, dclk); + + /* Set the divider to the next-lowest supported power of two. */ + dclk_div = rounddown_pow_of_two(min(dclk_div, AD7768_MAX_DCLK_DIV)); + + switch (dclk_div) { + case 1: + dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_1; + break; + case 2: + dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_2; + break; + case 4: + dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_4; + break; + case 8: + dclk_div_reg = AD7768_INTERFACE_CFG_DCLK_DIV_8; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(st->regmap, AD7768_REG_INTERFACE_CFG, + AD7768_INTERFACE_CFG_DCLK_DIV_MSK, + FIELD_PREP(AD7768_INTERFACE_CFG_DCLK_DIV_MSK, + dclk_div_reg)); +} + +static int ad7768_set_mode_decimation(struct ad7768_state *st, + unsigned int freq, unsigned int mode) +{ + const struct ad7768_freq_config *freq_cfg; + + freq_cfg = ad7768_find_freq_config(st, st->power_mode_idx, freq); + if (!freq_cfg) + return -EINVAL; + + return regmap_update_bits(st->regmap, AD7768_REG_CH_MODE(mode), + AD7768_CH_MODE_DEC_RATE_MSK, + FIELD_PREP(AD7768_CH_MODE_DEC_RATE_MSK, + freq_cfg->dec_rate)); +} + +static int ad7768_set_lowest_noise_mode(struct ad7768_state *st, + const unsigned long *scan_mask) +{ + unsigned int mode_idx = ARRAY_SIZE(ad7768_power_modes); + + /* + * The output data rate ranges overlap between the power modes. At a + * common ODR, the faster mode has lower noise, so prefer the fastest + * mode that supports every enabled channel. + */ + while (mode_idx--) { + unsigned int channel; + + for_each_set_bit(channel, scan_mask, st->chip_info->num_channels) { + if (!ad7768_find_freq_config(st, mode_idx, + st->ch_freq[channel])) + break; + } + + /* All enabled channels support their requested frequency. */ + if (channel == st->chip_info->num_channels) + return ad7768_set_power_mode(st, mode_idx); + } + + return -EINVAL; +} + +static bool ad7768_freq_supported_in_any_mode(const struct ad7768_state *st, + unsigned int freq) +{ + for (unsigned int mode_idx = 0; + mode_idx < ARRAY_SIZE(ad7768_power_modes); mode_idx++) { + if (ad7768_find_freq_config(st, mode_idx, freq)) + return true; + } + + return false; +} + +static int ad7768_set_sampling_freq(struct iio_dev *indio_dev, + unsigned int freq, unsigned int channel) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + if (!freq || !ad7768_freq_supported_in_any_mode(st, freq)) + return -EINVAL; + + guard(mutex)(&st->lock); + st->ch_freq[channel] = freq; + + return 0; +} + +static int ad7768_set_filter_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + guard(mutex)(&st->lock); + st->ch_filter[chan->channel] = mode; + + return 0; +} + +static int ad7768_get_filter_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + return st->ch_filter[chan->channel]; +} + +static const char *const ad7768_filter_types[] = { + [AD7768_FILTER_TYPE_WIDEBAND] = "wideband", + [AD7768_FILTER_TYPE_SINC5] = "sinc5", +}; + +static const struct iio_enum ad7768_filter_types_enum = { + .items = ad7768_filter_types, + .num_items = ARRAY_SIZE(ad7768_filter_types), + .set = ad7768_set_filter_mode, + .get = ad7768_get_filter_mode, +}; + +static struct iio_chan_spec_ext_info ad7768_ext_info[] = { + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7768_filter_types_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ad7768_filter_types_enum), + { } +}; + +static int ad7768_find_matching_mode(const bool *mode_used, + const unsigned int *mode_freq, + const enum ad7768_filter_type *mode_filter, + unsigned int freq, + enum ad7768_filter_type filter) +{ + for (unsigned int mode = 0; mode < AD7768_NUM_CHANNEL_MODES; mode++) { + if (!mode_used[mode] || + (mode_freq[mode] == freq && mode_filter[mode] == filter)) + return mode; + } + + return -EINVAL; +} + +static int ad7768_get_convdelay_params(struct ad7768_state *st, unsigned int ch, + struct ad7768_convdelay_params *params) +{ + const struct ad7768_freq_config *freq_cfg; + unsigned int dec_rate; + unsigned int mclk_div; + unsigned long mclk; + u64 mult; + + freq_cfg = ad7768_find_freq_config(st, st->power_mode_idx, + st->ch_freq[ch]); + if (!freq_cfg) + return -EINVAL; + + dec_rate = ad7768_dec_rate[freq_cfg->dec_rate]; + switch (dec_rate) { + case 32: + params->shift = 3; + params->max_raw = 31; + mult = 1; + break; + case 64: + params->shift = 2; + params->max_raw = 63; + mult = 1; + break; + case 128: + params->shift = 1; + params->max_raw = 127; + mult = 1; + break; + case 256: + params->shift = 0; + params->max_raw = 255; + mult = 1; + break; + case 512: + params->shift = 0; + params->max_raw = 255; + mult = 2; + break; + case 1024: + params->shift = 0; + params->max_raw = 255; + mult = 4; + break; + default: + return -EINVAL; + } + + mclk = clk_get_rate(st->mclk); + if (!mclk || mclk > U32_MAX) + return -EINVAL; + + mclk_div = ad7768_power_modes[st->power_mode_idx].mclk_div; + params->step_ps = + DIV_ROUND_CLOSEST_ULL(mult * PSEC_PER_SEC * mclk_div, (u32)mclk); + if (params->step_ps > U32_MAX) + return -EINVAL; + + return 0; +} + +static int ad7768_set_channel_convdelay(struct ad7768_state *st, + unsigned int ch) +{ + u64 delay_ps = st->ch_convdelay_ps[ch]; + struct ad7768_convdelay_params params; + u64 max_delay_ps; + u64 raw; + int ret; + + ret = ad7768_get_convdelay_params(st, ch, ¶ms); + if (ret) + return ret; + + /* + * Validate the requested delay before rounding so an out-of-range value + * cannot round down into the valid range. Then verify that the rounded + * value fits in the hardware register. + */ + max_delay_ps = (u64)params.max_raw * params.step_ps; + if (delay_ps > max_delay_ps) + return -EINVAL; + + raw = DIV_ROUND_CLOSEST_ULL(delay_ps, (u32)params.step_ps); + if (raw > params.max_raw) + return -EINVAL; + + return regmap_write(st->regmap, ad7768_phase_reg(st, ch), + raw << params.shift); +} + +static void ad7768_filter_wait(const unsigned int *mode_freq, + const enum ad7768_filter_type *mode_filter, + const bool *mode_used) +{ + unsigned int t_settle_us = 0; + + for (unsigned int mode = 0; mode < AD7768_NUM_CHANNEL_MODES; mode++) { + unsigned int t_mode_us; + u8 settling_samples; + + if (!mode_used[mode] || !mode_freq[mode]) + continue; + + if (mode_filter[mode] == AD7768_FILTER_TYPE_SINC5) + settling_samples = AD7768_SINC5_SETTLING_SAMPLES; + else + settling_samples = AD7768_WIDEBAND_SETTLING_SAMPLES; + + t_mode_us = DIV_ROUND_UP(settling_samples * USEC_PER_SEC, + mode_freq[mode]); + t_settle_us = max(t_settle_us, t_mode_us); + } + + if (t_settle_us) + fsleep(t_settle_us); +} + +static int ad7768_apply_channel_modes(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + enum ad7768_filter_type mode_filter[AD7768_NUM_CHANNEL_MODES] = { }; + unsigned int mode_freq[AD7768_NUM_CHANNEL_MODES] = { }; + bool mode_used[AD7768_NUM_CHANNEL_MODES] = { }; + struct ad7768_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + unsigned long channel_mask; + unsigned long standby_mask; + unsigned int max_freq; + unsigned int c; + int ret; + + guard(mutex)(&st->lock); + + ret = ad7768_set_lowest_noise_mode(st, scan_mask); + if (ret == -EINVAL) + return dev_err_probe(dev, ret, + "No power mode supports all ODRs\n"); + if (ret) + return ret; + + channel_mask = ad7768_all_standby_mask(st); + standby_mask = channel_mask & ~*scan_mask; + ad7768_keep_xtal_channel_active(st, &standby_mask); + + for_each_set_bit(c, scan_mask, st->chip_info->num_channels) { + unsigned int mask; + int mode; + + mode = ad7768_find_matching_mode(mode_used, mode_freq, + mode_filter, st->ch_freq[c], + st->ch_filter[c]); + if (mode < 0) + return dev_err_probe(dev, -EINVAL, + "Over %d channel modes required\n", + AD7768_NUM_CHANNEL_MODES); + + mode_freq[mode] = st->ch_freq[c]; + mode_filter[mode] = st->ch_filter[c]; + mode_used[mode] = true; + + mask = ad7768_channel_mode_mask(st, c); + ret = regmap_assign_bits(st->regmap, AD7768_REG_CH_MODE_SEL, + mask, mode); + if (ret) + return ret; + } + + ret = regmap_update_bits(st->regmap, AD7768_REG_CH_STANDBY, + channel_mask, standby_mask); + if (ret) + return ret; + + max_freq = 0; + for (unsigned int mode = 0; mode < AD7768_NUM_CHANNEL_MODES; mode++) { + if (!mode_used[mode]) + continue; + + ret = ad7768_set_mode_decimation(st, mode_freq[mode], mode); + if (ret) + return ret; + + ret = regmap_assign_bits(st->regmap, AD7768_REG_CH_MODE(mode), + AD7768_CH_MODE_FILTER_TYPE_MSK, + mode_filter[mode]); + if (ret) + return ret; + + max_freq = max(max_freq, mode_freq[mode]); + } + + for_each_set_bit(c, scan_mask, st->chip_info->num_channels) { + ret = ad7768_set_channel_convdelay(st, c); + if (ret == -EINVAL) + return dev_err_probe(regmap_get_device(st->regmap), ret, + "Invalid conversion delay for channel %u\n", + c); + if (ret) + return ret; + } + + ret = ad7768_set_clk_divs(st, max_freq); + if (ret) + return ret; + + ret = ad7768_sync(st); + if (ret) + return ret; + + /* Apply a filter settling time (datasheet Tables 28 and 29). */ + ad7768_filter_wait(mode_freq, mode_filter, mode_used); + + return 0; +} + +static int ad7768_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + ret = ad7768_apply_channel_modes(indio_dev, scan_mask); + if (ret) + return ret; + + for (unsigned int ch = 0; ch < st->chip_info->num_channels; ch++) { + if (test_bit(ch, scan_mask)) + ret = iio_backend_chan_enable(st->back, ch); + else + ret = iio_backend_chan_disable(st->back, ch); + if (ret) + return ret; + } + + return 0; +} + +static const struct ad7768_chip_info ad7768_chip_info = { + .name = "ad7768", + .num_channels = 8, + .regmap_config = &ad7768_regmap_config, + .available_datalines = ad7768_available_datalines, + .num_datalines = ARRAY_SIZE(ad7768_available_datalines), + .chan_map = ad7768_chan_map, + .prebuf_split = 8, +}; + +static const struct ad7768_chip_info ad7768_4_chip_info = { + .name = "ad7768-4", + .num_channels = 4, + .regmap_config = &ad7768_4_regmap_config, + .available_datalines = ad7768_4_available_datalines, + .num_datalines = ARRAY_SIZE(ad7768_4_available_datalines), + .chan_map = ad7768_4_chan_map, + .prebuf_split = 4, +}; + +static int ad7768_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + return pm_runtime_resume_and_get(regmap_get_device(st->regmap)); +} + +static int ad7768_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + pm_runtime_put_autosuspend(regmap_get_device(st->regmap)); + + return 0; +} + +static const struct iio_buffer_setup_ops ad7768_buffer_ops = { + .preenable = ad7768_buffer_preenable, + .postdisable = ad7768_buffer_postdisable, +}; + +static int ad7768_read_scale(struct ad7768_state *st, + const struct iio_chan_spec *chan, + int *val, int *val2) +{ + unsigned int vref_idx; + + vref_idx = chan->channel >= st->chip_info->num_channels / 2; + *val = 2 * st->vref_uV[vref_idx] / (MICRO / MILLI); + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; +} + +static int ad7768_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + return ad7768_read_scale(st, chan, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: { + guard(mutex)(&st->lock); + *val = st->ch_freq[chan->channel]; + + return IIO_VAL_INT; + } + + case IIO_CHAN_INFO_CALIBBIAS: + case IIO_CHAN_INFO_CALIBSCALE: { + bool is_gain = info == IIO_CHAN_INFO_CALIBSCALE; + unsigned int base_reg; + + base_reg = ad7768_get_calib_reg_base(st, chan, is_gain); + ret = ad7768_read_calib_value(st, base_reg, !is_gain, val); + if (ret) + return ret; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_CONVDELAY: { + guard(mutex)(&st->lock); + iio_val_s64_decompose(st->ch_convdelay_ps[chan->channel], + val, val2); + + return IIO_VAL_DECIMAL64_PICO; + } + default: + return -EINVAL; + } +} + +static int ad7768_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long info) +{ + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + case IIO_CHAN_INFO_CALIBBIAS: + case IIO_CHAN_INFO_CALIBSCALE: + return IIO_VAL_INT; + case IIO_CHAN_INFO_CONVDELAY: + return IIO_VAL_DECIMAL64_PICO; + default: + return -EINVAL; + } +} + +static int ad7768_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad7768_state *st = iio_priv(indio_dev); + unsigned int base_reg; + s64 delay_ps; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + return ad7768_set_sampling_freq(indio_dev, val, chan->channel); + case IIO_CHAN_INFO_CALIBBIAS: + base_reg = ad7768_get_calib_reg_base(st, chan, false); + return ad7768_write_calib_value(st, base_reg, true, val); + case IIO_CHAN_INFO_CALIBSCALE: + base_reg = ad7768_get_calib_reg_base(st, chan, true); + return ad7768_write_calib_value(st, base_reg, false, val); + case IIO_CHAN_INFO_CONVDELAY: { + delay_ps = iio_val_s64_compose(val, val2); + if (delay_ps < 0) + return -EINVAL; + + guard(mutex)(&st->lock); + st->ch_convdelay_ps[chan->channel] = delay_ps; + return 0; + } + default: + return -EINVAL; + } +} + +static int ad7768_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + if (info != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + *vals = st->freqs; + *type = IIO_VAL_INT; + *length = st->n_freqs; + + return IIO_AVAIL_LIST; +} + +static const struct iio_info ad7768_info = { + .debugfs_reg_access = ad7768_reg_access, + .read_raw = ad7768_read_raw, + .write_raw_get_fmt = ad7768_write_raw_get_fmt, + .write_raw = ad7768_write_raw, + .read_avail = ad7768_read_avail, + .update_scan_mode = ad7768_update_scan_mode, +}; + +static int ad7768_gpio_adev_init(struct ad7768_state *st) +{ + struct device *dev = regmap_get_device(st->regmap); + struct spi_device *spi = to_spi_device(dev); + struct auxiliary_device *adev; + int id; + + if (!device_property_read_bool(dev, "gpio-controller")) + return 0; + + /* + * Use the SPI bus number and chip select to derive a stable per-device + * ID. + */ + id = (spi->controller->bus_num << 8) | spi_get_chipselect(spi, 0); + adev = __devm_auxiliary_device_create(dev, KBUILD_MODNAME, "gpio", + NULL, id); + if (!adev) + return dev_err_probe(dev, -ENODEV, + "Failed to create GPIO auxiliary device\n"); + + return 0; +} + +static int ad7768_configure_precharge_buffers(struct iio_dev *indio_dev, + struct ad7768_precharge_config *precharge_cfg) +{ + struct ad7768_state *st = iio_priv(indio_dev); + u8 prebuf1_val, prebuf2_val; + u16 prebuf_mask = 0; + u8 refbufp_val = 0; + u8 refbufn_val = 0; + int ret; + + for (unsigned int ch = 0; ch < indio_dev->num_channels; ch++) { + u8 channel = indio_dev->channels[ch].channel; + + if (precharge_cfg[channel].prebufp_en) + prebuf_mask |= AD7768_PREBUF_POS_EN(channel); + + if (precharge_cfg[channel].prebufn_en) + prebuf_mask |= AD7768_PREBUF_NEG_EN(channel); + + if (precharge_cfg[channel].refbufp) + refbufp_val |= ad7768_channel_mask(st, channel); + + if (precharge_cfg[channel].refbufn) + refbufn_val |= ad7768_channel_mask(st, channel); + } + + prebuf1_val = ad7768_precharge_buf1_mask(st, ~prebuf_mask); + prebuf2_val = ad7768_precharge_buf2_mask(st, ~prebuf_mask); + + ret = regmap_write(st->regmap, AD7768_REG_PRECHARGE_BUF1, prebuf1_val); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD7768_REG_PRECHARGE_BUF2, prebuf2_val); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD7768_REG_REFP_BUF, refbufp_val); + if (ret) + return ret; + + return regmap_write(st->regmap, AD7768_REG_REFN_BUF, refbufn_val); +} + +static void ad7768_set_available_sampling_freqs(struct ad7768_state *st) +{ + unsigned int n_power_modes = ARRAY_SIZE(ad7768_power_modes); + unsigned int mclk = clk_get_rate(st->mclk); + + for (unsigned int mode_idx = 0; mode_idx < n_power_modes; mode_idx++) { + unsigned int dec = ARRAY_SIZE(ad7768_dec_rate); + struct ad7768_avail_freq *avail_freq; + unsigned int div; + + avail_freq = &st->avail_freq[mode_idx]; + div = ad7768_power_modes[mode_idx].mclk_div; + while (dec--) { + struct ad7768_freq_config *freq_cfg; + + freq_cfg = &avail_freq->freq_cfg[avail_freq->n_freqs++]; + freq_cfg->dec_rate = dec; + freq_cfg->freq_hz = mclk / (ad7768_dec_rate[dec] * div); + } + } + + /* One DOUT line cannot carry the AD7768 fast-mode x32 output rate. */ + if (st->datalines == 1 && + st->chip_info->num_channels == AD7768_MAX_CHANNEL) + st->avail_freq[n_power_modes - 1].n_freqs--; + + for (unsigned int mode_idx = 0; mode_idx < n_power_modes; mode_idx++) { + const struct ad7768_avail_freq *avail_freq; + + avail_freq = &st->avail_freq[mode_idx]; + for (unsigned int i = 0; i < avail_freq->n_freqs; i++) { + unsigned int freq = avail_freq->freq_cfg[i].freq_hz; + + if (st->n_freqs && st->freqs[st->n_freqs - 1] >= freq) + continue; + + st->freqs[st->n_freqs++] = freq; + } + } +} + +static int ad7768_init_sampling_freqs(struct ad7768_state *st) +{ + const struct ad7768_avail_freq *avail_freq; + unsigned int default_freq; + int ret; + + ret = ad7768_set_power_mode(st, ARRAY_SIZE(ad7768_power_modes) - 1); + if (ret) + return ret; + + avail_freq = &st->avail_freq[st->power_mode_idx]; + default_freq = avail_freq->freq_cfg[avail_freq->n_freqs - 1].freq_hz; + for (unsigned int channel = 0; + channel < st->chip_info->num_channels; channel++) { + st->ch_freq[channel] = default_freq; + st->ch_filter[channel] = AD7768_FILTER_TYPE_WIDEBAND; + } + + return 0; +} + +static int ad7768_parse_config(struct iio_dev *indio_dev, + struct device *dev) +{ + struct ad7768_precharge_config precharge_cfg[AD7768_MAX_CHANNEL] = { }; + struct ad7768_state *st = iio_priv(indio_dev); + const unsigned int *available_datalines; + const char *propname; + bool datalines_valid = false; + struct iio_chan_spec *chan; + unsigned int num_channels; + unsigned long standby_mask; + unsigned int len; + int chan_idx = 0; + int ret; + + propname = "adi,data-lines-number"; + num_channels = device_get_named_child_node_count(dev, "channel"); + if (num_channels == 0) + return dev_err_probe(dev, -ENOENT, "No channel specified\n"); + if (num_channels > st->chip_info->num_channels) + return dev_err_probe(dev, -ENOSPC, "Invalid number of channels\n"); + + chan = devm_kcalloc(dev, num_channels, sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + + indio_dev->channels = chan; + indio_dev->num_channels = num_channels; + + standby_mask = ad7768_all_standby_mask(st); + ad7768_keep_xtal_channel_active(st, &standby_mask); + + ret = regmap_write(st->regmap, AD7768_REG_CH_STANDBY, standby_mask); + if (ret) + return ret; + + device_for_each_named_child_node_scoped(dev, child, "channel") { + u32 channel; + + ret = fwnode_property_read_u32(child, "reg", &channel); + if (ret) + return dev_err_probe(dev, ret, + "Failed to parse reg of %pfwP\n", + child); + + if (channel >= st->chip_info->num_channels) + return dev_err_probe(dev, -ECHRNG, + "Invalid channel %u in firmware\n", + channel); + + ret = regmap_clear_bits(st->regmap, AD7768_REG_CH_STANDBY, + BIT(channel)); + if (ret) + return ret; + + precharge_cfg[channel].prebufp_en = + fwnode_property_read_bool(child, "adi,prechargebuf-pos-enable"); + precharge_cfg[channel].prebufn_en = + fwnode_property_read_bool(child, "adi,prechargebuf-neg-enable"); + precharge_cfg[channel].refbufp = + fwnode_property_read_bool(child, "adi,refbuf-pos-enable"); + precharge_cfg[channel].refbufn = + fwnode_property_read_bool(child, "adi,refbuf-neg-enable"); + + chan[chan_idx++] = (struct iio_chan_spec) { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CONVDELAY) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .indexed = 1, + .channel = channel, + .scan_index = channel, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + .ext_info = ad7768_ext_info, + }; + } + + ret = ad7768_configure_precharge_buffers(indio_dev, precharge_cfg); + if (ret) + return ret; + + available_datalines = st->chip_info->available_datalines; + len = st->chip_info->num_datalines; + if (device_property_present(dev, propname)) { + ret = device_property_read_u32(dev, propname, &st->datalines); + if (ret) + return dev_err_probe(dev, ret, "Invalid %s property\n", + propname); + } else { + st->datalines = available_datalines[len - 1]; + } + + for (unsigned int i = 0; i < len; i++) { + if (available_datalines[i] == st->datalines) { + datalines_valid = true; + break; + } + } + + if (!datalines_valid) + return dev_err_probe(dev, -EINVAL, + "Invalid %s %u for %s\n", propname, + st->datalines, st->chip_info->name); + + ad7768_set_available_sampling_freqs(st); + + ret = ad7768_init_sampling_freqs(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to set power mode\n"); + + return 0; +} + +static int ad7768_reset(struct ad7768_state *st) +{ + struct device *dev = regmap_get_device(st->regmap); + struct reset_control *reset_ctrl; + unsigned long reset_low_us; + unsigned long mclk; + int ret; + + reset_ctrl = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(reset_ctrl)) + return PTR_ERR(reset_ctrl); + + if (reset_ctrl) { + mclk = clk_get_rate(st->mclk); + if (!mclk) + return -EINVAL; + + /* + * Minimum RESET low pulse width: 2 x tMCLK + * (datasheet Table 1). + */ + reset_low_us = DIV_ROUND_UP_ULL(2ULL * USEC_PER_SEC, mclk); + + ret = reset_control_assert(reset_ctrl); + if (ret) + return ret; + + fsleep(max(1UL, reset_low_us)); + + ret = reset_control_deassert(reset_ctrl); + if (ret) + return ret; + } else { + ret = regmap_write(st->regmap, AD7768_REG_DATA_CONTROL, + AD7768_DATA_CONTROL_SPI_RESET_1); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD7768_REG_DATA_CONTROL, + AD7768_DATA_CONTROL_SPI_RESET_2); + if (ret) + return ret; + } + + /* ADC start-up time after reset: 1.66 ms max (datasheet Table 1) */ + fsleep(1660); + + return 0; +} + +static void ad7768_disable_clk(void *clk) +{ + clk_disable_unprepare(clk); +} + +static int ad7768_configure_xtal_clock(struct ad7768_state *st) +{ + int ret; + + ret = regmap_set_bits(st->regmap, AD7768_REG_GPIO_WRITE, BIT(4)); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, AD7768_REG_GPIO_CONTROL, + AD7768_GPIO_UGPIO_ENABLE | + AD7768_GPIO4_OUTPUT_ENABLE); +} + +static int ad7768_enable_lvds_clock(struct ad7768_state *st) +{ + struct device *dev = regmap_get_device(st->regmap); + int ret; + + ret = regmap_clear_bits(st->regmap, AD7768_REG_GPIO_WRITE, BIT(4)); + if (ret) + return ret; + + ret = regmap_set_bits(st->regmap, AD7768_REG_GPIO_CONTROL, + AD7768_GPIO_UGPIO_ENABLE | + AD7768_GPIO4_OUTPUT_ENABLE); + if (ret) + return ret; + + ret = regmap_set_bits(st->regmap, AD7768_REG_POWER_MODE, + AD7768_POWER_MODE_LVDS_ENABLE); + if (ret) + return ret; + + ret = clk_prepare_enable(st->mclk); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, ad7768_disable_clk, st->mclk); +} + +static int ad7768_get_enable_vref(struct device *dev, unsigned int index) +{ + const char * const *supply = ad7768_vref_supply_names[index]; + int refp_uV; + int refn_uV; + + refp_uV = devm_regulator_get_enable_read_voltage(dev, supply[0]); + if (refp_uV < 0) + return dev_err_probe(dev, refp_uV, + "Failed to get %s supply voltage\n", supply[0]); + + refn_uV = devm_regulator_get_enable_read_voltage(dev, supply[1]); + if (refn_uV == -ENODEV) + refn_uV = 0; + else if (refn_uV < 0) + return dev_err_probe(dev, refn_uV, + "Failed to get %s supply voltage\n", supply[1]); + + if (refp_uV <= refn_uV) + return dev_err_probe(dev, -EINVAL, + "Invalid reference %u voltage\n", index + 1); + + return refp_uV - refn_uV; +} + +static int ad7768_enter_sleep(struct ad7768_state *st) +{ + return regmap_set_bits(st->regmap, AD7768_REG_POWER_MODE, + AD7768_SLEEP_MODE_MSK); +} + +static void ad7768_power_off(void *data) +{ + struct ad7768_state *st = data; + struct device *dev = regmap_get_device(st->regmap); + int ret; + + ret = ad7768_enter_sleep(st); + if (ret) + dev_err(dev, "Failed to put device to sleep\n"); +} + +static int ad7768_validate_mclk_rate(struct device *dev, + const struct ad7768_state *st) +{ + unsigned long min_rate = AD7768_MIN_MCLK_FREQ_HZ; + unsigned long rate = clk_get_rate(st->mclk); + + if (st->clock_source == AD7768_CLOCK_SOURCE_XTAL) + min_rate = AD7768_MIN_XTAL_FREQ_HZ; + + if (rate < min_rate || rate > AD7768_MAX_MCLK_FREQ_HZ) + return dev_err_probe(dev, -EINVAL, + "MCLK rate %lu Hz outside %lu-%lu Hz\n", + rate, min_rate, AD7768_MAX_MCLK_FREQ_HZ); + + return 0; +} + +static int ad7768_probe(struct spi_device *spi) +{ + unsigned int spi_readback, rev_id; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad7768_state *st; + const char *clock_name; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, st); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return dev_err_probe(dev, -ENODATA, "Failed to get match data\n"); + + ret = devm_regulator_get_enable_optional(dev, "avss"); + if (ret == -ENODEV) { + /* AVSS may be connected directly to ground instead of a regulator. */ + } else if (ret) { + return dev_err_probe(dev, ret, "Failed to enable AVSS supply\n"); + } + + ret = devm_regulator_get_enable_read_voltage(dev, "avdd1"); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to enable AVDD1 supply\n"); + + st->avdd1_uV = ret; + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(ad7768_supply_names), + ad7768_supply_names); + if (ret) + return ret; + + for (unsigned int i = 0; i < ARRAY_SIZE(ad7768_vref_supply_names); i++) { + ret = ad7768_get_enable_vref(dev, i); + if (ret < 0) + return ret; + + st->vref_uV[i] = ret; + } + + ret = device_property_match_property_string(dev, "clock-names", + ad7768_clock_names, + ARRAY_SIZE(ad7768_clock_names)); + if (ret < 0) + return dev_err_probe(dev, ret, "Invalid clock source\n"); + + st->clock_source = ret; + clock_name = ad7768_clock_names[st->clock_source]; + + /* + * The device must start on its internal clock. Keep the LVDS clock + * disabled until GPIO4 is driven low and the LVDS input is enabled in + * the power mode register. + */ + switch (st->clock_source) { + case AD7768_CLOCK_SOURCE_MCLK: + case AD7768_CLOCK_SOURCE_XTAL: + st->mclk = devm_clk_get_enabled(dev, clock_name); + break; + case AD7768_CLOCK_SOURCE_LVDS: + st->mclk = devm_clk_get(dev, clock_name); + break; + } + + if (IS_ERR(st->mclk)) + return dev_err_probe(dev, PTR_ERR(st->mclk), + "Failed to get master clock\n"); + + ret = ad7768_validate_mclk_rate(dev, st); + if (ret) + return ret; + + st->regmap = devm_regmap_init(dev, &ad7768_regmap_bus, spi, + st->chip_info->regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret = ad7768_reset(st); + if (ret) + return ret; + + /* Discard the reset response with a dummy SPI register read. */ + ret = regmap_read(st->regmap, AD7768_REG_REV_ID, &spi_readback); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD7768_REG_REV_ID, &rev_id); + if (ret) + return ret; + + if (rev_id != AD7768_REV_ID_VAL) + dev_info(dev, "Unexpected revision ID 0x%02x\n", rev_id); + + switch (st->clock_source) { + case AD7768_CLOCK_SOURCE_MCLK: + break; + case AD7768_CLOCK_SOURCE_XTAL: + ret = ad7768_configure_xtal_clock(st); + if (ret) + return dev_err_probe(dev, ret, + "Failed to configure crystal clock\n"); + break; + case AD7768_CLOCK_SOURCE_LVDS: + ret = ad7768_enable_lvds_clock(st); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable LVDS clock\n"); + break; + } + + ret = devm_add_action_or_reset(dev, ad7768_power_off, st); + if (ret) + return ret; + + ret = ad7768_parse_config(indio_dev, dev); + if (ret) + return ret; + + /* + * Hardware supports CRC every 4 or 16 samples; the backend supports only + * 4-sample CRC. + */ + ret = regmap_update_bits(st->regmap, AD7768_REG_INTERFACE_CFG, + AD7768_INTERFACE_CFG_CRC_SELECT_MSK, + FIELD_PREP(AD7768_INTERFACE_CFG_CRC_SELECT_MSK, + AD7768_INTERFACE_CFG_CRC_SELECT_4)); + if (ret) + return ret; + + indio_dev->name = st->chip_info->name; + indio_dev->info = &ad7768_info; + indio_dev->setup_ops = &ad7768_buffer_ops; + + st->back = devm_iio_backend_get(dev, NULL); + if (IS_ERR(st->back)) + return PTR_ERR(st->back); + + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + + ret = iio_backend_num_lanes_set(st->back, st->datalines); + if (ret) + return ret; + + ret = iio_backend_crc_enable(st->back); + if (ret) + return ret; + + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return ret; + + ret = ad7768_register_vcm_regulator(dev, st); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register VCM regulator\n"); + + ret = ad7768_gpio_adev_init(st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int ad7768_runtime_suspend(struct device *dev) +{ + struct ad7768_state *st = dev_get_drvdata(dev); + + return ad7768_enter_sleep(st); +} + +static int ad7768_runtime_resume(struct device *dev) +{ + struct ad7768_state *st = dev_get_drvdata(dev); + int ret; + + ret = regmap_clear_bits(st->regmap, AD7768_REG_POWER_MODE, + AD7768_SLEEP_MODE_MSK); + if (ret) + return ret; + + /* + * The datasheet does not specify a wake-up time. Allow 20 ms for the + * ADC and digital clocks to restart. + */ + fsleep(20 * USEC_PER_MSEC); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ad7768_pm_ops, ad7768_runtime_suspend, + ad7768_runtime_resume, NULL); + +static const struct of_device_id ad7768_of_match[] = { + { .compatible = "adi,ad7768", .data = &ad7768_chip_info }, + { .compatible = "adi,ad7768-4", .data = &ad7768_4_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7768_of_match); + +static const struct spi_device_id ad7768_spi_id[] = { + { .name = "ad7768", .driver_data = (kernel_ulong_t)&ad7768_chip_info }, + { .name = "ad7768-4", .driver_data = (kernel_ulong_t)&ad7768_4_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7768_spi_id); + +static struct spi_driver ad7768_driver = { + .probe = ad7768_probe, + .driver = { + .name = "ad7768", + .of_match_table = ad7768_of_match, + .pm = pm_ptr(&ad7768_pm_ops), + }, + .id_table = ad7768_spi_id, +}; +module_spi_driver(ad7768_driver); + +MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>"); +MODULE_AUTHOR("Janani Sunil <janani.sunil@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD7768 ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c index 4fc0eb7e70a8..c4450f038898 100644 --- a/drivers/iio/adc/ade9000.c +++ b/drivers/iio/adc/ade9000.c @@ -242,14 +242,6 @@ #define ADE9000_LAST_PAGE_BIT BIT(15) #define ADE9000_MIDDLE_PAGE_BIT BIT(7) -/* - * Full scale Codes referred from Datasheet. Respective digital codes are - * produced when ADC inputs are at full scale. - */ -#define ADE9000_RMS_FULL_SCALE_CODES 52866837 -#define ADE9000_WATT_FULL_SCALE_CODES 20694066 -#define ADE9000_PCF_FULL_SCALE_CODES 74770000 - /* Phase and channel definitions */ #define ADE9000_PHASE_A_NR 0 #define ADE9000_PHASE_B_NR 1 @@ -290,7 +282,29 @@ enum ade9000_wfb_cfg { #define ADE9000_ADDR_ADJUST(addr, chan) \ (((chan) == 0 ? 0 : (chan) == 1 ? 2 : 4) << 4 | (addr)) +/** + * struct ade9000_chip_info - part-specific configuration + * @name: IIO device name reported to userspace + * @channels: channel specification for this part + * @num_channels: number of entries in @channels + * @rms_full_scale_codes: digital code produced at full-scale RMS input + * @watt_full_scale_codes: digital code produced at full-scale power input + * @pcf_full_scale_codes: digital code produced at full-scale xI_PCF/xV_PCF input + * + * The full-scale codes are taken from the respective datasheets and are used to + * derive the IIO scale of the raw measurement channels. + */ +struct ade9000_chip_info { + const char *name; + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int rms_full_scale_codes; + unsigned int watt_full_scale_codes; + unsigned int pcf_full_scale_codes; +}; + struct ade9000_state { + const struct ade9000_chip_info *info; struct completion reset_completion; struct mutex lock; /* Protects SPI transactions */ u8 wf_src; @@ -488,6 +502,7 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { .scan_index = -1 \ } +/* With swell/dip (sag) events - ADE9000 only */ #define ADE9000_ALTVOLTAGE_RMS_CHANNEL(num) { \ .type = IIO_ALTVOLTAGE, \ .channel = num, \ @@ -503,6 +518,20 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { .scan_index = -1 \ } +/* Without swell/dip events - parts lacking DIP_LVL/SWELL_LVL hardware */ +#define ADE9000_ALTVOLTAGE_RMS_CHANNEL_NO_EVENTS(num) { \ + .type = IIO_ALTVOLTAGE, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVRMS, num), \ + .channel2 = IIO_MOD_RMS, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_index = -1 \ +} + #define ADE9000_POWER_ACTIVE_CHANNEL(num) { \ .type = IIO_POWER, \ .channel = num, \ @@ -584,43 +613,82 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { .scan_index = -1 \ } -static const struct iio_chan_spec ade9000_channels[] = { - /* Phase A channels */ - ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_A_NR), - ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AWATTHR_LO), - ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AVAHR_LO), - ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AFVARHR_LO), - ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_A_NR), - /* Phase B channels */ - ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_B_NR), - ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BWATTHR_LO), - ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BVAHR_LO), - ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BFVARHR_LO), - ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_B_NR), - /* Phase C channels */ - ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_C_NR), - ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CWATTHR_LO), - ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CVAHR_LO), - ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CFVARHR_LO), - ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_C_NR), +/* + * Declare a full channel array. @altvoltage_rms picks the RMS voltage channel + * variant so parts without dip/swell hardware (e.g. ADE9078) omit those events. + */ +#define ADE9000_DECLARE_CHANNELS(_name, altvoltage_rms) \ + static const struct iio_chan_spec _name[] = { \ + /* Phase A channels */ \ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_A_NR), \ + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_A_NR), \ + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_A_NR), \ + altvoltage_rms(ADE9000_PHASE_A_NR), \ + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR), \ + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR), \ + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_A_NR), \ + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR, \ + ADE9000_REG_AWATTHR_LO), \ + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_A_NR, \ + ADE9000_REG_AVAHR_LO), \ + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR, \ + ADE9000_REG_AFVARHR_LO), \ + ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_A_NR), \ + /* Phase B channels */ \ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_B_NR), \ + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_B_NR), \ + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_B_NR), \ + altvoltage_rms(ADE9000_PHASE_B_NR), \ + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR), \ + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR), \ + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_B_NR), \ + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR, \ + ADE9000_REG_BWATTHR_LO), \ + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_B_NR, \ + ADE9000_REG_BVAHR_LO), \ + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR, \ + ADE9000_REG_BFVARHR_LO), \ + ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_B_NR), \ + /* Phase C channels */ \ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_C_NR), \ + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_C_NR), \ + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_C_NR), \ + altvoltage_rms(ADE9000_PHASE_C_NR), \ + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR), \ + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR), \ + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_C_NR), \ + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR, \ + ADE9000_REG_CWATTHR_LO), \ + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_C_NR, \ + ADE9000_REG_CVAHR_LO), \ + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR, \ + ADE9000_REG_CFVARHR_LO), \ + ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_C_NR), \ + } + +ADE9000_DECLARE_CHANNELS(ade9000_channels, ADE9000_ALTVOLTAGE_RMS_CHANNEL); +ADE9000_DECLARE_CHANNELS(ade9078_channels, ADE9000_ALTVOLTAGE_RMS_CHANNEL_NO_EVENTS); + +/* + * Full-scale codes referred from the respective datasheets. These are the + * digital codes produced when the ADC inputs are at full scale. + */ +static const struct ade9000_chip_info ade9000_chip_info = { + .name = "ade9000", + .channels = ade9000_channels, + .num_channels = ARRAY_SIZE(ade9000_channels), + .rms_full_scale_codes = 52866837, + .watt_full_scale_codes = 20694066, + .pcf_full_scale_codes = 74770000, +}; + +static const struct ade9000_chip_info ade9078_chip_info = { + .name = "ade9078", + .channels = ade9078_channels, + .num_channels = ARRAY_SIZE(ade9078_channels), + .rms_full_scale_codes = 52866837, + .watt_full_scale_codes = 20823646, + .pcf_full_scale_codes = 74680000, }; static const struct reg_sequence ade9000_initialization_sequence[] = { @@ -1064,7 +1132,7 @@ static int ade9000_read_raw(struct iio_dev *indio_dev, case ADE9000_REG_CI_PCF: case ADE9000_REG_CV_PCF: *val = 1; - *val2 = ADE9000_PCF_FULL_SCALE_CODES; + *val2 = st->info->pcf_full_scale_codes; return IIO_VAL_FRACTIONAL; case ADE9000_REG_AIRMS: case ADE9000_REG_AVRMS: @@ -1073,14 +1141,14 @@ static int ade9000_read_raw(struct iio_dev *indio_dev, case ADE9000_REG_CIRMS: case ADE9000_REG_CVRMS: *val = 1; - *val2 = ADE9000_RMS_FULL_SCALE_CODES; + *val2 = st->info->rms_full_scale_codes; return IIO_VAL_FRACTIONAL; default: return -EINVAL; } case IIO_POWER: *val = 1; - *val2 = ADE9000_WATT_FULL_SCALE_CODES; + *val2 = st->info->watt_full_scale_codes; return IIO_VAL_FRACTIONAL; default: break; @@ -1693,6 +1761,10 @@ static int ade9000_probe(struct spi_device *spi) st = iio_priv(indio_dev); + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODEV; + regmap = devm_regmap_init(dev, NULL, st, &ade9000_regmap_config); if (IS_ERR(regmap)) return dev_err_probe(dev, PTR_ERR(regmap), "Unable to allocate ADE9000 regmap"); @@ -1715,7 +1787,7 @@ static int ade9000_probe(struct spi_device *spi) if (ret) return ret; - indio_dev->name = "ade9000"; + indio_dev->name = st->info->name; indio_dev->info = &ade9000_info; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->setup_ops = &ade9000_buffer_ops; @@ -1738,8 +1810,8 @@ static int ade9000_probe(struct spi_device *spi) if (ret) return ret; - indio_dev->channels = ade9000_channels; - indio_dev->num_channels = ARRAY_SIZE(ade9000_channels); + indio_dev->channels = st->info->channels; + indio_dev->num_channels = st->info->num_channels; ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, &ade9000_buffer_ops); @@ -1770,13 +1842,15 @@ static int ade9000_probe(struct spi_device *spi) }; static const struct spi_device_id ade9000_id[] = { - { .name = "ade9000" }, + { .name = "ade9000", .driver_data = (kernel_ulong_t)&ade9000_chip_info }, + { .name = "ade9078", .driver_data = (kernel_ulong_t)&ade9078_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ade9000_id); static const struct of_device_id ade9000_of_match[] = { - { .compatible = "adi,ade9000" }, + { .compatible = "adi,ade9000", .data = &ade9000_chip_info }, + { .compatible = "adi,ade9078", .data = &ade9078_chip_info }, { } }; MODULE_DEVICE_TABLE(of, ade9000_of_match); diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index fc7cc92b35f0..8b7c45d51dbe 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -47,6 +47,9 @@ #define ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK BIT(1) #define ADI_AXI_ADC_REG_CNTRL_3 0x004c +/* Bit 8 is common to all AXI ADC cores; bits 7-0 are per-HDL-project. */ +#define ADI_AXI_ADC_CNTRL_3_CRC_EN_MASK BIT(8) + #define AXI_AD485X_CNTRL_3_OS_EN_MSK BIT(2) #define AXI_AD485X_CNTRL_3_PACKET_FORMAT_MSK GENMASK(1, 0) #define AXI_AD485X_PACKET_FORMAT_20BIT 0x0 @@ -596,6 +599,22 @@ static int axi_adc_reg_access(struct iio_backend *back, unsigned int reg, return regmap_write(st->regmap, reg, writeval); } +static int axi_adc_crc_enable(struct iio_backend *back) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + return regmap_set_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3, + ADI_AXI_ADC_CNTRL_3_CRC_EN_MASK); +} + +static int axi_adc_crc_disable(struct iio_backend *back) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + return regmap_clear_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3, + ADI_AXI_ADC_CNTRL_3_CRC_EN_MASK); +} + static const struct regmap_config axi_adc_regmap_config = { .val_bits = 32, .reg_bits = 32, @@ -644,6 +663,8 @@ static const struct iio_backend_ops adi_axi_adc_ops = { .num_lanes_set = axi_adc_num_lanes_set, .debugfs_reg_access = iio_backend_debugfs_ptr(axi_adc_reg_access), .debugfs_print_chan_status = iio_backend_debugfs_ptr(axi_adc_debugfs_print_chan_status), + .crc_enable = axi_adc_crc_enable, + .crc_disable = axi_adc_crc_disable, }; static const struct iio_backend_info adi_axi_adc_generic = { diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c index e8a5285bb6d4..78081cbfde5e 100644 --- a/drivers/iio/adc/at91-sama5d2_adc.c +++ b/drivers/iio/adc/at91-sama5d2_adc.c @@ -2016,6 +2016,8 @@ static void at91_adc_dma_init(struct at91_adc_state *st) unsigned int pages = DIV_ROUND_UP(AT91_HWFIFO_MAX_SIZE * sample_size * 2, PAGE_SIZE); + struct device *dma_dev; + if (st->dma_st.dma_chan) return; @@ -2026,7 +2028,8 @@ static void at91_adc_dma_init(struct at91_adc_state *st) goto dma_exit; } - st->dma_st.rx_buf = dma_alloc_coherent(st->dma_st.dma_chan->device->dev, + dma_dev = dmaengine_get_dma_device(st->dma_st.dma_chan); + st->dma_st.rx_buf = dma_alloc_coherent(dma_dev, pages * PAGE_SIZE, &st->dma_st.rx_dma_buf, GFP_KERNEL); @@ -2054,7 +2057,7 @@ static void at91_adc_dma_init(struct at91_adc_state *st) return; dma_free_area: - dma_free_coherent(st->dma_st.dma_chan->device->dev, pages * PAGE_SIZE, + dma_free_coherent(dma_dev, pages * PAGE_SIZE, st->dma_st.rx_buf, st->dma_st.rx_dma_buf); dma_chan_disable: dma_release_channel(st->dma_st.dma_chan); @@ -2078,7 +2081,7 @@ static void at91_adc_dma_disable(struct at91_adc_state *st) /* wait for all transactions to be terminated first*/ dmaengine_terminate_sync(st->dma_st.dma_chan); - dma_free_coherent(st->dma_st.dma_chan->device->dev, pages * PAGE_SIZE, + dma_free_coherent(dmaengine_get_dma_device(st->dma_st.dma_chan), pages * PAGE_SIZE, st->dma_st.rx_buf, st->dma_st.rx_dma_buf); dma_release_channel(st->dma_st.dma_chan); st->dma_st.dma_chan = NULL; @@ -2487,6 +2490,7 @@ static void at91_adc_remove(struct platform_device *pdev) pm_runtime_disable(st->dev); pm_runtime_set_suspended(st->dev); + pm_runtime_dont_use_autosuspend(st->dev); clk_disable_unprepare(st->per_clk); regulator_disable(st->vref); diff --git a/drivers/iio/adc/axiado_saradc.c b/drivers/iio/adc/axiado_saradc.c new file mode 100644 index 000000000000..699ee31616fc --- /dev/null +++ b/drivers/iio/adc/axiado_saradc.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2021-2026 Axiado Corporation + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <linux/iio/iio.h> + +/* Register offsets */ +#define AX_SARADC_GLOBAL_CTRL_REG 0x0004 +#define AX_SARADC_MANUAL_CTRL_REG 0x0008 +#define AX_SARADC_DOUT_REG 0x001C + +/* GLOBAL_CTRL register fields */ +#define AX_SARADC_GLOBAL_CTRL_CH_EN_MASK GENMASK(31, 16) +#define AX_SARADC_GLOBAL_CTRL_SAMPLE_MASK GENMASK(6, 5) +#define AX_SARADC_GLOBAL_CTRL_MODE_MASK GENMASK(4, 3) +#define AX_SARADC_GLOBAL_CTRL_PD BIT(2) +#define AX_SARADC_GLOBAL_CTRL_ENABLE BIT(0) + +/* GLOBAL_CTRL SAMPLE_MASK field value: 0 selects 16 samples. */ +#define AX_SARADC_GLOBAL_CTRL_SAMPLE_16 0 + +/* GLOBAL_CTRL MODE_MASK field value: 1 selects manual mode. */ +#define AX_SARADC_GLOBAL_CTRL_MODE_MANUAL 1 + +/* MANUAL_CTRL register fields */ +#define AX_SARADC_MANUAL_CTRL_ENABLE BIT(0) +#define AX_SARADC_MANUAL_CTRL_CH_SEL_MASK GENMASK(4, 1) + +#define AX_RESOLUTION_BITS 10 +#define AX_SARADC_CONV_CYCLES 13 +#define AX_SARADC_CONV_DELAY_MARGIN_US 10 + +struct axiado_saradc { + struct regmap *regmap; + struct mutex lock; /* Serializes ADC conversions. */ + unsigned long clk_rate; + int vref_uV; +}; + +/* + * Registers contain transient control, status, and conversion data, + * so accesses must always go directly to hardware. + */ +static const struct regmap_config axiado_saradc_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = AX_SARADC_DOUT_REG, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int axiado_saradc_conversion(struct axiado_saradc *info, + struct iio_chan_spec const *chan, int *val) +{ + unsigned long usecs; + unsigned int regval; + int ret; + + guard(mutex)(&info->lock); + + /* Select the channel to be used and trigger conversion. */ + ret = regmap_write(info->regmap, AX_SARADC_MANUAL_CTRL_REG, + AX_SARADC_MANUAL_CTRL_ENABLE | + FIELD_PREP(AX_SARADC_MANUAL_CTRL_CH_SEL_MASK, chan->channel)); + if (ret) + return ret; + + /* Hardware requires 13 conversion cycles at clk_rate. */ + usecs = DIV_ROUND_UP(AX_SARADC_CONV_CYCLES * USEC_PER_SEC, info->clk_rate); + fsleep(usecs + AX_SARADC_CONV_DELAY_MARGIN_US); + + ret = regmap_read(info->regmap, AX_SARADC_DOUT_REG, ®val); + + /* Best effort to stop manual conversion. */ + regmap_write(info->regmap, AX_SARADC_MANUAL_CTRL_REG, 0); + + if (ret) + return ret; + + *val = regval & GENMASK(AX_RESOLUTION_BITS - 1, 0); + + return 0; +} + +static int axiado_saradc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct axiado_saradc *info = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = axiado_saradc_conversion(info, chan, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = info->vref_uV / (MICRO / MILLI); + *val2 = AX_RESOLUTION_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static const struct iio_info axiado_saradc_iio_info = { + .read_raw = axiado_saradc_read_raw, +}; + +struct axiado_saradc_soc_data { + const char *name; + unsigned int num_channels; +}; + +static const struct axiado_saradc_soc_data ax3000_saradc_data = { + .name = "ax3000_saradc", + .num_channels = 16, +}; + +static const struct axiado_saradc_soc_data ax3005_saradc_data = { + .name = "ax3005_saradc", + .num_channels = 8, +}; + +#define AX_SARADC_CH(_index) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "adc" #_index, \ + } + +static const struct iio_chan_spec axiado_saradc_iio_channels[] = { + AX_SARADC_CH(0), + AX_SARADC_CH(1), + AX_SARADC_CH(2), + AX_SARADC_CH(3), + AX_SARADC_CH(4), + AX_SARADC_CH(5), + AX_SARADC_CH(6), + AX_SARADC_CH(7), + AX_SARADC_CH(8), + AX_SARADC_CH(9), + AX_SARADC_CH(10), + AX_SARADC_CH(11), + AX_SARADC_CH(12), + AX_SARADC_CH(13), + AX_SARADC_CH(14), + AX_SARADC_CH(15), +}; + +static void axiado_saradc_disable(void *map) +{ + regmap_write(map, AX_SARADC_GLOBAL_CTRL_REG, AX_SARADC_GLOBAL_CTRL_PD); +} + +static int axiado_saradc_probe(struct platform_device *pdev) +{ + const struct axiado_saradc_soc_data *soc_data; + struct device *dev = &pdev->dev; + struct axiado_saradc *info; + struct iio_dev *indio_dev; + void __iomem *regs; + struct regmap *map; + struct clk *clk; + u32 regval; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + map = devm_regmap_init_mmio(dev, regs, &axiado_saradc_regmap_config); + if (IS_ERR(map)) + return PTR_ERR(map); + info->regmap = map; + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + info->clk_rate = clk_get_rate(clk); + if (!info->clk_rate) + return dev_err_probe(dev, -EINVAL, "invalid clock rate\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get vref voltage\n"); + + info->vref_uV = ret; + + soc_data = device_get_match_data(dev); + if (!soc_data) + return dev_err_probe(dev, -ENODATA, "failed to get match data\n"); + + ret = devm_mutex_init(dev, &info->lock); + if (ret) + return ret; + + regval = FIELD_PREP(AX_SARADC_GLOBAL_CTRL_CH_EN_MASK, + GENMASK(soc_data->num_channels - 1, 0)) | + FIELD_PREP(AX_SARADC_GLOBAL_CTRL_SAMPLE_MASK, + AX_SARADC_GLOBAL_CTRL_SAMPLE_16) | + FIELD_PREP(AX_SARADC_GLOBAL_CTRL_MODE_MASK, + AX_SARADC_GLOBAL_CTRL_MODE_MANUAL) | + AX_SARADC_GLOBAL_CTRL_ENABLE; + + ret = regmap_write(map, AX_SARADC_GLOBAL_CTRL_REG, regval); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, axiado_saradc_disable, map); + if (ret) + return ret; + + indio_dev->name = soc_data->name; + indio_dev->info = &axiado_saradc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = axiado_saradc_iio_channels; + indio_dev->num_channels = soc_data->num_channels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id axiado_saradc_match[] = { + { + .compatible = "axiado,ax3000-saradc", + .data = &ax3000_saradc_data, + }, + { + .compatible = "axiado,ax3005-saradc", + .data = &ax3005_saradc_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, axiado_saradc_match); + +static struct platform_driver axiado_saradc_driver = { + .driver = { + .name = "axiado-saradc", + .of_match_table = axiado_saradc_match, + }, + .probe = axiado_saradc_probe, +}; +module_platform_driver(axiado_saradc_driver); + +MODULE_AUTHOR("Axiado Corporation"); +MODULE_DESCRIPTION("Axiado SARADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c index cf4738b16e62..ca81e0edd24d 100644 --- a/drivers/iio/adc/bcm_iproc_adc.c +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -3,14 +3,18 @@ * Copyright 2016 Broadcom */ -#include <linux/module.h> -#include <linux/io.h> +#include <linux/array_size.h> +#include <linux/bits.h> #include <linux/clk.h> -#include <linux/mfd/syscon.h> -#include <linux/regmap.h> +#include <linux/completion.h> #include <linux/delay.h> #include <linux/interrupt.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/mutex.h> #include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/types.h> #include <linux/iio/iio.h> @@ -106,7 +110,6 @@ do { \ struct iproc_adc_priv { struct regmap *regmap; - struct clk *adc_clk; struct mutex mutex; int irqno; int chan_val; @@ -448,6 +451,11 @@ static void iproc_adc_disable(struct iio_dev *indio_dev) } } +static void iproc_adc_disable_action(void *indio_dev) +{ + iproc_adc_disable(indio_dev); +} + static int iproc_adc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, @@ -506,35 +514,31 @@ static int iproc_adc_probe(struct platform_device *pdev) { struct iproc_adc_priv *adc_priv; struct iio_dev *indio_dev = NULL; + struct device *dev = &pdev->dev; + struct clk *adc_clk; int ret; - indio_dev = devm_iio_device_alloc(&pdev->dev, - sizeof(*adc_priv)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc_priv)); if (!indio_dev) return -ENOMEM; adc_priv = iio_priv(indio_dev); - platform_set_drvdata(pdev, indio_dev); - mutex_init(&adc_priv->mutex); + ret = devm_mutex_init(dev, &adc_priv->mutex); + if (ret) + return ret; init_completion(&adc_priv->completion); adc_priv->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "adc-syscon"); - if (IS_ERR(adc_priv->regmap)) { - dev_err(&pdev->dev, "failed to get handle for tsc syscon\n"); - ret = PTR_ERR(adc_priv->regmap); - return ret; - } + if (IS_ERR(adc_priv->regmap)) + return dev_err_probe(dev, PTR_ERR(adc_priv->regmap), + "failed to get handle for tsc syscon\n"); - adc_priv->adc_clk = devm_clk_get(&pdev->dev, "tsc_clk"); - if (IS_ERR(adc_priv->adc_clk)) { - dev_err(&pdev->dev, - "failed getting clock tsc_clk\n"); - ret = PTR_ERR(adc_priv->adc_clk); - return ret; - } + adc_clk = devm_clk_get_enabled(dev, "tsc_clk"); + if (IS_ERR(adc_clk)) + return dev_err_probe(dev, PTR_ERR(adc_clk), "failed to enable clock\n"); adc_priv->irqno = platform_get_irq(pdev, 0); if (adc_priv->irqno < 0) @@ -542,32 +546,23 @@ static int iproc_adc_probe(struct platform_device *pdev) ret = regmap_clear_bits(adc_priv->regmap, IPROC_REGCTL2, IPROC_ADC_AUXIN_SCAN_ENA); - if (ret) { - dev_err(&pdev->dev, "failed to write IPROC_REGCTL2 %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to write IPROC_REGCTL2\n"); - ret = devm_request_threaded_irq(&pdev->dev, adc_priv->irqno, + ret = devm_request_threaded_irq(dev, adc_priv->irqno, iproc_adc_interrupt_handler, iproc_adc_interrupt_thread, IRQF_SHARED, "iproc-adc", indio_dev); - if (ret) { - dev_err(&pdev->dev, "request_irq error %d\n", ret); + if (ret) return ret; - } - ret = clk_prepare_enable(adc_priv->adc_clk); - if (ret) { - dev_err(&pdev->dev, - "clk_prepare_enable failed %d\n", ret); + ret = iproc_adc_enable(indio_dev); + if (ret) return ret; - } - ret = iproc_adc_enable(indio_dev); - if (ret) { - dev_err(&pdev->dev, "failed to enable adc %d\n", ret); - goto err_adc_enable; - } + ret = devm_add_action_or_reset(dev, iproc_adc_disable_action, indio_dev); + if (ret) + return ret; indio_dev->name = "iproc-static-adc"; indio_dev->info = &iproc_adc_iio_info; @@ -575,30 +570,7 @@ static int iproc_adc_probe(struct platform_device *pdev) indio_dev->channels = iproc_adc_iio_channels; indio_dev->num_channels = ARRAY_SIZE(iproc_adc_iio_channels); - ret = iio_device_register(indio_dev); - if (ret) { - dev_err(&pdev->dev, "iio_device_register failed:err %d\n", ret); - goto err_clk; - } - - return 0; - -err_clk: - iproc_adc_disable(indio_dev); -err_adc_enable: - clk_disable_unprepare(adc_priv->adc_clk); - - return ret; -} - -static void iproc_adc_remove(struct platform_device *pdev) -{ - struct iio_dev *indio_dev = platform_get_drvdata(pdev); - struct iproc_adc_priv *adc_priv = iio_priv(indio_dev); - - iio_device_unregister(indio_dev); - iproc_adc_disable(indio_dev); - clk_disable_unprepare(adc_priv->adc_clk); + return devm_iio_device_register(dev, indio_dev); } static const struct of_device_id iproc_adc_of_match[] = { @@ -609,7 +581,6 @@ MODULE_DEVICE_TABLE(of, iproc_adc_of_match); static struct platform_driver iproc_adc_driver = { .probe = iproc_adc_probe, - .remove = iproc_adc_remove, .driver = { .name = "iproc-static-adc", .of_match_table = iproc_adc_of_match, diff --git a/drivers/iio/adc/ltc2497-core.c b/drivers/iio/adc/ltc2497-core.c index 2dc5c7044269..a6e34da83b81 100644 --- a/drivers/iio/adc/ltc2497-core.c +++ b/drivers/iio/adc/ltc2497-core.c @@ -9,9 +9,11 @@ #include <linux/delay.h> #include <linux/iio/iio.h> #include <linux/iio/driver.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/regulator/consumer.h> +#include <linux/units.h> #include "ltc2497.h" @@ -19,24 +21,54 @@ #define LTC2497_DIFF 0 #define LTC2497_SIGN BIT(3) -static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata) +/* + * Output-rate modes, indexed by ltc2497core_driverdata.speed_2x + * (0 = 1x, the power-on default; 1 = 2x, LTC2499 only). The advertised + * sampling_frequency and the conversion-time budget are two views of the same + * mode, so they are kept in lock-step here. + */ +static const int ltc2497core_samp_freq_avail[] = { + 6, 800000, /* 1x: ~6.8 Hz (1 / t_CONV_1 typ 146.9ms) */ + 13, 600000, /* 2x: ~13.6 Hz (1 / t_CONV_2 typ 73.6ms) */ +}; + +static const unsigned int ltc2497core_conv_time_ms_tbl[] = { + LTC2497_CONV_TIME_1X_MS, /* 1x */ + LTC2499_CONV_TIME_2X_MS, /* 2x */ +}; + +static unsigned int ltc2497core_conv_time_ms(struct ltc2497core_driverdata *ddata, + u8 address) +{ + /* + * SPD is ignored by the part during a temperature measurement: it + * always converts at 1x, so budget the 1x time regardless of the + * selected voltage-channel mode. + */ + if (address == LTC2497_TEMP_ADDR) + return ltc2497core_conv_time_ms_tbl[0]; + + return ltc2497core_conv_time_ms_tbl[ddata->speed_2x]; +} + +static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata, + unsigned int conv_time_ms) { s64 time_elapsed; time_elapsed = ktime_ms_delta(ktime_get(), ddata->time_prev); - if (time_elapsed < LTC2497_CONVERSION_TIME_MS) { + if (time_elapsed < conv_time_ms) { /* delay if conversion time not passed * since last read or write */ - if (msleep_interruptible( - LTC2497_CONVERSION_TIME_MS - time_elapsed)) + if (msleep_interruptible(conv_time_ms - time_elapsed)) return -ERESTARTSYS; return 0; } - if (time_elapsed - LTC2497_CONVERSION_TIME_MS <= 0) { + if (time_elapsed - conv_time_ms <= 0) { /* We're in automatic mode - * so the last reading is still not outdated */ @@ -48,9 +80,18 @@ static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata) static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, int *val) { + unsigned int conv_time_ms = ltc2497core_conv_time_ms(ddata, address); int ret; - ret = ltc2497core_wait_conv(ddata); + /* + * Wait for the conversion currently in flight, whose duration was fixed + * by the mode active when it was started (ddata->conv_time_prev). This + * can be longer than the freshly selected mode's time - e.g. a 1x + * conversion is still running when the first 2x read arrives after a + * sampling_frequency change - and reprogramming the device before it + * finishes would be NACKed (-EIO). + */ + ret = ltc2497core_wait_conv(ddata, ddata->conv_time_prev); if (ret < 0) return ret; @@ -60,7 +101,17 @@ static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, in return ret; ddata->addr_prev = address; - if (msleep_interruptible(LTC2497_CONVERSION_TIME_MS)) + /* + * The reprogram above starts a conversion in the new mode. + * Record its start time and duration before sleeping, so that if + * msleep_interruptible() is interrupted the conversion state is + * already consistent: the next retry then waits only the time + * remaining from the real start instead of from a stale + * time_prev, which would let it reprogram/read too early. + */ + ddata->time_prev = ktime_get(); + ddata->conv_time_prev = conv_time_ms; + if (msleep_interruptible(conv_time_ms)) return -ERESTARTSYS; } @@ -69,6 +120,8 @@ static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, in return ret; ddata->time_prev = ktime_get(); + /* The read above auto-starts the next conversion in the current mode. */ + ddata->conv_time_prev = conv_time_ms; return ret; } @@ -95,74 +148,209 @@ static int ltc2497core_read_raw(struct iio_dev *indio_dev, if (ret < 0) return ret; - *val = ret / 1000; - *val2 = ddata->chip_info->resolution + 1; + switch (chan->type) { + case IIO_TEMP: + /* + * raw is normalised to 2^(resolution + 1), i.e. + * raw = 2 * DATAOUT24, so the PTAT scale (datasheet + * Vref / 1570 per Kelvin) doubles its denominator and, + * in m°C, becomes Vref_uV / 3140000. + */ + *val = ret; + *val2 = 3140000; + return IIO_VAL_FRACTIONAL; + case IIO_VOLTAGE: + *val = ret / (MICRO / MILLI); + *val2 = ddata->chip_info->resolution + 1; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + ret = regulator_get_voltage(ddata->ref); + if (ret < 0) + return ret; + if (ret == 0) + return -EINVAL; + /* + * 0 °C == 273.15 K must map to raw + offset such that + * (raw + offset) * scale == 0 m°C, i.e. + * offset = -273150 / scale + * = -273150 * 3140000 / Vref_uV + * Computed in 64-bit to avoid overflow. + */ + *val = div_s64(ABSOLUTE_ZERO_MILLICELSIUS * 3140000LL, ret); + return IIO_VAL_INT; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * Only advertised on the voltage channels of parts with a speed + * mode; the sampling frequency is a property of the selected 1x/2x + * mode, not of an individual conversion. + */ + mutex_lock(&ddata->lock); + *val = ltc2497core_samp_freq_avail[ddata->speed_2x * 2]; + *val2 = ltc2497core_samp_freq_avail[ddata->speed_2x * 2 + 1]; + mutex_unlock(&ddata->lock); - return IIO_VAL_FRACTIONAL_LOG2; + return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; } } -#define LTC2497_CHAN(_chan, _addr, _ds_name) { \ +static int ltc2497core_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = ltc2497core_samp_freq_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(ltc2497core_samp_freq_avail); + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static int ltc2497core_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ltc2497core_driverdata *ddata = iio_priv(indio_dev); + unsigned int i; + bool speed_2x; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + /* Match the (val, val2) pair against the advertised rates. */ + for (i = 0; i < ARRAY_SIZE(ltc2497core_samp_freq_avail); i += 2) { + if (val == ltc2497core_samp_freq_avail[i] && + val2 == ltc2497core_samp_freq_avail[i + 1]) + break; + } + if (i == ARRAY_SIZE(ltc2497core_samp_freq_avail)) + return -EINVAL; + + speed_2x = i / 2; + + mutex_lock(&ddata->lock); + ddata->speed_2x = speed_2x; + /* + * The new speed only takes effect once the second command byte + * is reprogrammed, so force the next read to reprogram rather + * than reuse the value already latched for this address. + * LTC2497_CONFIG_DEFAULT is not a valid channel/temperature + * address, so it is a safe re-arm sentinel (as used at probe). + * + * A conversion started under the old speed may still be in + * flight; its own duration (conv_time_prev), not the new mode's, + * still gates the next reprogram, so the timing state is left + * untouched here. + */ + ddata->addr_prev = LTC2497_CONFIG_DEFAULT; + mutex_unlock(&ddata->lock); + + return 0; + + default: + return -EINVAL; + } +} + +#define LTC2497_CHAN(_chan, _addr, _ds_name, _extra_mask) { \ .type = IIO_VOLTAGE, \ .indexed = 1, \ .channel = (_chan), \ .address = (_addr | (_chan / 2) | ((_chan & 1) ? LTC2497_SIGN : 0)), \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | (_extra_mask), \ + .info_mask_shared_by_type_available = (_extra_mask), \ .datasheet_name = (_ds_name), \ } -#define LTC2497_CHAN_DIFF(_chan, _addr) { \ +#define LTC2497_CHAN_DIFF(_chan, _addr, _extra_mask) { \ .type = IIO_VOLTAGE, \ .indexed = 1, \ .channel = (_chan) * 2 + ((_addr) & LTC2497_SIGN ? 1 : 0), \ .channel2 = (_chan) * 2 + ((_addr) & LTC2497_SIGN ? 0 : 1),\ .address = (_addr | _chan), \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | (_extra_mask), \ + .info_mask_shared_by_type_available = (_extra_mask), \ .differential = 1, \ } -static const struct iio_chan_spec ltc2497core_channel[] = { - LTC2497_CHAN(0, LTC2497_SGL, "CH0"), - LTC2497_CHAN(1, LTC2497_SGL, "CH1"), - LTC2497_CHAN(2, LTC2497_SGL, "CH2"), - LTC2497_CHAN(3, LTC2497_SGL, "CH3"), - LTC2497_CHAN(4, LTC2497_SGL, "CH4"), - LTC2497_CHAN(5, LTC2497_SGL, "CH5"), - LTC2497_CHAN(6, LTC2497_SGL, "CH6"), - LTC2497_CHAN(7, LTC2497_SGL, "CH7"), - LTC2497_CHAN(8, LTC2497_SGL, "CH8"), - LTC2497_CHAN(9, LTC2497_SGL, "CH9"), - LTC2497_CHAN(10, LTC2497_SGL, "CH10"), - LTC2497_CHAN(11, LTC2497_SGL, "CH11"), - LTC2497_CHAN(12, LTC2497_SGL, "CH12"), - LTC2497_CHAN(13, LTC2497_SGL, "CH13"), - LTC2497_CHAN(14, LTC2497_SGL, "CH14"), - LTC2497_CHAN(15, LTC2497_SGL, "CH15"), - LTC2497_CHAN_DIFF(0, LTC2497_DIFF), - LTC2497_CHAN_DIFF(1, LTC2497_DIFF), - LTC2497_CHAN_DIFF(2, LTC2497_DIFF), - LTC2497_CHAN_DIFF(3, LTC2497_DIFF), - LTC2497_CHAN_DIFF(4, LTC2497_DIFF), - LTC2497_CHAN_DIFF(5, LTC2497_DIFF), - LTC2497_CHAN_DIFF(6, LTC2497_DIFF), - LTC2497_CHAN_DIFF(7, LTC2497_DIFF), - LTC2497_CHAN_DIFF(0, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(1, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(2, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(3, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(4, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(5, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(6, LTC2497_DIFF | LTC2497_SIGN), - LTC2497_CHAN_DIFF(7, LTC2497_DIFF | LTC2497_SIGN), -}; +/* + * SPD is ignored by the part during a temperature measurement, so the + * temperature channel carries no sampling-frequency attribute. + */ +#define LTC2497_TEMP_CHANNEL { \ + .type = IIO_TEMP, \ + .address = LTC2497_TEMP_ADDR, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ +} + +/* + * Parts with a speed mode advertise sampling_frequency on their voltage + * channels; the two tables are otherwise identical. + */ +#define LTC2497_DEFINE_CHANNELS(_name, _extra_mask) \ +static const struct iio_chan_spec _name[] = { \ + LTC2497_CHAN(0, LTC2497_SGL, "CH0", _extra_mask), \ + LTC2497_CHAN(1, LTC2497_SGL, "CH1", _extra_mask), \ + LTC2497_CHAN(2, LTC2497_SGL, "CH2", _extra_mask), \ + LTC2497_CHAN(3, LTC2497_SGL, "CH3", _extra_mask), \ + LTC2497_CHAN(4, LTC2497_SGL, "CH4", _extra_mask), \ + LTC2497_CHAN(5, LTC2497_SGL, "CH5", _extra_mask), \ + LTC2497_CHAN(6, LTC2497_SGL, "CH6", _extra_mask), \ + LTC2497_CHAN(7, LTC2497_SGL, "CH7", _extra_mask), \ + LTC2497_CHAN(8, LTC2497_SGL, "CH8", _extra_mask), \ + LTC2497_CHAN(9, LTC2497_SGL, "CH9", _extra_mask), \ + LTC2497_CHAN(10, LTC2497_SGL, "CH10", _extra_mask), \ + LTC2497_CHAN(11, LTC2497_SGL, "CH11", _extra_mask), \ + LTC2497_CHAN(12, LTC2497_SGL, "CH12", _extra_mask), \ + LTC2497_CHAN(13, LTC2497_SGL, "CH13", _extra_mask), \ + LTC2497_CHAN(14, LTC2497_SGL, "CH14", _extra_mask), \ + LTC2497_CHAN(15, LTC2497_SGL, "CH15", _extra_mask), \ + LTC2497_CHAN_DIFF(0, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(1, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(2, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(3, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(4, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(5, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(6, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(7, LTC2497_DIFF, _extra_mask), \ + LTC2497_CHAN_DIFF(0, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(1, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(2, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(3, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(4, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(5, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(6, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_CHAN_DIFF(7, LTC2497_DIFF | LTC2497_SIGN, _extra_mask), \ + LTC2497_TEMP_CHANNEL, \ +} + +LTC2497_DEFINE_CHANNELS(ltc2497core_channel, 0); +LTC2497_DEFINE_CHANNELS(ltc2497core_channel_samp_freq, BIT(IIO_CHAN_INFO_SAMP_FREQ)); static const struct iio_info ltc2497core_info = { .read_raw = ltc2497core_read_raw, + .read_avail = ltc2497core_read_avail, + .write_raw = ltc2497core_write_raw, }; int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev) @@ -182,8 +370,15 @@ int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev) indio_dev->info = <c2497core_info; indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = ltc2497core_channel; - indio_dev->num_channels = ARRAY_SIZE(ltc2497core_channel); + if (ddata->chip_info->has_speed_mode) + indio_dev->channels = ltc2497core_channel_samp_freq; + else + indio_dev->channels = ltc2497core_channel; + /* Only the ltc2499 has a temperature channel; it is the last entry. */ + if (ddata->chip_info->has_temp) + indio_dev->num_channels = ARRAY_SIZE(ltc2497core_channel); + else + indio_dev->num_channels = ARRAY_SIZE(ltc2497core_channel) - 1; ret = ddata->result_and_measure(ddata, LTC2497_CONFIG_DEFAULT, NULL); if (ret < 0) @@ -209,6 +404,8 @@ int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev) ddata->addr_prev = LTC2497_CONFIG_DEFAULT; ddata->time_prev = ktime_get(); + /* Power-on default mode is 1x; a conversion is already in flight. */ + ddata->conv_time_prev = LTC2497_CONV_TIME_1X_MS; mutex_init(&ddata->lock); diff --git a/drivers/iio/adc/ltc2497.c b/drivers/iio/adc/ltc2497.c index c1668b5a351e..15458d4e21a1 100644 --- a/drivers/iio/adc/ltc2497.c +++ b/drivers/iio/adc/ltc2497.c @@ -84,6 +84,46 @@ static int ltc2497_result_and_measure(struct ltc2497core_driverdata *ddata, return 0; } + /* + * Parts with a second config byte (LTC2499: internal PTAT sensor and/or + * the 2x speed mode) latch their converter configuration from that byte + * and only re-evaluate it when EN2 is set; a single byte, or a second + * byte with EN2 = 0, means "keep previous". A one-byte channel select + * therefore cannot pull the device back out of temperature mode, so a + * voltage read after a temperature read would keep returning the PTAT + * result. Always drive the second byte with EN2 set on these parts: + * - temperature read: IM = 1 (SPD is ignored by the part in + * temperature mode and is left 0 here); + * - voltage read: IM = 0 (external input), plus SPD when 2x is + * selected. + * FA = FB = 0 keeps the power-on simultaneous 50/60Hz rejection, whose + * worst-case conversion time the driver's wait already covers. + * + * The byte could be skipped while the latched configuration is already + * the one wanted; it is sent on every conversion for simplicity. + */ + if (ddata->chip_info->has_temp || ddata->chip_info->has_speed_mode) { + u8 cmd[2]; + + if (address == LTC2497_TEMP_ADDR) { + cmd[0] = LTC2497_ENABLE | LTC2497_CONFIG_DEFAULT; + cmd[1] = LTC2499_EN2 | LTC2499_IM; + } else { + cmd[0] = LTC2497_ENABLE | address; + cmd[1] = LTC2499_EN2; + if (ddata->speed_2x) + cmd[1] |= LTC2499_SPD; + } + + ret = i2c_master_send(st->client, cmd, sizeof(cmd)); + if (ret < 0) { + dev_err(&st->client->dev, "i2c transfer failed: %pe\n", + ERR_PTR(ret)); + return ret; + } + return 0; + } + ret = i2c_smbus_write_byte(st->client, LTC2497_ENABLE | address); if (ret) @@ -137,6 +177,8 @@ static const struct ltc2497_chip_info ltc2497_info[] = { [TYPE_LTC2499] = { .resolution = 24, .name = "ltc2499", + .has_temp = true, + .has_speed_mode = true, }, }; diff --git a/drivers/iio/adc/ltc2497.h b/drivers/iio/adc/ltc2497.h index 64e81c95a3dd..cee9a2f50b82 100644 --- a/drivers/iio/adc/ltc2497.h +++ b/drivers/iio/adc/ltc2497.h @@ -2,11 +2,35 @@ #define LTC2497_ENABLE 0xA0 #define LTC2497_CONFIG_DEFAULT LTC2497_ENABLE -#define LTC2497_CONVERSION_TIME_MS 150ULL + +/* + * Conversion-time bounds used to gate reads. Each value is the datasheet + * t_CONV maximum, rounded UP to the next whole millisecond. + * + * The 2x mode (LTC2499_SPD, LTC2499 only) disables the offset auto-calibration + * to roughly double the output rate; adding the 2x wait time is what makes the + * SPD control actually faster. + */ +#define LTC2497_CONV_TIME_1X_MS 150ULL /* ceil(t_CONV_1 simult. max 149.9) */ +#define LTC2499_CONV_TIME_2X_MS 76ULL /* ceil(t_CONV_2 simult. max 75.1) */ + +/* + * Sentinel passed as `address` to result_and_measure() to request a + * temperature conversion instead of a voltage channel. Valid channel + * addresses fit in 5 bits (0x00–0x1F), so 0xFF is unambiguous. + */ +#define LTC2497_TEMP_ADDR 0xFF + +/* Second config-byte bits (LTC2499 / LTC2493 only) */ +#define LTC2499_EN2 BIT(7) /* enable second config byte */ +#define LTC2499_IM BIT(6) /* 1 = measure internal temp sensor */ +#define LTC2499_SPD BIT(3) /* 1 = 2x output rate (offset cal off) */ struct ltc2497_chip_info { - u32 resolution; const char *name; + u32 resolution; + bool has_temp; + bool has_speed_mode; /* SPD bit in the 2nd config byte (LTC2499/LTC2493) */ }; struct ltc2497core_driverdata { @@ -16,6 +40,9 @@ struct ltc2497core_driverdata { struct mutex lock; const struct ltc2497_chip_info *chip_info; u8 addr_prev; + bool speed_2x; /* SPD: false = 1x (default), true = 2x */ + /* Conversion time (ms) of the conversion currently in flight. */ + unsigned int conv_time_prev; int (*result_and_measure)(struct ltc2497core_driverdata *ddata, u8 address, int *val); }; diff --git a/drivers/iio/adc/max11205.c b/drivers/iio/adc/max11205.c index 63b6561dcf47..53ac2f4f383e 100644 --- a/drivers/iio/adc/max11205.c +++ b/drivers/iio/adc/max11205.c @@ -117,6 +117,8 @@ static int max11205_probe(struct spi_device *spi) ad_sd_init(&st->sd, indio_dev, spi, &max11205_sigma_delta_info); st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODATA; indio_dev->name = st->chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; diff --git a/drivers/iio/adc/max14001.c b/drivers/iio/adc/max14001.c index 58adeac62dde..7694fbb6a9fd 100644 --- a/drivers/iio/adc/max14001.c +++ b/drivers/iio/adc/max14001.c @@ -366,6 +366,7 @@ static const struct spi_device_id max14001_id_table[] = { { .name = "max14002", .driver_data = (kernel_ulong_t)&max14002_chip_info }, { } }; +MODULE_DEVICE_TABLE(spi, max14001_id_table); static const struct of_device_id max14001_of_match[] = { { .compatible = "adi,max14001", .data = &max14001_chip_info }, diff --git a/drivers/iio/adc/max40080.c b/drivers/iio/adc/max40080.c new file mode 100644 index 000000000000..c6946eb6ff5b --- /dev/null +++ b/drivers/iio/adc/max40080.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MAX40080 Digital Current-Sense Amplifier driver + * + * Copyright 2026 Analog Devices, Inc. + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX40080.pdf + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/i2c.h> +#include <linux/iopoll.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/property.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <asm/byteorder.h> + +#include <linux/iio/iio.h> + +#define MAX40080_REG_CFG 0x00 +#define MAX40080_CFG_MODE_MSK GENMASK(2, 0) +#define MAX40080_CFG_PEC_EN_MSK BIT(5) +#define MAX40080_CFG_RANGE_MSK BIT(6) +#define MAX40080_CFG_FILTER_MSK GENMASK(14, 12) + +#define MAX40080_REG_FIFO_CFG 0x0A +#define MAX40080_FIFO_CFG_STORE_IV_MSK GENMASK(1, 0) + +#define MAX40080_REG_IV 0x10 +/* Current is a 13-bit two's-complement value (magnitude + sign bit). */ +#define MAX40080_IV_I_MSK GENMASK(12, 0) +#define MAX40080_IV_I_SIGN_BIT 12 +#define MAX40080_IV_V_MAG_MSK GENMASK(27, 16) +#define MAX40080_IV_VALID_MSK BIT(31) + +/* CFG.mode field values. */ +#define MAX40080_CFG_MODE_STDBY 0x00 +#define MAX40080_CFG_MODE_SINGLE 0x02 + +/* CFG.range field values. */ +#define MAX40080_CFG_RANGE_50mV 0 +#define MAX40080_CFG_RANGE_10mV 1 + +/* FIFO_CFG.store_iv field values. */ +#define MAX40080_FIFO_CFG_STORE_IV 0x02 + +#define MAX40080_ADC_RES_BITS 12 +#define MAX40080_INTER_VREF_mV 1250 +#define MAX40080_V_BUFF_GAIN 30 +#define MAX40080_CSA_50mV_GAIN 25 +#define MAX40080_CSA_10mV_GAIN 125 + +/* + * The RANGE field (CFG bit 6) selects one of two current-sense full-scale + * ranges (the MAX40080 supports exactly two: +/-50 mV and +/-10 mV). Indexed + * by the CFG.range field value. + */ +static const int max40080_csa_gain[] = { + [MAX40080_CFG_RANGE_50mV] = MAX40080_CSA_50mV_GAIN, + [MAX40080_CFG_RANGE_10mV] = MAX40080_CSA_10mV_GAIN, +}; + +struct max40080_state { + struct i2c_client *client; + /* Serializes read-modify-write access to the CFG register. */ + struct mutex lock; + u32 shunt_resistor_uOhm; + /* Cached configuration: the selected RANGE index and oversampling ratio. */ + unsigned int range; + int oversampling_ratio; + /* + * Precomputed current scale (mA per code) for each RANGE setting, as + * {integer, nano} pairs for IIO_VAL_INT_PLUS_NANO. The range is + * selected by writing the corresponding scale. + */ + int current_scale[2][2]; +}; + +static const int max40080_oversampling_avail[] = { 1, 8, 16, 32, 64, 128 }; + +static int max40080_update_bits(struct max40080_state *st, u8 reg, + u16 mask, u16 val) +{ + int tmp; + + tmp = i2c_smbus_read_word_data(st->client, reg); + if (tmp < 0) + return tmp; + + tmp = (tmp & ~mask) | (val & mask); + + return i2c_smbus_write_word_data(st->client, reg, tmp); +} + +/* + * In single-measurement mode the device sits idle until it receives an SMBus + * Quick Command, then performs exactly one current and one voltage conversion + * and returns to idle. Triggering on demand this way (rather than running the + * FIFO continuously in active mode) means each read returns a fresh, coherent + * current/voltage pair instead of the oldest queued FIFO entry. + */ +static int max40080_trigger_measurement(struct max40080_state *st) +{ + return i2c_smbus_xfer(st->client->adapter, st->client->addr, + st->client->flags, I2C_SMBUS_WRITE, 0, + I2C_SMBUS_QUICK, NULL); +} + +/* + * A single measurement holds the matched current/voltage pair in one 32-bit + * word (MAX40080_REG_IV). Reading all four bytes in one transaction returns + * both from the same conversion; reading the separate current (0x0C) and + * voltage (0x0E) registers would decorrelate the two channels. + * + * Unlike the word accesses used elsewhere, this is a plain I2C block read: the + * SMBus layer does not append or verify a PEC byte for it even when PEC is + * otherwise enabled for the device, so this transfer is not PEC protected. + */ +static int max40080_read_iv_once(struct max40080_state *st, u32 *iv) +{ + __le32 buf = 0; + int ret; + + ret = i2c_smbus_read_i2c_block_data(st->client, MAX40080_REG_IV, + sizeof(buf), (u8 *)&buf); + if (ret < 0) + return ret; + + *iv = le32_to_cpu(buf); + + return 0; +} + +static int max40080_read_iv(struct max40080_state *st, u32 *iv) +{ + u32 tmp = 0; + int ret, io_ret; + + guard(mutex)(&st->lock); + + ret = max40080_trigger_measurement(st); + if (ret < 0) + return ret; + + /* + * Wait for the conversion to complete by polling the FIFO valid bit + * (or bail out on an I2C error). Polling the device's own status makes + * this independent of the actual conversion time, which varies with the + * oversampling ratio and the bus speed. The timeout is only a safety + * ceiling: the worst case is the maximum 128x averaging on both the + * current and voltage channels at the slowest 15 ksps base rate plus + * the inter-channel switching time, i.e. roughly 20 ms; 50 ms leaves + * ample margin. + */ + ret = read_poll_timeout(max40080_read_iv_once, io_ret, + io_ret || (tmp & MAX40080_IV_VALID_MSK), + 500, 50 * USEC_PER_MSEC, false, st, &tmp); + if (io_ret) + return io_ret; + + /* + * Propagate the last-read value even on timeout so the caller can + * inspect it for debugging. + */ + *iv = tmp; + + return ret; +} + +static int max40080_get_current(struct max40080_state *st, int *val) +{ + u32 iv; + int ret; + + ret = max40080_read_iv(st, &iv); + if (ret) + return ret; + + *val = sign_extend32(FIELD_GET(MAX40080_IV_I_MSK, iv), + MAX40080_IV_I_SIGN_BIT); + + return 0; +} + +static int max40080_get_voltage(struct max40080_state *st, int *val) +{ + u32 iv; + int ret; + + ret = max40080_read_iv(st, &iv); + if (ret) + return ret; + + *val = FIELD_GET(MAX40080_IV_V_MAG_MSK, iv); + + return 0; +} + +static int max40080_set_range(struct max40080_state *st, unsigned int range) +{ + int ret; + + ret = max40080_update_bits(st, MAX40080_REG_CFG, MAX40080_CFG_RANGE_MSK, + FIELD_PREP(MAX40080_CFG_RANGE_MSK, range)); + if (ret) + return ret; + + WRITE_ONCE(st->range, range); + + return 0; +} + +/* + * Precompute the current scale (mA per code) for each RANGE setting as + * {integer, nano} pairs. The shunt drop for a full-scale code is + * Vref[mV] / (BIT(ADC_RES_BITS) * gain) + * and current = Vshunt / Rshunt, so with Rshunt in micro-ohms the scale in + * mA/code is + * Vref[mV] * NANO * MICRO / (BIT(ADC_RES_BITS) * gain * Rshunt[uOhm]) + * expressed as an integer part plus a nano fractional part. + */ +static void max40080_calc_current_scale(struct max40080_state *st) +{ + u64 numerator, denominator; + u32 rem; + + for (unsigned int i = 0; i < ARRAY_SIZE(max40080_csa_gain); i++) { + numerator = (u64)MAX40080_INTER_VREF_mV * NANO * MICRO; + denominator = BIT_ULL(MAX40080_ADC_RES_BITS) * max40080_csa_gain[i] * + st->shunt_resistor_uOhm; + numerator = div64_u64(numerator, denominator); + st->current_scale[i][0] = div_u64_rem(numerator, NANO, &rem); + st->current_scale[i][1] = rem; + } +} + +/* + * max40080_oversampling_avail[] is ordered so that its index is the FILTER + * field value (index 0 = no averaging, index 1 = 8x, ...). Return that index + * for an exact match, or -EINVAL for a value that is not on the list. + */ +static int max40080_oversampling_to_filter(int val) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(max40080_oversampling_avail); i++) { + if (max40080_oversampling_avail[i] == val) + return i; + } + + return -EINVAL; +} + +static int max40080_set_oversampling_ratio(struct max40080_state *st, int val) +{ + int filter; + int ret; + + filter = max40080_oversampling_to_filter(val); + if (filter < 0) + return filter; + + ret = max40080_update_bits(st, MAX40080_REG_CFG, MAX40080_CFG_FILTER_MSK, + FIELD_PREP(MAX40080_CFG_FILTER_MSK, filter)); + if (ret) + return ret; + + WRITE_ONCE(st->oversampling_ratio, val); + + return 0; +} + +static int max40080_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max40080_state *st = iio_priv(indio_dev); + unsigned int range; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_CURRENT) { + ret = max40080_get_current(st, val); + if (ret) + return ret; + } else if (chan->type == IIO_VOLTAGE) { + ret = max40080_get_voltage(st, val); + if (ret) + return ret; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_CURRENT) { + /* + * The selectable current-sense range is exposed through + * scale: each RANGE setting has its own precomputed + * mA-per-code value. Userspace picks the range by + * writing the matching scale. + * + * Use READ_ONCE to ensure the compiler reads st->range + * exactly once, so val and val2 come from the same + * setting even if a concurrent write changes st->range. + */ + range = READ_ONCE(st->range); + *val = st->current_scale[range][0]; + *val2 = st->current_scale[range][1]; + return IIO_VAL_INT_PLUS_NANO; + } + /* voltage[mV] = raw * Vref[mV] * buffer_gain / BIT(ADC_RES_BITS) */ + *val = MAX40080_INTER_VREF_mV * MAX40080_V_BUFF_GAIN; + *val2 = MAX40080_ADC_RES_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = READ_ONCE(st->oversampling_ratio); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int max40080_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max40080_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + /* Only the current channel has a selectable range/scale. */ + if (chan->type != IIO_CURRENT) + return -EINVAL; + + for (unsigned int i = 0; i < ARRAY_SIZE(max40080_csa_gain); i++) { + if (val == st->current_scale[i][0] && + val2 == st->current_scale[i][1]) + return max40080_set_range(st, i); + } + + return -EINVAL; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return max40080_set_oversampling_ratio(st, val); + default: + return -EINVAL; + } +} + +static int max40080_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT; + } +} + +static int max40080_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct max40080_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_CURRENT) + return -EINVAL; + + *vals = (int *)st->current_scale; + *length = ARRAY_SIZE(max40080_csa_gain) * 2; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = max40080_oversampling_avail; + *length = ARRAY_SIZE(max40080_oversampling_avail); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int max40080_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int write_val, unsigned int *read_val) +{ + struct max40080_state *st = iio_priv(indio_dev); + int val; + + if (read_val) { + val = i2c_smbus_read_word_data(st->client, reg); + if (val < 0) + return val; + + *read_val = val; + + return 0; + } + + return i2c_smbus_write_word_data(st->client, reg, write_val); +} + +static const struct iio_info max40080_info = { + .read_raw = max40080_read_raw, + .write_raw = max40080_write_raw, + .write_raw_get_fmt = max40080_write_raw_get_fmt, + .read_avail = max40080_read_avail, + .debugfs_reg_access = &max40080_reg_access, +}; + +static const struct iio_chan_spec max40080_channels[] = { + { + .type = IIO_CURRENT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, +}; + +/* + * Configure the device from the cached state. The device powers up in standby + * with PEC enabled (CFG POR = 0x0060), so PEC is kept enabled throughout. + */ +static int max40080_init(struct max40080_state *st) +{ + u16 fifo_cfg, cfg; + int ret, filter; + + filter = max40080_oversampling_to_filter(st->oversampling_ratio); + if (filter < 0) + return filter; + + /* + * Put the device in standby before (re)configuring the FIFO: the FIFO + * configuration register can only be written while the device is not + * converting. + */ + cfg = FIELD_PREP(MAX40080_CFG_MODE_MSK, MAX40080_CFG_MODE_STDBY) | + FIELD_PREP(MAX40080_CFG_PEC_EN_MSK, 1); + ret = i2c_smbus_write_word_data(st->client, MAX40080_REG_CFG, cfg); + if (ret) + return ret; + + /* Store a matched current+voltage pair per conversion. */ + fifo_cfg = FIELD_PREP(MAX40080_FIFO_CFG_STORE_IV_MSK, MAX40080_FIFO_CFG_STORE_IV); + ret = i2c_smbus_write_word_data(st->client, MAX40080_REG_FIFO_CFG, + fifo_cfg); + if (ret) + return ret; + + /* + * Use single-measurement mode: the device stays idle and converts once + * per SMBus Quick Command (see max40080_trigger_measurement()), so each + * read returns a fresh sample rather than a queued FIFO entry. + */ + cfg = FIELD_PREP(MAX40080_CFG_MODE_MSK, MAX40080_CFG_MODE_SINGLE) | + FIELD_PREP(MAX40080_CFG_PEC_EN_MSK, 1) | + FIELD_PREP(MAX40080_CFG_RANGE_MSK, st->range) | + FIELD_PREP(MAX40080_CFG_FILTER_MSK, filter); + + return i2c_smbus_write_word_data(st->client, MAX40080_REG_CFG, cfg); +} + +static int max40080_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const char *propname; + struct iio_dev *indio_dev; + struct max40080_state *st; + int ret; + + /* + * The device powers up with PEC enabled (CFG POR = 0x0060) and rejects + * unprotected transactions, so PEC support is mandatory, along with + * word access, the I2C block read used for the current/voltage pair, + * and the Quick Command used to trigger a conversion. + */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK | + I2C_FUNC_SMBUS_QUICK | + I2C_FUNC_SMBUS_PEC)) + return -EOPNOTSUPP; + + client->flags |= I2C_CLIENT_PEC; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->client = client; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + propname = "shunt-resistor-micro-ohms"; + ret = device_property_read_u32(dev, propname, &st->shunt_resistor_uOhm); + if (ret) + return dev_err_probe(dev, ret, "can't read %s\n", propname); + if (!st->shunt_resistor_uOhm) + return dev_err_probe(dev, -EINVAL, "%s must be non-zero\n", propname); + + max40080_calc_current_scale(st); + + /* Defaults: 50 mV range, no averaging. */ + st->range = MAX40080_CFG_RANGE_50mV; + st->oversampling_ratio = 1; + + indio_dev->name = "max40080"; + indio_dev->info = &max40080_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = max40080_channels; + indio_dev->num_channels = ARRAY_SIZE(max40080_channels); + + ret = max40080_init(st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id max40080_i2c_ids[] = { + { .name = "max40080" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max40080_i2c_ids); + +static const struct of_device_id max40080_of_match[] = { + { .compatible = "adi,max40080" }, + { } +}; +MODULE_DEVICE_TABLE(of, max40080_of_match); + +static struct i2c_driver max40080_driver = { + .driver = { + .name = "max40080", + .of_match_table = max40080_of_match, + }, + .probe = max40080_probe, + .id_table = max40080_i2c_ids, +}; +module_i2c_driver(max40080_driver); + +MODULE_AUTHOR("Ciprian Hegbeli <ciprian.hegbeli@analog.com>"); +MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>"); +MODULE_DESCRIPTION("Analog Devices MAX40080 current-sense amplifier driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/mcp3911.c b/drivers/iio/adc/mcp3911.c index 797424f59f74..90167930f913 100644 --- a/drivers/iio/adc/mcp3911.c +++ b/drivers/iio/adc/mcp3911.c @@ -721,6 +721,8 @@ static int mcp3911_probe(struct spi_device *spi) adc = iio_priv(indio_dev); adc->spi = spi; adc->chip = spi_get_device_match_data(spi); + if (!adc->chip) + return -ENODATA; ret = devm_regulator_get_enable_read_voltage(dev, "vref"); if (ret < 0 && ret != -ENODEV) diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c index 000e39ca5c62..2b003fd5afeb 100644 --- a/drivers/iio/adc/meson_saradc.c +++ b/drivers/iio/adc/meson_saradc.c @@ -739,7 +739,7 @@ static int meson_sar_adc_clk_init(struct iio_dev *indio_dev, { struct meson_sar_adc_priv *priv = iio_priv(indio_dev); struct device *dev = indio_dev->dev.parent; - struct clk_init_data init; + struct clk_init_data init = { }; const char *clk_parents[1]; init.name = devm_kasprintf(dev, GFP_KERNEL, "%s#adc_div", dev_name(dev)); diff --git a/drivers/iio/adc/nxp-sar-adc.c b/drivers/iio/adc/nxp-sar-adc.c index 894d3211b283..2f716a2f97b8 100644 --- a/drivers/iio/adc/nxp-sar-adc.c +++ b/drivers/iio/adc/nxp-sar-adc.c @@ -604,7 +604,7 @@ static void nxp_sar_adc_dma_cb(void *data) dma_buf = &info->dma_buf; dma_samples = (u32 *)dma_buf->buf; - dev_dma = info->dma_chan->device->dev; + dev_dma = dmaengine_get_dma_device(info->dma_chan); /* * DMA in some corner cases might have already be charged for diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c index de59dc27c440..5e901ab25c8e 100644 --- a/drivers/iio/adc/pac1934.c +++ b/drivers/iio/adc/pac1934.c @@ -19,6 +19,7 @@ #include <linux/i2c.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> +#include <linux/kstrtox.h> #include <linux/unaligned.h> /* @@ -494,11 +495,13 @@ static ssize_t pac1934_shunt_value_store(struct device *dev, struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct pac1934_chip_info *info = iio_priv(indio_dev); struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); - int sh_val; + unsigned int sh_val; + int ret; - if (kstrtouint(buf, 10, &sh_val)) { + ret = kstrtouint(buf, 10, &sh_val); + if (ret) { dev_err(dev, "Shunt value is not valid\n"); - return -EINVAL; + return ret; } scoped_guard(mutex, &info->lock) diff --git a/drivers/iio/adc/qcom-spmi-iadc.c b/drivers/iio/adc/qcom-spmi-iadc.c index 0ec3a0c4b1de..196525ba0b4b 100644 --- a/drivers/iio/adc/qcom-spmi-iadc.c +++ b/drivers/iio/adc/qcom-spmi-iadc.c @@ -16,6 +16,7 @@ #include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/slab.h> +#include <linux/spmi.h> /* IADC register and bit definition */ #define IADC_REVISION2 0x1 @@ -94,7 +95,6 @@ * struct iadc_chip - IADC Current ADC device structure. * @regmap: regmap for register read/write. * @dev: This device pointer. - * @base: base offset for the ADC peripheral. * @rsense: Values of the internal and external sense resister in micro Ohms. * @poll_eoc: Poll for end of conversion instead of waiting for IRQ. * @offset: Raw offset values for the internal and external channels. @@ -105,7 +105,6 @@ struct iadc_chip { struct regmap *regmap; struct device *dev; - u16 base; bool poll_eoc; u32 rsense[2]; u16 offset[2]; @@ -114,77 +113,59 @@ struct iadc_chip { struct completion complete; }; -static int iadc_read(struct iadc_chip *iadc, u16 offset, u8 *data) -{ - unsigned int val; - int ret; - - ret = regmap_read(iadc->regmap, iadc->base + offset, &val); - if (ret < 0) - return ret; - - *data = val; - return 0; -} - -static int iadc_write(struct iadc_chip *iadc, u16 offset, u8 data) -{ - return regmap_write(iadc->regmap, iadc->base + offset, data); -} - static int iadc_reset(struct iadc_chip *iadc) { - u8 data; + u32 data; int ret; - ret = iadc_write(iadc, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA); + ret = regmap_write(iadc->regmap, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA); if (ret < 0) return ret; - ret = iadc_read(iadc, IADC_PERH_RESET_CTL3, &data); + ret = regmap_read(iadc->regmap, IADC_PERH_RESET_CTL3, &data); if (ret < 0) return ret; - ret = iadc_write(iadc, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA); + ret = regmap_write(iadc->regmap, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA); if (ret < 0) return ret; data |= IADC_FOLLOW_WARM_RB; - return iadc_write(iadc, IADC_PERH_RESET_CTL3, data); + return regmap_write(iadc->regmap, IADC_PERH_RESET_CTL3, data); } static int iadc_set_state(struct iadc_chip *iadc, bool state) { - return iadc_write(iadc, IADC_EN_CTL1, state ? IADC_EN_CTL1_SET : 0); + return regmap_write(iadc->regmap, IADC_EN_CTL1, state ? IADC_EN_CTL1_SET : 0); } static void iadc_status_show(struct iadc_chip *iadc) { - u8 mode, sta1, chan, dig, en, req; + u32 mode, sta1, chan, dig, en, req; int ret; - ret = iadc_read(iadc, IADC_MODE_CTL, &mode); + ret = regmap_read(iadc->regmap, IADC_MODE_CTL, &mode); if (ret < 0) return; - ret = iadc_read(iadc, IADC_DIG_PARAM, &dig); + ret = regmap_read(iadc->regmap, IADC_DIG_PARAM, &dig); if (ret < 0) return; - ret = iadc_read(iadc, IADC_CH_SEL_CTL, &chan); + ret = regmap_read(iadc->regmap, IADC_CH_SEL_CTL, &chan); if (ret < 0) return; - ret = iadc_read(iadc, IADC_CONV_REQ, &req); + ret = regmap_read(iadc->regmap, IADC_CONV_REQ, &req); if (ret < 0) return; - ret = iadc_read(iadc, IADC_STATUS1, &sta1); + ret = regmap_read(iadc->regmap, IADC_STATUS1, &sta1); if (ret < 0) return; - ret = iadc_read(iadc, IADC_EN_CTL1, &en); + ret = regmap_read(iadc->regmap, IADC_EN_CTL1, &en); if (ret < 0) return; @@ -200,34 +181,34 @@ static int iadc_configure(struct iadc_chip *iadc, int channel) /* Mode selection */ mode = (IADC_OP_MODE_NORMAL << IADC_OP_MODE_SHIFT) | IADC_TRIM_EN; - ret = iadc_write(iadc, IADC_MODE_CTL, mode); + ret = regmap_write(iadc->regmap, IADC_MODE_CTL, mode); if (ret < 0) return ret; /* Channel selection */ - ret = iadc_write(iadc, IADC_CH_SEL_CTL, channel); + ret = regmap_write(iadc->regmap, IADC_CH_SEL_CTL, channel); if (ret < 0) return ret; /* Digital parameter setup */ decim = IADC_DEF_DECIMATION << IADC_DIG_DEC_RATIO_SEL_SHIFT; - ret = iadc_write(iadc, IADC_DIG_PARAM, decim); + ret = regmap_write(iadc->regmap, IADC_DIG_PARAM, decim); if (ret < 0) return ret; /* HW settle time delay */ - ret = iadc_write(iadc, IADC_HW_SETTLE_DELAY, IADC_DEF_HW_SETTLE_TIME); + ret = regmap_write(iadc->regmap, IADC_HW_SETTLE_DELAY, IADC_DEF_HW_SETTLE_TIME); if (ret < 0) return ret; - ret = iadc_write(iadc, IADC_FAST_AVG_CTL, IADC_DEF_AVG_SAMPLES); + ret = regmap_write(iadc->regmap, IADC_FAST_AVG_CTL, IADC_DEF_AVG_SAMPLES); if (ret < 0) return ret; if (IADC_DEF_AVG_SAMPLES) - ret = iadc_write(iadc, IADC_FAST_AVG_EN, IADC_FAST_AVG_EN_SET); + ret = regmap_write(iadc->regmap, IADC_FAST_AVG_EN, IADC_FAST_AVG_EN_SET); else - ret = iadc_write(iadc, IADC_FAST_AVG_EN, 0); + ret = regmap_write(iadc->regmap, IADC_FAST_AVG_EN, 0); if (ret < 0) return ret; @@ -240,19 +221,19 @@ static int iadc_configure(struct iadc_chip *iadc, int channel) return ret; /* Request conversion */ - return iadc_write(iadc, IADC_CONV_REQ, IADC_CONV_REQ_SET); + return regmap_write(iadc->regmap, IADC_CONV_REQ, IADC_CONV_REQ_SET); } static int iadc_poll_wait_eoc(struct iadc_chip *iadc, unsigned int interval_us) { unsigned int count, retry; + u32 sta1; int ret; - u8 sta1; retry = interval_us / IADC_CONV_TIME_MIN_US; for (count = 0; count < retry; count++) { - ret = iadc_read(iadc, IADC_STATUS1, &sta1); + ret = regmap_read(iadc->regmap, IADC_STATUS1, &sta1); if (ret < 0) return ret; @@ -268,11 +249,6 @@ static int iadc_poll_wait_eoc(struct iadc_chip *iadc, unsigned int interval_us) return -ETIMEDOUT; } -static int iadc_read_result(struct iadc_chip *iadc, u16 *data) -{ - return regmap_bulk_read(iadc->regmap, iadc->base + IADC_DATA, data, 2); -} - static int iadc_do_conversion(struct iadc_chip *iadc, int chan, u16 *data) { unsigned int wait; @@ -297,7 +273,7 @@ static int iadc_do_conversion(struct iadc_chip *iadc, int chan, u16 *data) } if (!ret) - ret = iadc_read_result(iadc, data); + ret = regmap_bulk_read(iadc->regmap, IADC_DATA, data, sizeof(*data)); exit: iadc_set_state(iadc, false); if (ret < 0) @@ -393,33 +369,33 @@ static int iadc_update_offset(struct iadc_chip *iadc) static int iadc_version_check(struct iadc_chip *iadc) { - u8 val; + u32 val; int ret; - ret = iadc_read(iadc, IADC_PERPH_TYPE, &val); + ret = regmap_read(iadc->regmap, IADC_PERPH_TYPE, &val); if (ret < 0) return ret; if (val < IADC_PERPH_TYPE_ADC) { - dev_err(iadc->dev, "%d is not ADC\n", val); + dev_err(iadc->dev, "%u is not ADC\n", val); return -EINVAL; } - ret = iadc_read(iadc, IADC_PERPH_SUBTYPE, &val); + ret = regmap_read(iadc->regmap, IADC_PERPH_SUBTYPE, &val); if (ret < 0) return ret; if (val < IADC_PERPH_SUBTYPE_IADC) { - dev_err(iadc->dev, "%d is not IADC\n", val); + dev_err(iadc->dev, "%u is not IADC\n", val); return -EINVAL; } - ret = iadc_read(iadc, IADC_REVISION2, &val); + ret = regmap_read(iadc->regmap, IADC_REVISION2, &val); if (ret < 0) return ret; if (val < IADC_REVISION2_SUPPORTED_IADC) { - dev_err(iadc->dev, "revision %d not supported\n", val); + dev_err(iadc->dev, "revision %u not supported\n", val); return -EINVAL; } @@ -429,7 +405,7 @@ static int iadc_version_check(struct iadc_chip *iadc) static int iadc_rsense_read(struct iadc_chip *iadc, struct device_node *node) { int ret, sign, int_sense; - u8 deviation; + u32 deviation; ret = of_property_read_u32(node, "qcom,external-resistor-micro-ohms", &iadc->rsense[IADC_EXT_RSENSE]); @@ -441,7 +417,7 @@ static int iadc_rsense_read(struct iadc_chip *iadc, struct device_node *node) return -EINVAL; } - ret = iadc_read(iadc, IADC_NOMINAL_RSENSE, &deviation); + ret = regmap_read(iadc->regmap, IADC_NOMINAL_RSENSE, &deviation); if (ret < 0) return ret; @@ -488,12 +464,19 @@ static void iadc_disable_irq_wake(void *data) static int iadc_probe(struct platform_device *pdev) { + struct regmap_config iadc_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xff, + .fast_io = true, + }; struct device_node *node = pdev->dev.of_node; struct device *dev = &pdev->dev; + struct spmi_subdevice *sub_sdev; + struct spmi_device *sparent; struct iio_dev *indio_dev; struct iadc_chip *iadc; int ret, irq_eoc; - u32 res; indio_dev = devm_iio_device_alloc(dev, sizeof(*iadc)); if (!indio_dev) @@ -502,18 +485,24 @@ static int iadc_probe(struct platform_device *pdev) iadc = iio_priv(indio_dev); iadc->dev = dev; - iadc->regmap = dev_get_regmap(dev->parent, NULL); - if (!iadc->regmap) + sparent = spmi_get_parent_spmi_device(dev); + if (!sparent) return -ENODEV; + sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent); + if (IS_ERR(sub_sdev)) + return PTR_ERR(sub_sdev); + init_completion(&iadc->complete); mutex_init(&iadc->lock); - ret = of_property_read_u32(node, "reg", &res); + ret = device_property_read_u32(dev, "reg", &iadc_regmap_config.reg_base); if (ret < 0) return -ENODEV; - iadc->base = res; + iadc->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &iadc_regmap_config); + if (IS_ERR(iadc->regmap)) + return PTR_ERR(iadc->regmap); ret = iadc_version_check(iadc); if (ret < 0) @@ -596,3 +585,4 @@ MODULE_ALIAS("platform:qcom-spmi-iadc"); MODULE_DESCRIPTION("Qualcomm SPMI PMIC current ADC driver"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>"); +MODULE_IMPORT_NS("SPMI"); diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c index 0f0bf2906af0..5c45fae11890 100644 --- a/drivers/iio/adc/rockchip_saradc.c +++ b/drivers/iio/adc/rockchip_saradc.c @@ -348,6 +348,19 @@ static const struct rockchip_saradc_data rk3588_saradc_data = { .read = rockchip_saradc_read_v2, }; +static const struct iio_chan_spec rockchip_rv1106_saradc_iio_channels[] = { + SARADC_CHANNEL(0, "adc0", 10), + SARADC_CHANNEL(1, "adc1", 10), +}; + +static const struct rockchip_saradc_data rv1106_saradc_data = { + .channels = rockchip_rv1106_saradc_iio_channels, + .num_channels = ARRAY_SIZE(rockchip_rv1106_saradc_iio_channels), + .clk_rate = 1000000, + .start = rockchip_saradc_start_v2, + .read = rockchip_saradc_read_v2, +}; + static const struct of_device_id rockchip_saradc_match[] = { { .compatible = "rockchip,saradc", @@ -370,6 +383,9 @@ static const struct of_device_id rockchip_saradc_match[] = { }, { .compatible = "rockchip,rk3588-saradc", .data = &rk3588_saradc_data, + }, { + .compatible = "rockchip,rv1106-saradc", + .data = &rv1106_saradc_data, }, { } }; diff --git a/drivers/iio/adc/rzg2l_adc.c b/drivers/iio/adc/rzg2l_adc.c index 408fbf8c29cc..3b95f47445a8 100644 --- a/drivers/iio/adc/rzg2l_adc.c +++ b/drivers/iio/adc/rzg2l_adc.c @@ -62,7 +62,7 @@ * @adsmp_mask: ADC sampling period mask (see ADM3 register) * @adint_inten_mask: conversion end interrupt mask (see ADINT register) * @default_adcmp: default ADC cmp (see ADM3 register) - * @num_channels: number of supported channels + * @max_channels: maximum number of channels * @adivc: specifies if ADVIC register is available */ struct rzg2l_adc_hw_params { @@ -70,7 +70,7 @@ struct rzg2l_adc_hw_params { u16 adsmp_mask; u16 adint_inten_mask; u8 default_adcmp; - u8 num_channels; + u8 max_channels; bool adivc; }; @@ -196,7 +196,7 @@ static int rzg2l_adc_conversion_setup(struct rzg2l_adc *adc, u8 ch) /* Select analog input channel subjected to conversion. */ reg = rzg2l_adc_readl(adc, RZG2L_ADM(2)); - reg &= ~GENMASK(hw_params->num_channels - 1, 0); + reg &= ~GENMASK(hw_params->max_channels - 1, 0); reg |= BIT(ch); rzg2l_adc_writel(adc, RZG2L_ADM(2), reg); @@ -307,11 +307,11 @@ static irqreturn_t rzg2l_adc_isr(int irq, void *dev_id) return IRQ_HANDLED; } - intst = reg & GENMASK(hw_params->num_channels - 1, 0); + intst = reg & GENMASK(hw_params->max_channels - 1, 0); if (!intst) return IRQ_NONE; - for_each_set_bit(ch, &intst, hw_params->num_channels) + for_each_set_bit(ch, &intst, hw_params->max_channels) adc->last_val[ch] = rzg2l_adc_readl(adc, RZG2L_ADCR(ch)) & RZG2L_ADCR_AD_MASK; /* clear the channel interrupt */ @@ -341,12 +341,12 @@ static int rzg2l_adc_parse_properties(struct platform_device *pdev, struct rzg2l num_channels = devm_iio_adc_device_alloc_chaninfo_se(&pdev->dev, &rzg2l_adc_chan_template, - hw_params->num_channels - 1, + hw_params->max_channels - 1, &chan_array); if (num_channels < 0) return num_channels; - if (num_channels > hw_params->num_channels) + if (num_channels > hw_params->max_channels) return dev_err_probe(&pdev->dev, -EINVAL, "num of channel children out of range\n"); @@ -429,7 +429,7 @@ static int rzg2l_adc_probe(struct platform_device *pdev) adc = iio_priv(indio_dev); adc->hw_params = device_get_match_data(dev); - if (!adc->hw_params || adc->hw_params->num_channels > RZG2L_ADC_MAX_CHANNELS) + if (!adc->hw_params || adc->hw_params->max_channels > RZG2L_ADC_MAX_CHANNELS) return -EINVAL; ret = rzg2l_adc_parse_properties(pdev, adc); @@ -484,7 +484,7 @@ static int rzg2l_adc_probe(struct platform_device *pdev) } static const struct rzg2l_adc_hw_params rzg2l_hw_params = { - .num_channels = 8, + .max_channels = 8, .default_adcmp = 0xe, .default_adsmp = { 0x578 }, .adsmp_mask = GENMASK(15, 0), @@ -493,15 +493,24 @@ static const struct rzg2l_adc_hw_params rzg2l_hw_params = { }; static const struct rzg2l_adc_hw_params rzg3s_hw_params = { - .num_channels = 9, + .max_channels = 9, .default_adcmp = 0x1d, .default_adsmp = { 0x7f, 0xff }, .adsmp_mask = GENMASK(7, 0), .adint_inten_mask = GENMASK(11, 0), }; +static const struct rzg2l_adc_hw_params rzg3l_adc1_hw_params = { + .max_channels = 9, + .default_adcmp = 0x1d, + .default_adsmp = { 0x7f, 0xff }, + .adsmp_mask = GENMASK(7, 0), + .adint_inten_mask = BIT(8), +}; + static const struct of_device_id rzg2l_adc_match[] = { { .compatible = "renesas,r9a08g045-adc", .data = &rzg3s_hw_params }, + { .compatible = "renesas,r9a08g046-adc1", .data = &rzg3l_adc1_hw_params }, { .compatible = "renesas,rzg2l-adc", .data = &rzg2l_hw_params }, { } }; diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c index 4e0eb02d3d14..8ba7b0fde8a1 100644 --- a/drivers/iio/adc/rzt2h_adc.c +++ b/drivers/iio/adc/rzt2h_adc.c @@ -2,13 +2,19 @@ #include <linux/bitfield.h> #include <linux/cleanup.h> +#include <linux/clk.h> #include <linux/completion.h> #include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> #include <linux/iio/adc-helpers.h> +#include <linux/iio/buffer.h> #include <linux/iio/iio.h> +#include <linux/iio/kfifo_buf.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/iopoll.h> +#include <linux/kthread.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> @@ -18,6 +24,7 @@ #define RZT2H_ADCSR_ADIE_MASK BIT(12) #define RZT2H_ADCSR_ADCS_MASK GENMASK(14, 13) #define RZT2H_ADCSR_ADCS_SINGLE 0b00 +#define RZT2H_ADCSR_ADCS_CONTINUOUS 0b10 #define RZT2H_ADCSR_ADST_MASK BIT(15) #define RZT2H_ADANSA0_REG 0x04 @@ -25,24 +32,71 @@ #define RZT2H_ADDR_REG(x) (0x20 + 0x2 * (x)) +#define RZT2H_ADSSTRn(n) (0xe0 + 0x1 * (n)) + +/* + * Each A/D channel conversion takes a fixed base of 13 ADCLK cycles plus the + * sample time programmed in ADSSTRn, giving a conversion rate of + * ADCLK / (13 + ADSSTRn). + */ +#define RZT2H_ADC_CONV_CYCLES_BASE 13 +#define RZT2H_ADC_SST_MIN 0x7 +#define RZT2H_ADC_SST_MAX 0xff +#define RZT2H_ADC_SST_DEFAULT 0xb + #define RZT2H_ADCALCTL_REG 0x1f0 #define RZT2H_ADCALCTL_CAL_MASK BIT(0) #define RZT2H_ADCALCTL_CAL_RDY_MASK BIT(1) #define RZT2H_ADCALCTL_CAL_ERR_MASK BIT(2) #define RZT2H_ADC_MAX_CHANNELS 16 +#define RZT2H_ADC_CHANNEL_BYTES sizeof(u16) +#define RZT2H_ADC_DMA_PERIOD_SAMPLES 128 +#define RZT2H_ADC_DMA_PERIODS 64 +#define RZT2H_ADC_DMA_BUFFER_SAMPLES (RZT2H_ADC_DMA_PERIODS * \ + RZT2H_ADC_DMA_PERIOD_SAMPLES) +#define RZT2H_ADC_DMA_BUFFER_SIZE (RZT2H_ADC_DMA_BUFFER_SAMPLES * \ + RZT2H_ADC_MAX_CHANNELS * \ + RZT2H_ADC_CHANNEL_BYTES) + +struct rzt2h_adc_dma { + struct dma_chan *chan; + u16 *buf; + dma_addr_t addr; + + unsigned int period_index; + unsigned int period_bytes; + unsigned int first_chan; + unsigned int sample_chans; + + u8 gather[RZT2H_ADC_MAX_CHANNELS]; + unsigned int gather_len; + + atomic_t pending_periods; + + wait_queue_head_t wq; + struct task_struct *thread; +}; struct rzt2h_adc { void __iomem *base; struct device *dev; + phys_addr_t phys_base; + struct rzt2h_adc_dma dma; struct completion completion; /* lock to protect against multiple access to the device */ struct mutex lock; const struct iio_chan_spec *channels; unsigned int num_channels; - unsigned int max_channels; + u16 buf[RZT2H_ADC_MAX_CHANNELS]; + + unsigned long adclk_rate; + int samp_freq_avail[3]; + u8 sst[RZT2H_ADC_MAX_CHANNELS]; + + int irq; }; static void rzt2h_adc_start(struct rzt2h_adc *adc, unsigned int conversion_type) @@ -72,6 +126,11 @@ static void rzt2h_adc_stop(struct rzt2h_adc *adc) writew(reg, adc->base + RZT2H_ADCSR_REG); } +static void rzt2h_adc_set_sst(struct rzt2h_adc *adc, unsigned int ch, u8 sst) +{ + writeb(sst, adc->base + RZT2H_ADSSTRn(ch)); +} + static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *val) { int ret; @@ -87,13 +146,18 @@ static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *va /* Enable a single channel */ writew(RZT2H_ADANSA0_CH_MASK(ch), adc->base + RZT2H_ADANSA0_REG); + rzt2h_adc_set_sst(adc, ch, adc->sst[ch]); + rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_SINGLE); /* - * Datasheet Page 2770, Table 41.1: - * 0.32us per channel when sample-and-hold circuits are not in use. + * Conversion can take up to ~4.3us at the maximum configurable ADSSTRn + * (ADSSTRn + 13 cycles at 62.5 MHz), which rounds up to 1 jiffy. A bare + * 1-jiffy timeout can expire almost immediately if it's armed right + * before a tick, so add one more jiffy to guarantee the conversion time + * actually elapses. */ - ret = wait_for_completion_timeout(&adc->completion, usecs_to_jiffies(1)); + ret = wait_for_completion_timeout(&adc->completion, usecs_to_jiffies(5) + 1); if (!ret) { ret = -ETIMEDOUT; goto disable; @@ -149,6 +213,281 @@ static int rzt2h_adc_calibrate(struct rzt2h_adc *adc) return 0; } +static void rzt2h_adc_push_period(struct iio_dev *indio_dev, u16 *period, + dma_addr_t addr) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + u16 *dst = adc->buf; + u16 *src = period; + + dma_sync_single_for_cpu(adc->dev, addr, adc->dma.period_bytes, + DMA_FROM_DEVICE); + + for (unsigned int sample = 0; sample < RZT2H_ADC_DMA_PERIOD_SAMPLES; sample++) { + for (unsigned int i = 0; i < adc->dma.gather_len; i++) + dst[i] = src[adc->dma.gather[i]]; + + src += adc->dma.sample_chans; + + iio_push_to_buffers(indio_dev, adc->buf); + } +} + +static void rzt2h_adc_advance_period_index(struct rzt2h_adc *adc, unsigned int i) +{ + adc->dma.period_index += i; + adc->dma.period_index %= RZT2H_ADC_DMA_PERIODS; +} + +static void rzt2h_adc_dma_thread_loop(struct iio_dev *indio_dev) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + int pending, drop; + dma_addr_t addr; + u16 *period; + + pending = atomic_xchg(&adc->dma.pending_periods, 0); + + if (pending >= RZT2H_ADC_DMA_PERIODS) { + /* + * The consumer fell a full buffer behind and the oldest periods + * have already been overwritten by the cyclic DMA transfer. + * Drop them and jump straight to the oldest period that has + * not been overwritten. + */ + drop = pending - RZT2H_ADC_DMA_PERIODS + 1; + + rzt2h_adc_advance_period_index(adc, drop); + pending -= drop; + } + + for (unsigned int i = 0; i < pending; i++) { + unsigned int backlog = atomic_read(&adc->dma.pending_periods) + + pending - i; + + /* + * Bail if enough new periods have completed since reading the + * pending_periods that the next period about to be read is at + * risk of being overwritten. + */ + if (backlog >= RZT2H_ADC_DMA_PERIODS) { + rzt2h_adc_advance_period_index(adc, pending - i); + break; + } + + period = adc->dma.buf + adc->dma.period_index * + RZT2H_ADC_DMA_PERIOD_SAMPLES * adc->dma.sample_chans; + addr = adc->dma.addr + adc->dma.period_index * + adc->dma.period_bytes; + + rzt2h_adc_push_period(indio_dev, period, addr); + rzt2h_adc_advance_period_index(adc, 1); + } +} + +static int rzt2h_adc_dma_thread(void *data) +{ + struct iio_dev *indio_dev = data; + struct rzt2h_adc *adc = iio_priv(indio_dev); + + while (!kthread_should_stop()) { + wait_event_interruptible(adc->dma.wq, + atomic_read(&adc->dma.pending_periods) || + kthread_should_stop()); + + if (kthread_should_stop()) + break; + + rzt2h_adc_dma_thread_loop(indio_dev); + } + + return 0; +} + +static void rzt2h_adc_dma_callback(void *data) +{ + struct iio_dev *indio_dev = data; + struct rzt2h_adc *adc = iio_priv(indio_dev); + + atomic_inc(&adc->dma.pending_periods); + wake_up(&adc->dma.wq); +} + +static void rzt2h_adc_dma_calc_layout(struct iio_dev *indio_dev) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + unsigned int hi = 0, lo = RZT2H_ADC_MAX_CHANNELS - 1; + const struct iio_chan_spec *chan; + unsigned int sample_chans; + unsigned int first_chan; + unsigned int scan_index; + unsigned int swap; + unsigned int idx; + + /* Find the lowest and highest enabled channel. */ + iio_for_each_active_channel(indio_dev, scan_index) { + chan = &indio_dev->channels[scan_index]; + + lo = min_t(unsigned int, lo, chan->channel); + hi = max_t(unsigned int, hi, chan->channel); + } + + /* + * The DMA has no scatter-gather and transfers must have a power-of-two + * width, so pick the smallest power-of-two-aligned block of channels + * that covers all enabled channels. + */ + for (sample_chans = 1; sample_chans < RZT2H_ADC_MAX_CHANNELS; sample_chans <<= 1) { + first_chan = round_down(lo, sample_chans); + + if (first_chan + sample_chans > hi) + break; + } + + /* + * Build a table to map each enabled channel to its position in the + * transferred block, it will be used later to extract only the enabled + * channels out of it. + * The DMA moves data in 32-bit words, which swaps each pair of adjacent + * 16-bit channels. Undo it. + */ + adc->dma.gather_len = 0; + swap = sample_chans > 1; + iio_for_each_active_channel(indio_dev, scan_index) { + chan = &indio_dev->channels[scan_index]; + idx = chan->channel - first_chan; + + adc->dma.gather[adc->dma.gather_len++] = idx ^ swap; + } + + adc->dma.first_chan = first_chan; + adc->dma.sample_chans = sample_chans; + adc->dma.period_bytes = RZT2H_ADC_DMA_PERIOD_SAMPLES * sample_chans * + RZT2H_ADC_CHANNEL_BYTES; +} + +static int rzt2h_adc_start_dma(struct iio_dev *indio_dev) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + struct dma_async_tx_descriptor *desc; + struct dma_slave_config config; + unsigned int buffer_bytes; + dma_cookie_t cookie; + int ret; + + rzt2h_adc_dma_calc_layout(indio_dev); + + config = (struct dma_slave_config) { + .src_addr = adc->phys_base + RZT2H_ADDR_REG(adc->dma.first_chan), + .src_addr_width = adc->dma.sample_chans * RZT2H_ADC_CHANNEL_BYTES, + }; + + buffer_bytes = RZT2H_ADC_DMA_PERIODS * adc->dma.period_bytes; + + ret = dmaengine_slave_config(adc->dma.chan, &config); + if (ret) + return ret; + + desc = dmaengine_prep_dma_cyclic(adc->dma.chan, adc->dma.addr, + buffer_bytes, adc->dma.period_bytes, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + if (!desc) + return -EBUSY; + + desc->callback = rzt2h_adc_dma_callback; + desc->callback_param = indio_dev; + + cookie = dmaengine_submit(desc); + ret = dma_submit_error(cookie); + if (ret) { + dmaengine_terminate_sync(adc->dma.chan); + return ret; + } + + adc->dma.thread = kthread_run(rzt2h_adc_dma_thread, indio_dev, + "rzt2h-adc-dma"); + if (IS_ERR(adc->dma.thread)) { + dmaengine_terminate_sync(adc->dma.chan); + return PTR_ERR(adc->dma.thread); + } + + disable_irq(adc->irq); + + dma_async_issue_pending(adc->dma.chan); + + return 0; +} + +static int rzt2h_adc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + const struct iio_chan_spec *chan; + struct device *dev = adc->dev; + unsigned int scan_index; + u16 val = 0; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + iio_for_each_active_channel(indio_dev, scan_index) { + chan = &indio_dev->channels[scan_index]; + val |= RZT2H_ADANSA0_CH_MASK(chan->channel); + rzt2h_adc_set_sst(adc, chan->channel, adc->sst[chan->channel]); + } + + writew(val, adc->base + RZT2H_ADANSA0_REG); + + adc->dma.period_index = 0; + atomic_set(&adc->dma.pending_periods, 0); + + ret = rzt2h_adc_start_dma(indio_dev); + if (ret) { + pm_runtime_put_autosuspend(dev); + return ret; + } + + rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_CONTINUOUS); + + return 0; +} + +static int rzt2h_adc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + struct device *dev = adc->dev; + + rzt2h_adc_stop(adc); + + dmaengine_terminate_sync(adc->dma.chan); + + kthread_stop(adc->dma.thread); + + enable_irq(adc->irq); + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static int rzt2h_adc_sst_to_freq(struct rzt2h_adc *adc, u8 sst) +{ + return adc->adclk_rate / (RZT2H_ADC_CONV_CYCLES_BASE + sst); +} + +static u8 rzt2h_adc_freq_to_sst(struct rzt2h_adc *adc, int freq) +{ + unsigned int cycles; + + cycles = DIV_ROUND_CLOSEST(adc->adclk_rate, freq); + cycles = clamp_val(cycles, + RZT2H_ADC_CONV_CYCLES_BASE + RZT2H_ADC_SST_MIN, + RZT2H_ADC_CONV_CYCLES_BASE + RZT2H_ADC_SST_MAX); + + return cycles - RZT2H_ADC_CONV_CYCLES_BASE; +} + static int rzt2h_adc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -156,19 +495,76 @@ static int rzt2h_adc_read_raw(struct iio_dev *indio_dev, struct rzt2h_adc *adc = iio_priv(indio_dev); switch (mask) { - case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_RAW: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + return rzt2h_adc_read_single(adc, chan->channel, val); + } case IIO_CHAN_INFO_SCALE: *val = 1800; *val2 = 12; return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = rzt2h_adc_sst_to_freq(adc, adc->sst[chan->channel]); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int rzt2h_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + if (val <= 0) + return -EINVAL; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + adc->sst[chan->channel] = rzt2h_adc_freq_to_sst(adc, val); + + return 0; + } + default: + return -EINVAL; + } +} + +static int rzt2h_adc_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = adc->samp_freq_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(adc->samp_freq_avail); + return IIO_AVAIL_RANGE; default: return -EINVAL; } } +static const struct iio_buffer_setup_ops rzt2h_adc_buffer_setup_ops = { + .postenable = rzt2h_adc_buffer_postenable, + .predisable = rzt2h_adc_buffer_predisable, +}; + static const struct iio_info rzt2h_adc_iio_info = { .read_raw = rzt2h_adc_read_raw, + .write_raw = rzt2h_adc_write_raw, + .read_avail = rzt2h_adc_read_avail, }; static irqreturn_t rzt2h_adc_isr(int irq, void *private) @@ -183,14 +579,21 @@ static irqreturn_t rzt2h_adc_isr(int irq, void *private) static const struct iio_chan_spec rzt2h_adc_chan_template = { .indexed = 1, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | - BIT(IIO_CHAN_INFO_SCALE), + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), .type = IIO_VOLTAGE, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_CPU, + }, }; static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc) { struct iio_chan_spec *chan_array; - unsigned int i; int ret; ret = devm_iio_adc_device_alloc_chaninfo_se(adc->dev, @@ -203,19 +606,64 @@ static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc) adc->num_channels = ret; adc->channels = chan_array; - for (i = 0; i < adc->num_channels; i++) - if (chan_array[i].channel + 1 > adc->max_channels) - adc->max_channels = chan_array[i].channel + 1; + for (unsigned int i = 0; i < adc->num_channels; i++) { + adc->sst[chan_array[i].channel] = RZT2H_ADC_SST_DEFAULT; + + chan_array[i].scan_index = i; + } return 0; } +static void rzt2h_adc_free_dma_buf(void *p) +{ + struct rzt2h_adc *adc = p; + + dma_free_noncoherent(adc->dev, RZT2H_ADC_DMA_BUFFER_SIZE, + adc->dma.buf, adc->dma.addr, DMA_FROM_DEVICE); +} + +static int rzt2h_adc_setup_dma(struct iio_dev *indio_dev) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + struct device *dev = adc->dev; + int ret; + + adc->dma.chan = devm_dma_request_chan(dev, "rx"); + if (IS_ERR(adc->dma.chan)) { + ret = PTR_ERR(adc->dma.chan); + if (ret != -ENODEV) + return dev_err_probe(dev, ret, "DMA channel request failed\n"); + + adc->dma.chan = NULL; + return 0; + } + + adc->dma.buf = dma_alloc_noncoherent(dev, RZT2H_ADC_DMA_BUFFER_SIZE, + &adc->dma.addr, DMA_FROM_DEVICE, + GFP_KERNEL); + if (!adc->dma.buf) + return -ENOMEM; + + dma_sync_single_for_device(dev, adc->dma.addr, RZT2H_ADC_DMA_BUFFER_SIZE, + DMA_FROM_DEVICE); + + ret = devm_add_action_or_reset(dev, rzt2h_adc_free_dma_buf, adc); + if (ret) + return ret; + + return devm_iio_kfifo_buffer_setup_ext(dev, indio_dev, + &rzt2h_adc_buffer_setup_ops, NULL); +} + static int rzt2h_adc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct iio_dev *indio_dev; struct rzt2h_adc *adc; - int ret, irq; + struct resource *res; + struct clk *adclk; + int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); if (!indio_dev) @@ -224,6 +672,7 @@ static int rzt2h_adc_probe(struct platform_device *pdev) adc = iio_priv(indio_dev); adc->dev = dev; init_completion(&adc->completion); + init_waitqueue_head(&adc->dma.wq); ret = devm_mutex_init(dev, &adc->lock); if (ret) @@ -235,21 +684,35 @@ static int rzt2h_adc_probe(struct platform_device *pdev) if (ret) return ret; - adc->base = devm_platform_ioremap_resource(pdev, 0); + adc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(adc->base)) return PTR_ERR(adc->base); + adc->phys_base = res->start; + + adclk = devm_clk_get(dev, "adclk"); + if (IS_ERR(adclk)) + return dev_err_probe(dev, PTR_ERR(adclk), "failed to get adclk\n"); + + adc->adclk_rate = clk_get_rate(adclk); + if (!adc->adclk_rate) + return dev_err_probe(dev, -EINVAL, "invalid adclk rate\n"); + + adc->samp_freq_avail[0] = rzt2h_adc_sst_to_freq(adc, RZT2H_ADC_SST_MAX); + adc->samp_freq_avail[1] = 1; + adc->samp_freq_avail[2] = rzt2h_adc_sst_to_freq(adc, RZT2H_ADC_SST_MIN); + pm_runtime_set_autosuspend_delay(dev, 300); pm_runtime_use_autosuspend(dev); ret = devm_pm_runtime_enable(dev); if (ret) return ret; - irq = platform_get_irq_byname(pdev, "adi"); - if (irq < 0) - return irq; + adc->irq = platform_get_irq_byname(pdev, "adi"); + if (adc->irq < 0) + return adc->irq; - ret = devm_request_irq(dev, irq, rzt2h_adc_isr, 0, dev_name(dev), adc); + ret = devm_request_irq(dev, adc->irq, rzt2h_adc_isr, 0, dev_name(dev), adc); if (ret) return ret; @@ -259,6 +722,10 @@ static int rzt2h_adc_probe(struct platform_device *pdev) indio_dev->channels = adc->channels; indio_dev->num_channels = adc->num_channels; + ret = rzt2h_adc_setup_dma(indio_dev); + if (ret) + return ret; + return devm_iio_device_register(dev, indio_dev); } diff --git a/drivers/iio/adc/sophgo-cv1800b-adc.c b/drivers/iio/adc/sophgo-cv1800b-adc.c index bdc3e1326a9a..c6eed09acfcd 100644 --- a/drivers/iio/adc/sophgo-cv1800b-adc.c +++ b/drivers/iio/adc/sophgo-cv1800b-adc.c @@ -182,6 +182,8 @@ static int cv1800b_adc_probe(struct platform_device *pdev) return PTR_ERR(saradc->regs); saradc->irq = platform_get_irq_optional(pdev, 0); + if (saradc->irq < 0 && saradc->irq != -ENXIO) + return saradc->irq; if (saradc->irq > 0) { init_completion(&saradc->completion); ret = devm_request_irq(dev, saradc->irq, diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index 90f0e257e6d1..600f975f1d7b 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -2465,6 +2465,7 @@ static int stm32_adc_dma_request(struct device *dev, struct iio_dev *indio_dev) { struct stm32_adc *adc = iio_priv(indio_dev); struct dma_slave_config config = { }; + struct device *dma_dev; int ret; adc->dma_chan = dma_request_chan(dev, "rx"); @@ -2479,7 +2480,8 @@ static int stm32_adc_dma_request(struct device *dev, struct iio_dev *indio_dev) return 0; } - adc->rx_buf = dma_alloc_coherent(adc->dma_chan->device->dev, + dma_dev = dmaengine_get_dma_device(adc->dma_chan); + adc->rx_buf = dma_alloc_coherent(dma_dev, STM32_DMA_BUFFER_SIZE, &adc->rx_dma_buf, GFP_KERNEL); if (!adc->rx_buf) { @@ -2499,7 +2501,7 @@ static int stm32_adc_dma_request(struct device *dev, struct iio_dev *indio_dev) return 0; err_free: - dma_free_coherent(adc->dma_chan->device->dev, STM32_DMA_BUFFER_SIZE, + dma_free_coherent(dma_dev, STM32_DMA_BUFFER_SIZE, adc->rx_buf, adc->rx_dma_buf); err_release: dma_release_channel(adc->dma_chan); @@ -2626,7 +2628,7 @@ err_buffer_cleanup: err_dma_disable: if (adc->dma_chan) { - dma_free_coherent(adc->dma_chan->device->dev, + dma_free_coherent(dmaengine_get_dma_device(adc->dma_chan), STM32_DMA_BUFFER_SIZE, adc->rx_buf, adc->rx_dma_buf); dma_release_channel(adc->dma_chan); @@ -2649,7 +2651,7 @@ static void stm32_adc_remove(struct platform_device *pdev) pm_runtime_put_noidle(&pdev->dev); iio_triggered_buffer_cleanup(indio_dev); if (adc->dma_chan) { - dma_free_coherent(adc->dma_chan->device->dev, + dma_free_coherent(dmaengine_get_dma_device(adc->dma_chan), STM32_DMA_BUFFER_SIZE, adc->rx_buf, adc->rx_dma_buf); dma_release_channel(adc->dma_chan); @@ -2734,7 +2736,7 @@ static const struct stm32_adc_cfg stm32h7_adc_cfg = { .set_ovs = stm32h7_adc_set_ovs, }; -static const unsigned int stm32_adc_min_ts_mp1[] = { 100, 100, 100, 4300, 9800 }; +static const unsigned int stm32_adc_min_ts_mp1[] = { 100, 0, 0, 4300, 9800 }; static_assert(ARRAY_SIZE(stm32_adc_min_ts_mp1) == STM32_ADC_INT_CH_NB); static const struct stm32_adc_cfg stm32mp1_adc_cfg = { @@ -2756,7 +2758,7 @@ static const struct stm32_adc_cfg stm32mp1_adc_cfg = { .set_ovs = stm32h7_adc_set_ovs, }; -static const unsigned int stm32_adc_min_ts_mp13[] = { 100, 0, 0, 4300, 9800 }; +static const unsigned int stm32_adc_min_ts_mp13[] = { 1000, 1000, 1000, 4300, 9800 }; static_assert(ARRAY_SIZE(stm32_adc_min_ts_mp13) == STM32_ADC_INT_CH_NB); static const struct stm32_adc_cfg stm32mp13_adc_cfg = { diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c index 00f05e167afc..674767da3ac3 100644 --- a/drivers/iio/adc/stm32-dfsdm-adc.c +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -1484,7 +1484,7 @@ static void stm32_dfsdm_dma_release(struct iio_dev *indio_dev) struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); if (adc->dma_chan) { - dma_free_coherent(adc->dma_chan->device->dev, + dma_free_coherent(dmaengine_get_dma_device(adc->dma_chan), DFSDM_DMA_BUFFER_SIZE, adc->rx_buf, adc->dma_buf); dma_release_channel(adc->dma_chan); @@ -1504,7 +1504,7 @@ static int stm32_dfsdm_dma_request(struct device *dev, return ret; } - adc->rx_buf = dma_alloc_coherent(adc->dma_chan->device->dev, + adc->rx_buf = dma_alloc_coherent(dmaengine_get_dma_device(adc->dma_chan), DFSDM_DMA_BUFFER_SIZE, &adc->dma_buf, GFP_KERNEL); if (!adc->rx_buf) { diff --git a/drivers/iio/adc/ti-adc128s052.c b/drivers/iio/adc/ti-adc128s052.c index 1899813c1aee..f9a995ff403e 100644 --- a/drivers/iio/adc/ti-adc128s052.c +++ b/drivers/iio/adc/ti-adc128s052.c @@ -195,12 +195,14 @@ static int adc128_probe(struct spi_device *spi) adc = iio_priv(indio_dev); adc->spi = spi; - indio_dev->name = spi_get_device_id(spi)->name; + config = spi_get_device_match_data(spi); + if (!config) + return -ENODATA; + + indio_dev->name = spi->modalias; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &adc128_info; - config = spi_get_device_match_data(spi); - indio_dev->channels = config->channels; indio_dev->num_channels = config->num_channels; diff --git a/drivers/iio/adc/ti-ads1015.c b/drivers/iio/adc/ti-ads1015.c index 0fbfa4e499aa..0f9244107c6d 100644 --- a/drivers/iio/adc/ti-ads1015.c +++ b/drivers/iio/adc/ti-ads1015.c @@ -233,6 +233,7 @@ static const struct iio_event_spec ads1015_events[] = { struct ads1015_channel_data { unsigned int pga; unsigned int data_rate; + const char *label; }; struct ads1015_thresh_data { @@ -586,6 +587,15 @@ static int ads1015_read_raw(struct iio_dev *indio_dev, } } +static int ads1015_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + struct ads1015_data *data = iio_priv(indio_dev); + + return sysfs_emit(label, "%s\n", + data->channel_data[chan->address].label); +} + static int ads1015_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) @@ -843,6 +853,7 @@ static const struct iio_buffer_setup_ops ads1015_buffer_setup_ops = { static const struct iio_info ads1015_info = { .read_avail = ads1015_read_avail, .read_raw = ads1015_read_raw, + .read_label = ads1015_read_label, .write_raw = ads1015_write_raw, .read_event_value = ads1015_read_event, .write_event_value = ads1015_write_event, @@ -853,6 +864,7 @@ static const struct iio_info ads1015_info = { static const struct iio_info tla2024_info = { .read_avail = ads1015_read_avail, .read_raw = ads1015_read_raw, + .read_label = ads1015_read_label, .write_raw = ads1015_write_raw, }; @@ -861,7 +873,9 @@ static int ads1015_client_get_channels_config(struct i2c_client *client) struct iio_dev *indio_dev = i2c_get_clientdata(client); struct ads1015_data *data = iio_priv(indio_dev); struct device *dev = &client->dev; + const char *label; int i = -1; + int ret; device_for_each_child_node_scoped(dev, node) { u32 pval; @@ -897,6 +911,15 @@ static int ads1015_client_get_channels_config(struct i2c_client *client) } } + if (fwnode_property_present(node, "label")) { + ret = fwnode_property_read_string(node, "label", &label); + if (ret) { + dev_err(dev, "invalid label on %pfw\n", node); + return ret; + } + data->channel_data[channel].label = label; + } + data->channel_data[channel].pga = pga; data->channel_data[channel].data_rate = data_rate; @@ -985,6 +1008,13 @@ static int ads1015_probe(struct i2c_client *client) /* we need to keep this ABI the same as used by hwmon ADS1015 driver */ ads1015_get_channels_config(client); + /* Channels without a label property fall back to the datasheet name */ + for (i = 0; i < ADS1015_CHANNELS; i++) { + if (!data->channel_data[i].label) + data->channel_data[i].label = + indio_dev->channels[i].datasheet_name; + } + data->regmap = devm_regmap_init_i2c(client, chip->has_comparator ? &ads1015_regmap_config : &tla2024_regmap_config); diff --git a/drivers/iio/adc/ti-ads1018.c b/drivers/iio/adc/ti-ads1018.c index d9ec4a8d7ce5..8f2d03eee77b 100644 --- a/drivers/iio/adc/ti-ads1018.c +++ b/drivers/iio/adc/ti-ads1018.c @@ -624,12 +624,16 @@ static int ads1018_trigger_setup(struct iio_dev *indio_dev) static int ads1018_spi_probe(struct spi_device *spi) { - const struct ads1018_chip_info *info = spi_get_device_match_data(spi); + const struct ads1018_chip_info *info; struct device *dev = &spi->dev; struct iio_dev *indio_dev; struct ads1018 *ads1018; int ret; + info = spi_get_device_match_data(spi); + if (!info) + return -ENODATA; + indio_dev = devm_iio_device_alloc(dev, sizeof(*ads1018)); if (!indio_dev) return -ENOMEM; diff --git a/drivers/iio/adc/ti-ads1100.c b/drivers/iio/adc/ti-ads1100.c index 9fe8d54cce83..9957c4d813fb 100644 --- a/drivers/iio/adc/ti-ads1100.c +++ b/drivers/iio/adc/ti-ads1100.c @@ -5,7 +5,7 @@ * Copyright (c) 2023, Topic Embedded Products * * Datasheet: https://www.ti.com/lit/gpn/ads1100 - * IIO driver for ADS1100 and ADS1000 ADC 16-bit I2C + * IIO driver for ADS1100 and similar single channel ADC 16-bit I2C */ #include <linux/bitfield.h> @@ -15,10 +15,12 @@ #include <linux/module.h> #include <linux/init.h> #include <linux/i2c.h> +#include <linux/iopoll.h> #include <linux/mutex.h> #include <linux/property.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> +#include <linux/time.h> #include <linux/units.h> #include <linux/iio/iio.h> @@ -39,17 +41,44 @@ #define ADS1100_SINGLESHOT ADS1100_CFG_SC #define ADS1100_SLEEP_DELAY_MS 2000 +#define ADS1110_INTERNAL_REF_mV 2048 static const int ads1100_data_rate[] = { 128, 32, 16, 8 }; +static const int ads1110_data_rate[] = { 240, 60, 30, 15 }; static const int ads1100_data_rate_bits[] = { 12, 14, 15, 16 }; +/* Timeout based on the minimum sample rate of 8 SPS (7500ms) */ +#define ADS1100_MAX_DRDY_TIMEOUT_US (7500 * USEC_PER_MSEC) + +struct ads1100_config { + const char *name; + const int *available_data_rate_Hz; + const int data_rate_count; + bool has_internal_vref_only; +}; + +static const struct ads1100_config ads1100_config = { + .name = "ads1100", + .available_data_rate_Hz = ads1100_data_rate, + .data_rate_count = ARRAY_SIZE(ads1100_data_rate), + .has_internal_vref_only = false, +}; + +static const struct ads1100_config ads1110_config = { + .name = "ads1110", + .available_data_rate_Hz = ads1110_data_rate, + .data_rate_count = ARRAY_SIZE(ads1110_data_rate), + .has_internal_vref_only = true, +}; + struct ads1100_data { struct i2c_client *client; struct regulator *reg_vdd; struct mutex lock; int scale_avail[2 * 4]; /* 4 gain settings */ + const struct ads1100_config *chip_info; u8 config; - bool supports_data_rate; /* Only the ADS1100 can select the rate */ + bool supports_data_rate; }; static const struct iio_chan_spec ads1100_channel = { @@ -85,6 +114,20 @@ static int ads1100_set_config_bits(struct ads1100_data *data, u8 mask, u8 value) return 0; }; +static int ads1100_get_vref_millivolts(struct ads1100_data *data) +{ + int voltage_uV; + + if (data->chip_info->has_internal_vref_only) + return ADS1110_INTERNAL_REF_mV; + + voltage_uV = regulator_get_voltage(data->reg_vdd); + if (voltage_uV < 0) + return voltage_uV; + + return voltage_uV / (MICRO / MILLI); +} + static int ads1100_data_bits(struct ads1100_data *data) { return ads1100_data_rate_bits[FIELD_GET(ADS1100_DR_MASK, data->config)]; @@ -123,10 +166,86 @@ static int ads1100_get_adc_result(struct ads1100_data *data, int chan, int *val) return 0; } +static int ads1100_conversion_busy(struct ads1100_data *data) +{ + u8 buffer[3]; + int ret; + + ret = i2c_master_recv(data->client, (char *)&buffer, sizeof(buffer)); + if (ret < 0) { + dev_err(&data->client->dev, "I2C read fail: %d\n", ret); + return ret; + } + + return FIELD_GET(ADS1100_CFG_ST_BSY, buffer[2]); +} + +static int ads1100_wait_single_conversion(struct ads1100_data *data) +{ + int data_rate_index = FIELD_GET(ADS1100_DR_MASK, data->config); + int data_rate_Hz = data->chip_info->available_data_rate_Hz[data_rate_index]; + unsigned long poll_us = DIV_ROUND_CLOSEST(USEC_PER_SEC, data_rate_Hz) / 4; + int busy; + int ret; + + ret = readx_poll_timeout(ads1100_conversion_busy, data, + busy, busy <= 0, + poll_us, ADS1100_MAX_DRDY_TIMEOUT_US); + if (busy < 0) + return busy; + + return ret; +} + +static int ads1100_start_single_conversion(struct ads1100_data *data) +{ + u8 config = data->config | ADS1100_CFG_SC; + int ret; + + ret = i2c_master_send(data->client, &config, sizeof(config)); + if (ret < 0) { + dev_err(&data->client->dev, "I2C write fail: %d\n", ret); + return ret; + } + /* Need to wait because of change from continuous to single mode */ + ret = ads1100_wait_single_conversion(data); + if (ret) + return ret; + + config |= ADS1100_CFG_ST_BSY; + + ret = i2c_master_send(data->client, &config, sizeof(config)); + if (ret < 0) { + dev_err(&data->client->dev, "I2C write fail: %d\n", ret); + return ret; + } + + /* No need to cache it, it's status bit */ + data->config = config & ~ADS1100_CFG_ST_BSY; + + return 0; +} + +static int ads1100_poll_data_ready(struct ads1100_data *data) +{ + int ret; + + ret = ads1100_start_single_conversion(data); + if (ret) + return ret; + + ret = ads1100_wait_single_conversion(data); + if (ret) + return ret; + + return ads1100_set_config_bits(data, ADS1100_CFG_SC, ADS1100_CONTINUOUS); +} + static int ads1100_set_scale(struct ads1100_data *data, int val, int val2) { int microvolts; int gain; + int ret; /* With Vdd between 2.7 and 5V, the scale is always below 1 */ if (val) @@ -135,7 +254,12 @@ static int ads1100_set_scale(struct ads1100_data *data, int val, int val2) if (!val2) return -EINVAL; - microvolts = regulator_get_voltage(data->reg_vdd); + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(&data->client->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + microvolts = ads1100_get_vref_millivolts(data) * (MICRO / MILLI); /* * val2 is in 'micro' units, n = val2 / 1000000 * result must be millivolts, d = microvolts / 1000 @@ -149,32 +273,40 @@ static int ads1100_set_scale(struct ads1100_data *data, int val, int val2) ads1100_set_config_bits(data, ADS1100_PGA_MASK, ffs(gain) - 1); - return 0; + return ads1100_poll_data_ready(data); } static int ads1100_set_data_rate(struct ads1100_data *data, int chan, int rate) { unsigned int i; unsigned int size; + int ret; - size = data->supports_data_rate ? ARRAY_SIZE(ads1100_data_rate) : 1; + size = data->supports_data_rate ? data->chip_info->data_rate_count : 1; for (i = 0; i < size; i++) { - if (ads1100_data_rate[i] == rate) - return ads1100_set_config_bits(data, ADS1100_DR_MASK, - FIELD_PREP(ADS1100_DR_MASK, i)); + if (data->chip_info->available_data_rate_Hz[i] == rate) + break; } - return -EINVAL; -} + if (i == size) + return -EINVAL; -static int ads1100_get_vdd_millivolts(struct ads1100_data *data) -{ - return regulator_get_voltage(data->reg_vdd) / (MICRO / MILLI); + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(&data->client->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = ads1100_set_config_bits(data, ADS1100_DR_MASK, + FIELD_PREP(ADS1100_DR_MASK, i)); + if (ret) + return ret; + + return ads1100_poll_data_ready(data); } static void ads1100_calc_scale_avail(struct ads1100_data *data) { - int millivolts = ads1100_get_vdd_millivolts(data); + int millivolts = ads1100_get_vref_millivolts(data); unsigned int i; for (i = 0; i < ARRAY_SIZE(data->scale_avail) / 2; i++) { @@ -196,9 +328,9 @@ static int ads1100_read_avail(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: *type = IIO_VAL_INT; - *vals = ads1100_data_rate; + *vals = data->chip_info->available_data_rate_Hz; if (data->supports_data_rate) - *length = ARRAY_SIZE(ads1100_data_rate); + *length = data->chip_info->data_rate_count; else *length = 1; return IIO_AVAIL_LIST; @@ -218,6 +350,7 @@ static int ads1100_read_raw(struct iio_dev *indio_dev, { int ret; struct ads1100_data *data = iio_priv(indio_dev); + int data_rate_index; guard(mutex)(&data->lock); switch (mask) { @@ -233,12 +366,12 @@ static int ads1100_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: /* full-scale is the supply voltage in millivolts */ - *val = ads1100_get_vdd_millivolts(data); + *val = ads1100_get_vref_millivolts(data); *val2 = 15 + FIELD_GET(ADS1100_PGA_MASK, data->config); return IIO_VAL_FRACTIONAL_LOG2; case IIO_CHAN_INFO_SAMP_FREQ: - *val = ads1100_data_rate[FIELD_GET(ADS1100_DR_MASK, - data->config)]; + data_rate_index = FIELD_GET(ADS1100_DR_MASK, data->config); + *val = data->chip_info->available_data_rate_Hz[data_rate_index]; return IIO_VAL_INT; default: return -EINVAL; @@ -318,7 +451,12 @@ static int ads1100_probe(struct i2c_client *client) data->client = client; mutex_init(&data->lock); - indio_dev->name = "ads1100"; + data->chip_info = i2c_get_match_data(client); + if (!data->chip_info) + return dev_err_probe(dev, -ENODATA, + "Can't get device data from firmware\n"); + + indio_dev->name = data->chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = &ads1100_channel; indio_dev->num_channels = 1; @@ -400,16 +538,18 @@ static DEFINE_RUNTIME_DEV_PM_OPS(ads1100_pm_ops, NULL); static const struct i2c_device_id ads1100_id[] = { - { .name = "ads1100" }, - { .name = "ads1000" }, + { .name = "ads1000", .driver_data = (kernel_ulong_t)&ads1100_config }, + { .name = "ads1100", .driver_data = (kernel_ulong_t)&ads1100_config }, + { .name = "ads1110", .driver_data = (kernel_ulong_t)&ads1110_config }, { } }; MODULE_DEVICE_TABLE(i2c, ads1100_id); static const struct of_device_id ads1100_of_match[] = { - {.compatible = "ti,ads1100" }, - {.compatible = "ti,ads1000" }, + { .compatible = "ti,ads1000", .data = &ads1100_config }, + { .compatible = "ti,ads1100", .data = &ads1100_config }, + { .compatible = "ti,ads1110", .data = &ads1110_config }, { } }; diff --git a/drivers/iio/adc/ti-ads112c04.c b/drivers/iio/adc/ti-ads112c04.c new file mode 100644 index 000000000000..acf7209db761 --- /dev/null +++ b/drivers/iio/adc/ti-ads112c04.c @@ -0,0 +1,524 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Texas Instruments ADS112C04 16-bit I2C ADC driver + * + * Copyright (c) 2026 Kyle Hsieh <kylehsieh1995@gmail.com> + * + * Datasheet: https://www.ti.com/lit/ds/symlink/ads112c04.pdf + * Based on TI Reference Code and standard Linux IIO framework. + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <linux/iio/iio.h> + +#define ADS112C04_CMD_RESET 0x06 +#define ADS112C04_CMD_START_SYNC 0x08 +#define ADS112C04_CMD_RDATA 0x10 +#define ADS112C04_CMD_RREG(reg) (0x20 | ((reg) << 2)) +#define ADS112C04_CMD_WREG(reg) (0x40 | ((reg) << 2)) + +#define ADS112C04_REG_CONFIG0 0x00 +#define ADS112C04_CONF0_MUX GENMASK(7, 4) +#define ADS112C04_CONF0_MUX_AIN0_AIN1 0 +#define ADS112C04_CONF0_MUX_AIN_SINGLE_BASE 8 +#define ADS112C04_CONF0_GAIN GENMASK(3, 1) +#define ADS112C04_CONF0_GAIN_X1 0 +#define ADS112C04_CONF0_PGA_BYPASS BIT(0) + +#define ADS112C04_REG_CONFIG1 0x01 +#define ADS112C04_CONF1_DR GENMASK(7, 5) +#define ADS112C04_CONF1_DR_20SPS 0 +#define ADS112C04_CONF1_MODE BIT(4) +#define ADS112C04_CONF1_MODE_NORMAL 0 +#define ADS112C04_CONF1_CM BIT(3) +#define ADS112C04_CONF1_CM_SINGLE_SHOT 0 +#define ADS112C04_CONF1_VREF GENMASK(2, 1) +#define ADS112C04_CONF1_VREF_INTERNAL 0 +#define ADS112C04_CONF1_VREF_EXTERNAL 1 +#define ADS112C04_CONF1_VREF_AVDD 2 +#define ADS112C04_CONF1_TS BIT(0) +#define ADS112C04_CONF1_TS_DISABLED 0 + +#define ADS112C04_REG_CONFIG2 0x02 +#define ADS112C04_CONF2_DRDY BIT(7) + +#define ADS112C04_INT_REF_mV 2048 + +#define ADS112C04_MAX_CHANNELS 12 + +#define ADS112C04_RESOLUTION_BITS 16 + +enum { + ADS112C04_VREF_SOURCE_INTERNAL, + ADS112C04_VREF_SOURCE_EXTERNAL, + ADS112C04_VREF_SOURCE_AVDD, +}; + +static const char * const ads112c04_vref_names[] = { + [ADS112C04_VREF_SOURCE_INTERNAL] = "internal", + [ADS112C04_VREF_SOURCE_EXTERNAL] = "external", + [ADS112C04_VREF_SOURCE_AVDD] = "avdd", +}; + +static const u8 ads112c04_vref_reg_val[] = { + [ADS112C04_VREF_SOURCE_INTERNAL] = ADS112C04_CONF1_VREF_INTERNAL, + [ADS112C04_VREF_SOURCE_EXTERNAL] = ADS112C04_CONF1_VREF_EXTERNAL, + [ADS112C04_VREF_SOURCE_AVDD] = ADS112C04_CONF1_VREF_AVDD, +}; + +/* Indexed by [AINP][AINN], -1 means the combination is not available. */ +static const s8 ads112c04_diff_mux[4][4] = { + { -1, 0, 1, 2 }, + { 3, -1, 4, 5 }, + { -1, -1, -1, 6 }, + { -1, -1, 7, -1 }, +}; + +struct ads112c04_state { + struct i2c_client *client; + /* Protects concurrent ADC reads and device configuration */ + struct mutex lock; + struct completion completion; + u32 avdd_mV; + u32 ext_ref_mV; + u8 *vref_source; /* one entry per channel, indexed by scan order */ + u8 config0; + u8 config1; +}; + +static int ads112c04_write_cmd(struct i2c_client *client, u8 cmd) +{ + return i2c_smbus_write_byte(client, cmd); +} + +static int ads112c04_read_reg(struct i2c_client *client, u8 reg, u8 *val) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, ADS112C04_CMD_RREG(reg)); + if (ret < 0) + return ret; + + *val = ret; + + return 0; +} + +static int ads112c04_write_reg(struct i2c_client *client, u8 reg, u8 val) +{ + return i2c_smbus_write_byte_data(client, ADS112C04_CMD_WREG(reg), val); +} + +static int ads112c04_wait_for_data(struct ads112c04_state *st) +{ + int ret, err; + u8 val; + + if (st->client->irq > 0) { + /* Timeout is 100ms (slowest data rate is 20 SPS) */ + if (!wait_for_completion_timeout(&st->completion, msecs_to_jiffies(100))) + return -ETIMEDOUT; + + return 0; + } + + ret = read_poll_timeout(ads112c04_read_reg, err, + err < 0 || (val & ADS112C04_CONF2_DRDY), + 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC, false, + st->client, ADS112C04_REG_CONFIG2, &val); + if (err < 0) + return err; + + return ret; +} + +static int ads112c04_read_data(struct ads112c04_state *st, int *val) +{ + int ret; + + ret = i2c_smbus_read_word_swapped(st->client, ADS112C04_CMD_RDATA); + if (ret < 0) + return ret; + + *val = sign_extend32(ret, ADS112C04_RESOLUTION_BITS - 1); + + return 0; +} + +static int ads112c04_get_adc_result(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct ads112c04_state *st = iio_priv(indio_dev); + unsigned int idx = chan - indio_dev->channels; + u8 new_config0, new_config1; + int ret; + + new_config0 = st->config0; + FIELD_MODIFY(ADS112C04_CONF0_MUX, &new_config0, chan->address); + + if (st->config0 != new_config0) { + ret = ads112c04_write_reg(st->client, ADS112C04_REG_CONFIG0, + new_config0); + if (ret < 0) + return ret; + + st->config0 = new_config0; + } + + new_config1 = st->config1; + FIELD_MODIFY(ADS112C04_CONF1_VREF, &new_config1, + ads112c04_vref_reg_val[st->vref_source[idx]]); + + if (st->config1 != new_config1) { + ret = ads112c04_write_reg(st->client, ADS112C04_REG_CONFIG1, + new_config1); + if (ret < 0) + return ret; + + st->config1 = new_config1; + } + + reinit_completion(&st->completion); + + ret = ads112c04_write_cmd(st->client, ADS112C04_CMD_START_SYNC); + if (ret < 0) + return ret; + + ret = ads112c04_wait_for_data(st); + if (ret < 0) + return ret; + + return ads112c04_read_data(st, val); +} + +static int ads112c04_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ads112c04_state *st = iio_priv(indio_dev); + unsigned int idx = chan - indio_dev->channels; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + ret = ads112c04_get_adc_result(indio_dev, chan, val); + mutex_unlock(&st->lock); + + if (ret < 0) + return ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (st->vref_source[idx]) { + case ADS112C04_VREF_SOURCE_EXTERNAL: + *val = st->ext_ref_mV; + break; + case ADS112C04_VREF_SOURCE_AVDD: + *val = st->avdd_mV; + break; + default: + *val = ADS112C04_INT_REF_mV; + break; + } + *val2 = ADS112C04_RESOLUTION_BITS - 1; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static irqreturn_t ads112c04_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ads112c04_state *st = iio_priv(indio_dev); + + complete(&st->completion); + + return IRQ_HANDLED; +} + +static const struct iio_info ads112c04_info = { + .read_raw = ads112c04_read_raw, +}; + +static int ads112c04_parse_vref_source(struct fwnode_handle *child) +{ + if (!fwnode_property_present(child, "reference-sources")) + return ADS112C04_VREF_SOURCE_INTERNAL; + + return fwnode_property_match_property_string(child, "reference-sources", + ads112c04_vref_names, + ARRAY_SIZE(ads112c04_vref_names)); +} + +static int ads112c04_parse_channels(struct iio_dev *indio_dev, + bool *need_avdd_ref, bool *need_ext_ref) +{ + struct device *dev = indio_dev->dev.parent; + struct ads112c04_state *st = iio_priv(indio_dev); + struct iio_chan_spec *channels; + u32 num_channels, pair[2], channel; + unsigned int i; + int ret; + + num_channels = device_get_named_child_node_count(dev, "channel"); + if (!num_channels) + return dev_err_probe(dev, -EINVAL, "no channel subnodes found\n"); + + if (num_channels > ADS112C04_MAX_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "num of channel nodes exceeds %d\n", + ADS112C04_MAX_CHANNELS); + + channels = devm_kcalloc(dev, num_channels, sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + st->vref_source = devm_kcalloc(dev, num_channels, + sizeof(*st->vref_source), GFP_KERNEL); + if (!st->vref_source) + return -ENOMEM; + + i = 0; + device_for_each_named_child_node_scoped(dev, child, "channel") { + const char *s_chan = "single-channel", *d_chan = "diff-channels"; + struct iio_chan_spec *spec = &channels[i]; + + if (fwnode_property_present(child, "excitation-channels")) + return dev_err_probe(dev, -EOPNOTSUPP, + "excitation-channels is not supported yet\n"); + + ret = ads112c04_parse_vref_source(child); + if (ret < 0) + return dev_err_probe(dev, ret, + "invalid reference-sources value\n"); + + st->vref_source[i] = ret; + + *need_ext_ref |= st->vref_source[i] == ADS112C04_VREF_SOURCE_EXTERNAL; + *need_avdd_ref |= st->vref_source[i] == ADS112C04_VREF_SOURCE_AVDD; + + /* + * REVISIT: when ti,refp-refn-resistor-ohms is implemented, a + * channel using an external resistor reference is effectively + * a resistance measurement and should use IIO_RESISTANCE. + */ + spec->type = IIO_VOLTAGE; + spec->indexed = 1; + spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE); + + if (fwnode_property_present(child, s_chan)) { + ret = fwnode_property_read_u32(child, s_chan, &channel); + if (ret) + return dev_err_probe(dev, ret, + "failed to read %s property\n", s_chan); + + if (channel > 3) + return dev_err_probe(dev, -EINVAL, "%s must be 0-3\n", s_chan); + + spec->channel = channel; + spec->address = ADS112C04_CONF0_MUX_AIN_SINGLE_BASE + channel; + } else if (fwnode_property_present(child, d_chan)) { + ret = fwnode_property_read_u32_array(child, d_chan, pair, ARRAY_SIZE(pair)); + if (ret) + return dev_err_probe(dev, ret, + "failed to read %s property\n", d_chan); + + if (pair[0] > 3 || pair[1] > 3) + return dev_err_probe(dev, -EINVAL, "%s must be 0-3\n", d_chan); + + spec->channel = pair[0]; + spec->channel2 = pair[1]; + spec->differential = 1; + + if (ads112c04_diff_mux[pair[0]][pair[1]] < 0) + return dev_err_probe(dev, -EINVAL, + "invalid %s combination\n", d_chan); + + spec->address = ads112c04_diff_mux[pair[0]][pair[1]]; + } else { + return dev_err_probe(dev, -EINVAL, + "channel node must have %s or %s\n", s_chan, d_chan); + } + + i++; + } + + indio_dev->channels = channels; + indio_dev->num_channels = i; + + return 0; +} + +static int ads112c04_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct ads112c04_state *st; + struct reset_control *reset; + bool need_avdd_ref = false, need_ext_ref = false; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->client = client; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + init_completion(&st->completion); + + indio_dev->name = "ads112c04"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ads112c04_info; + + /* Forward compatibility checks for unimplemented DT properties */ + if (device_property_present(dev, "refn-supply") || + device_property_present(dev, "ti,refp-refn-resistor-ohms")) + return dev_err_probe(dev, -EOPNOTSUPP, + "refn-supply and external resistors are not supported yet\n"); + + ret = ads112c04_parse_channels(indio_dev, &need_avdd_ref, &need_ext_ref); + if (ret) + return ret; + + if (need_avdd_ref) { + ret = devm_regulator_get_enable_read_voltage(dev, "avdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get avdd voltage\n"); + + st->avdd_mV = ret / (MICRO / MILLI); + } else { + ret = devm_regulator_get_enable(dev, "avdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to get avdd regulator\n"); + } + + ret = devm_regulator_get_enable(dev, "dvdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to get dvdd regulator\n"); + + if (device_property_present(dev, "refp-supply")) { + ret = devm_regulator_get_enable_read_voltage(dev, "refp"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get refp voltage\n"); + + st->ext_ref_mV = ret / (MICRO / MILLI); + } + + if (need_ext_ref && !st->ext_ref_mV) + return dev_err_probe(dev, -EINVAL, + "external reference measurements require refp-supply\n"); + + /* Datasheet: POR releases ~500us after supplies are stable */ + fsleep(500); + + reset = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), "failed to get reset\n"); + + if (reset) { + /* Datasheet: tw(RSL), the RESET low pulse, is 250ns minimum */ + fsleep(1); + + ret = reset_control_deassert(reset); + if (ret) + return dev_err_probe(dev, ret, "failed to deassert reset\n"); + } else { + ret = ads112c04_write_cmd(client, ADS112C04_CMD_RESET); + if (ret < 0) + return ret; + } + + /* Datasheet: td(RSSTA) is 100ns minimum after the RESET rising edge */ + fsleep(1); + + /* + * Initialize CONFIG0 with all fields explicit: gain of 1 with the PGA + * bypassed, which allows full-scale single-ended measurements. The MUX + * field is updated per channel before each conversion. + */ + st->config0 = FIELD_PREP(ADS112C04_CONF0_MUX, + ADS112C04_CONF0_MUX_AIN0_AIN1) | + FIELD_PREP(ADS112C04_CONF0_GAIN, + ADS112C04_CONF0_GAIN_X1) | + ADS112C04_CONF0_PGA_BYPASS; + + ret = ads112c04_write_reg(client, ADS112C04_REG_CONFIG0, st->config0); + if (ret) + return ret; + + st->config1 = FIELD_PREP(ADS112C04_CONF1_DR, + ADS112C04_CONF1_DR_20SPS) | + FIELD_PREP(ADS112C04_CONF1_MODE, + ADS112C04_CONF1_MODE_NORMAL) | + FIELD_PREP(ADS112C04_CONF1_CM, + ADS112C04_CONF1_CM_SINGLE_SHOT) | + FIELD_PREP(ADS112C04_CONF1_VREF, + ADS112C04_CONF1_VREF_INTERNAL) | + FIELD_PREP(ADS112C04_CONF1_TS, + ADS112C04_CONF1_TS_DISABLED); + + ret = ads112c04_write_reg(client, ADS112C04_REG_CONFIG1, st->config1); + if (ret) + return ret; + + if (client->irq > 0) { + ret = devm_request_irq(dev, client->irq, ads112c04_irq_handler, 0, + indio_dev->name, indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id ads112c04_id[] = { + { .name = "ads112c04" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ads112c04_id); + +static const struct of_device_id ads112c04_of_match[] = { + { .compatible = "ti,ads112c04" }, + { } +}; +MODULE_DEVICE_TABLE(of, ads112c04_of_match); + +static struct i2c_driver ads112c04_driver = { + .driver = { + .name = "ads112c04", + .of_match_table = ads112c04_of_match, + }, + .probe = ads112c04_probe, + .id_table = ads112c04_id, +}; +module_i2c_driver(ads112c04_driver); + +MODULE_AUTHOR("Kyle Hsieh <kylehsieh1995@gmail.com>"); +MODULE_DESCRIPTION("Texas Instruments ADS112C04 ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c index 60eab4852ba2..3c877126b0be 100644 --- a/drivers/iio/adc/ti-ads112c14.c +++ b/drivers/iio/adc/ti-ads112c14.c @@ -9,7 +9,10 @@ */ #include <linux/bitfield.h> +#include <linux/bitmap.h> #include <linux/cleanup.h> +#include <linux/clk.h> +#include <linux/completion.h> #include <linux/crc8.h> #include <linux/delay.h> #include <linux/dev_printk.h> @@ -17,8 +20,10 @@ #include <linux/i2c.h> #include <linux/iio/buffer.h> #include <linux/iio/iio.h> +#include <linux/iio/trigger.h> #include <linux/iio/trigger_consumer.h> #include <linux/iio/triggered_buffer.h> +#include <linux/interrupt.h> #include <linux/math64.h> #include <linux/minmax.h> #include <linux/module.h> @@ -72,7 +77,14 @@ #define ADS112C14_DEVICE_CFG_PWDN BIT(7) #define ADS112C14_DEVICE_CFG_STBY_MODE BIT(6) #define ADS112C14_DEVICE_CFG_BOCS GENMASK(5, 4) +#define ADS112C14_DEVICE_CFG_BOCS_DISABLED 0 +#define ADS112C14_DEVICE_CFG_BOCS_200_nA 1 +#define ADS112C14_DEVICE_CFG_BOCS_1_uA 2 +#define ADS112C14_DEVICE_CFG_BOCS_10_uA 3 + #define ADS112C14_DEVICE_CFG_CLK_SEL BIT(3) +#define ADS112C14_DEVICE_CFG_CLK_SEL_INTERNAL 0 +#define ADS112C14_DEVICE_CFG_CLK_SEL_EXTERNAL 1 #define ADS112C14_DEVICE_CFG_CONV_MODE BIT(2) #define ADS112C14_DEVICE_CFG_CONV_MODE_CONTINUOUS 0 #define ADS112C14_DEVICE_CFG_CONV_MODE_SINGLE_SHOT 1 @@ -82,6 +94,14 @@ #define ADS112C14_DATA_RATE_CFG_DELAY GENMASK(7, 4) #define ADS112C14_DATA_RATE_CFG_GC_EN BIT(3) #define ADS112C14_DATA_RATE_CFG_FLTR_OSR GENMASK(2, 0) +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_16 0 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_32 1 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_128 2 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_256 3 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_512 4 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024 5 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS 6 +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS 7 #define ADS112C14_REG_MUX_CFG 0x07 #define ADS112C14_MUX_CFG_AINP GENMASK(7, 4) @@ -117,9 +137,15 @@ #define ADS112C14_GPIO_CFG_GPIO2_CFG GENMASK(5, 4) #define ADS112C14_GPIO_CFG_GPIO1_CFG GENMASK(3, 2) #define ADS112C14_GPIO_CFG_GPIO0_CFG GENMASK(1, 0) +#define ADS112C14_GPIO_CFG_GPIO_CFG_DISABLED 0 +#define ADS112C14_GPIO_CFG_GPIO_CFG_INPUT 1 +#define ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_PUSH_PULL 2 +#define ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_OPEN_DRAIN 3 #define ADS112C14_REG_GPIO_DATA_OUTPUT 0x0C #define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC BIT(7) +#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC_DAT_OUT 0 +#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC_DRDY 1 #define ADS112C14_GPIO_DATA_OUTPUT_GPIO2_SRC BIT(6) #define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_DAT_OUT BIT(3) #define ADS112C14_GPIO_DATA_OUTPUT_GPIO2_DAT_OUT BIT(2) @@ -163,6 +189,63 @@ static const u32 ads112c14_pga_gains_x10[] = { 200, 320, 500, 640, 1000, 1280, 2000, 2560, /* 8 - 15 */ }; +#define ADS112C14_INTERNAL_CLK_Hz (4096 * HZ_PER_KHZ) + +/* Index corresponds to first 2 ADS112C14_DATA_RATE_CFG_FLTR_OSR values. */ +static const int ads112c14_sinc4_osr_available[] = { + 16, 32 +}; + +/* Index corresponds to next 4 ADS112C14_DATA_RATE_CFG_FLTR_OSR values. */ +static const int ads112c14_sinc4_sinc1_osr_available[] = { + 128, 256, 512, 1024 +}; + +/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */ +static const int ads112c14_sinc4_sinc1_pf1_20sps_osr_available[] = { + 1600, 12800, 25600, 51200 +}; + +/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */ +static const int ads112c14_sinc4_sinc1_pf1_25sps_osr_available[] = { + 1280, 10240, 20480, 40960 +}; + +/* Index corresponds to ADS112C14_DEVICE_CFG_SPEED_MODE value. */ +static const int ads112c14_fmod_div[] = { + 128, 16, 8, 4 +}; + +#define ADS112C14_DELAY_MAX FIELD_MAX(ADS112C14_DATA_RATE_CFG_DELAY) + +/* Table 7-6 latency in t_MOD for OSR [16, 32, 128, 256, 512, 1024]. */ +static const int ads112c14_sinc_latency_tmod[][ARRAY_SIZE(ads112c14_fmod_div)] = { + { 80, 88, 88, 104 }, + { 144, 152, 152, 168 }, + { 240, 248, 248, 264 }, + { 368, 376, 376, 392 }, + { 624, 632, 632, 648 }, + { 1136, 1144, 1144, 1160 }, +}; + +/* Table 7-7 latency in t_MOD for output data rates [25SPS, 20SPS]. */ +static const int ads112c14_fir_latency_tmod[][ARRAY_SIZE(ads112c14_fmod_div)] = { + { 1416, 10384, 20624, 41120 }, + { 1736, 12944, 25744, 51360 }, +}; + +enum ads112c14_filter_type { + ADS112C14_FILTER_TYPE_SINC4, + ADS112C14_FILTER_TYPE_SINC4_SINC1, + ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1, +}; + +static const char * const ads112c14_filter_type_names[] = { + [ADS112C14_FILTER_TYPE_SINC4] = "sinc4", + [ADS112C14_FILTER_TYPE_SINC4_SINC1] = "sinc4+sinc1", + [ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1] = "sinc4+sinc1+pf1", +}; + #define ADS112C14_I2C_CRC8_POLYNOMIAL 0x07 DECLARE_CRC8_TABLE(ads112c14_crc8_table); @@ -184,6 +267,8 @@ enum { ADS112C14_SYS_MON_CHANNEL_SHORT, }; +static const struct iio_chan_spec_ext_info ads112c14_ext_info[]; + static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { { .type = IIO_TEMP, @@ -192,7 +277,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { .address = 2, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) - | BIT(IIO_CHAN_INFO_OFFSET), + | BIT(IIO_CHAN_INFO_OFFSET) + | BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .ext_info = ads112c14_ext_info, }, { .type = IIO_VOLTAGE, @@ -200,7 +290,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { .channel = ADS112C14_SYS_MON_CHANNEL_EXT_REF, .address = 3, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) - | BIT(IIO_CHAN_INFO_SCALE), + | BIT(IIO_CHAN_INFO_SCALE) + | BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .ext_info = ads112c14_ext_info, }, { .type = IIO_VOLTAGE, @@ -208,7 +303,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { .channel = ADS112C14_SYS_MON_CHANNEL_AVDD, .address = 4, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) - | BIT(IIO_CHAN_INFO_SCALE), + | BIT(IIO_CHAN_INFO_SCALE) + | BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .ext_info = ads112c14_ext_info, }, { .type = IIO_VOLTAGE, @@ -216,7 +316,12 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { .channel = ADS112C14_SYS_MON_CHANNEL_DVDD, .address = 5, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) - | BIT(IIO_CHAN_INFO_SCALE), + | BIT(IIO_CHAN_INFO_SCALE) + | BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .ext_info = ads112c14_ext_info, }, { .type = IIO_VOLTAGE, @@ -226,8 +331,13 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { .differential = 1, .address = 1, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) - | BIT(IIO_CHAN_INFO_SCALE), - .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + | BIT(IIO_CHAN_INFO_SCALE) + | BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) + | BIT(IIO_CHAN_INFO_SAMP_FREQ) + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .ext_info = ads112c14_ext_info, }, }; @@ -241,16 +351,28 @@ struct ads112c14_measurement { u8 idac2_mux; u8 iadc_count; u8 gain_val; + u8 burnout; bool global_chop; bool bipolar; int scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2]; }; +struct ads112c14_channel_state { + u8 speed_mode; + u8 filter_osr; + u8 delay; +}; + struct ads112c14_data { const struct ads112c14_chip_info *chip_info; struct regmap *regmap; + struct iio_trigger *drdy_trig; /* Synchronizes access to register value fields. */ struct mutex lock; + long fclk_Hz; + int drdy_irq; + struct completion drdy_completion; + bool continuous_mode; bool i2c_crc_enabled; u32 avdd_uV; u32 ext_ref_uV; @@ -258,13 +380,36 @@ struct ads112c14_data { bool refn_is_gnd; u32 ext_ref_ohms; struct ads112c14_measurement *measurements; + struct ads112c14_channel_state *channel_states; u32 num_measurements; u8 sys_mon_chan_short_gain_val; int sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2]; + int sinc4_sample_rate_available[ARRAY_SIZE(ads112c14_sinc4_osr_available)][ARRAY_SIZE(ads112c14_fmod_div)][2]; + int sinc4_sinc1_sample_rate_available[ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available)][ARRAY_SIZE(ads112c14_fmod_div)][2]; + int sinc4_sinc1_pf1_sample_rate_available[2][2]; + int sinc_settling_time_range_available[ARRAY_SIZE(ads112c14_sinc_latency_tmod)][ARRAY_SIZE(ads112c14_fmod_div)][3][2]; + int fir_settling_time_range_available[ARRAY_SIZE(ads112c14_fir_latency_tmod)][ARRAY_SIZE(ads112c14_fmod_div)][3][2]; IIO_DECLARE_BUFFER_WITH_TS(__be32, scan, ADS112C14_MAX_MEASUREMENT_CHANNELS + ARRAY_SIZE(ads112c14_sys_mon_channels)); }; +static irqreturn_t ads112c14_drdy_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ads112c14_data *data = iio_priv(indio_dev); + + if (READ_ONCE(data->continuous_mode)) + iio_trigger_poll(data->drdy_trig); + else + complete(&data->drdy_completion); + + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops ads112c14_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -429,13 +574,250 @@ static const struct regmap_config ads112c14_regmap_config = { .cache_type = REGCACHE_MAPLE, }; +static int ads112c14_get_osr(struct ads112c14_channel_state *channel_state) +{ + u8 i; + + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32: + i = channel_state->filter_osr; + return ads112c14_sinc4_osr_available[i]; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + i = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128; + return ads112c14_sinc4_sinc1_osr_available[i]; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS: + i = channel_state->speed_mode; + return ads112c14_sinc4_sinc1_pf1_25sps_osr_available[i]; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: + i = channel_state->speed_mode; + return ads112c14_sinc4_sinc1_pf1_20sps_osr_available[i]; + default: + return -EINVAL; + } +} + +static int ads112c14_get_fmod_Hz(struct ads112c14_data *data, + struct ads112c14_channel_state *channel_state) +{ + return data->fclk_Hz / ads112c14_fmod_div[channel_state->speed_mode]; +} + +static int ads112c14_delay_to_tmod(u8 delay) +{ + if (!delay) + return 0; + + return BIT(delay - 1); +} + +static int ads112c14_get_latency_tmod(struct ads112c14_channel_state *channel_state) +{ + u8 speed_mode = channel_state->speed_mode; + u8 filter_osr = channel_state->filter_osr; + + if (speed_mode >= ARRAY_SIZE(ads112c14_fmod_div)) + return -EINVAL; + + if (filter_osr <= ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024) + return ads112c14_sinc_latency_tmod[filter_osr][speed_mode]; + + if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS) + return ads112c14_fir_latency_tmod[0][speed_mode]; + + if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS) + return ads112c14_fir_latency_tmod[1][speed_mode]; + + return -EINVAL; +} + +static int ads112c14_get_settling_time_us(struct ads112c14_data *data, + struct ads112c14_channel_state *channel_state, + u8 delay, u32 *settling_time_us) +{ + int fmod_Hz, latency_tmod; + u64 total_tmod; + + fmod_Hz = ads112c14_get_fmod_Hz(data, channel_state); + if (fmod_Hz <= 0) + return -EINVAL; + + latency_tmod = ads112c14_get_latency_tmod(channel_state); + if (latency_tmod < 0) + return latency_tmod; + + total_tmod = latency_tmod + ads112c14_delay_to_tmod(delay); + *settling_time_us = div64_u64(total_tmod * USEC_PER_SEC, fmod_Hz); + + return 0; +} + +static int ads112c14_find_delay_for_settling_time_us(struct ads112c14_data *data, + struct ads112c14_channel_state *channel_state, + s64 settling_time_us, u8 *delay) +{ + u64 delay_us, delay_tmod_needed; + u32 fixed_latency_us; + int ret, fmod_Hz; + u8 i; + + ret = ads112c14_get_settling_time_us(data, channel_state, 0, &fixed_latency_us); + if (ret) + return ret; + + if (settling_time_us <= fixed_latency_us) { + *delay = 0; + return 0; + } + + fmod_Hz = ads112c14_get_fmod_Hz(data, channel_state); + if (fmod_Hz <= 0) + return -EINVAL; + + delay_us = settling_time_us - fixed_latency_us; + delay_tmod_needed = DIV_ROUND_UP_ULL(delay_us * fmod_Hz, USEC_PER_SEC); + + for (i = 1; i < ADS112C14_DELAY_MAX; i++) { + if (ads112c14_delay_to_tmod(i) >= delay_tmod_needed) + break; + } + + *delay = i; + + return 0; +} + +static ssize_t ads112c14_read_settling_time(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_channel_state *channel_state; + u32 settling_time_us; + int vals[2]; + int ret; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + ret = ads112c14_get_settling_time_us(data, channel_state, + channel_state->delay, + &settling_time_us); + if (ret) + return ret; + + iio_val_s64_decompose(settling_time_us, &vals[0], &vals[1]); + + return iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(vals), vals); +} + +static ssize_t ads112c14_write_settling_time(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_channel_state *channel_state; + s64 settling_time_us; + int integer; + int fract; + u8 delay; + int ret; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract); + if (ret) + return ret; + + settling_time_us = integer * MICRO + fract; + if (settling_time_us < 0) + return -EINVAL; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + ret = ads112c14_find_delay_for_settling_time_us(data, channel_state, + settling_time_us, &delay); + if (ret) + return ret; + + channel_state->delay = delay; + + return len; +} + +static ssize_t ads112c14_read_settling_time_available(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_channel_state *channel_state; + u8 filter_osr, speed_mode; + const int (*range)[2]; + size_t len = 0; + int i; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + filter_osr = channel_state->filter_osr; + speed_mode = channel_state->speed_mode; + + if (speed_mode >= ARRAY_SIZE(ads112c14_fmod_div)) + return -EINVAL; + + if (filter_osr <= ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024) + range = data->sinc_settling_time_range_available[filter_osr][speed_mode]; + else if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS) + range = data->fir_settling_time_range_available[0][speed_mode]; + else if (filter_osr == ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS) + range = data->fir_settling_time_range_available[1][speed_mode]; + else + return -EINVAL; + + len += sysfs_emit_at(buf, len, "["); + for (i = 0; i < 3; i++) { + s64 range_val; + s32 int_val, rem; + + range_val = iio_val_s64_compose(range[i][0], range[i][1]); + int_val = div_s64_rem(range_val, MICRO, &rem); + + if (i) + len += sysfs_emit_at(buf, len, " "); + + len += sysfs_emit_at(buf, len, "%d.%06d", int_val, rem); + } + len += sysfs_emit_at(buf, len, "]\n"); + + return len; +} + static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data, - const struct iio_chan_spec *chan) + const struct iio_chan_spec *chan, + bool en_burnout) { struct ads112c14_measurement *measurement = &data->measurements[chan->scan_index]; + struct ads112c14_channel_state *channel_state; u32 refp_buf_en, refn_buf_en, ref_val, ref_sel; int ret; + channel_state = &data->channel_states[chan->scan_index]; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_SPEED_MODE, + FIELD_PREP(ADS112C14_DEVICE_CFG_SPEED_MODE, + channel_state->speed_mode)); + if (ret) + return ret; + ret = regmap_update_bits(data->regmap, ADS112C14_REG_MUX_CFG, ADS112C14_MUX_CFG_AINP | ADS112C14_MUX_CFG_AINN, FIELD_PREP(ADS112C14_MUX_CFG_AINP, chan->channel) | @@ -482,9 +864,16 @@ static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data, return ret; ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG, - ADS112C14_DATA_RATE_CFG_GC_EN, + ADS112C14_DATA_RATE_CFG_DELAY | + ADS112C14_DATA_RATE_CFG_GC_EN | + ADS112C14_DATA_RATE_CFG_FLTR_OSR, + FIELD_PREP(ADS112C14_DATA_RATE_CFG_DELAY, + channel_state->delay) | FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN, - measurement->global_chop)); + (measurement->global_chop && + !en_burnout) ? 1 : 0) | + FIELD_PREP(ADS112C14_DATA_RATE_CFG_FLTR_OSR, + channel_state->filter_osr)); if (ret) return ret; @@ -527,9 +916,12 @@ static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data, static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data, const struct iio_chan_spec *chan) { + struct ads112c14_channel_state *channel_state; u32 gain_val; int ret; + channel_state = &data->channel_states[chan->scan_index]; + /* * NB: IDAC registers are left as-is in case they are generating current * needed for the external reference measurement. @@ -556,6 +948,25 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data, if (ret) return ret; + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_SPEED_MODE, + FIELD_PREP(ADS112C14_DEVICE_CFG_SPEED_MODE, + channel_state->speed_mode)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG, + ADS112C14_DATA_RATE_CFG_DELAY | + ADS112C14_DATA_RATE_CFG_GC_EN | + ADS112C14_DATA_RATE_CFG_FLTR_OSR, + FIELD_PREP(ADS112C14_DATA_RATE_CFG_DELAY, + channel_state->delay) | + FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN, 0) | + FIELD_PREP(ADS112C14_DATA_RATE_CFG_FLTR_OSR, + channel_state->filter_osr)); + if (ret) + return ret; + /* * REVISIT: if we implement regulator support for the REFOUT pin, we * might need to make this voltage match what is required by that. In @@ -581,35 +992,108 @@ static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data, return 0; } +static int ads112c14_prepare_channel(struct ads112c14_data *data, + const struct iio_chan_spec *chan, + bool en_burnout) +{ + if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) + return ads112c14_prepare_measurement_channel(data, chan, en_burnout); + + return ads112c14_prepare_sys_mon_channel(data, chan); +} + +static int ads112c14_scan_read(struct ads112c14_data *data, u8 *buf) +{ + struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap)); + int ret; + u8 len; + + len = BITS_TO_BYTES(data->chip_info->resolution_bits); + if (data->i2c_crc_enabled) + len += 1; + + ret = i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA, len, buf); + if (ret < 0) + return ret; + + return 0; +} + +static int ads112c14_wait_for_conversion_irq(struct ads112c14_data *data, + u32 settle_time_us) +{ + unsigned long remaining; + int ret; + + reinit_completion(&data->drdy_completion); + + ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL, + ADS112C14_CONVERSION_CTRL_START); + if (ret) + return ret; + + /* Give it 1ms more than calculated settling time. */ + remaining = wait_for_completion_timeout(&data->drdy_completion, + usecs_to_jiffies(settle_time_us + + 1 * USEC_PER_MSEC)); + + return remaining ? 0 : -ETIMEDOUT; +} + +static int ads112c14_wait_for_conversion_poll(struct ads112c14_data *data, + u32 settle_time_us) +{ + u32 reg_val; + int ret; + + ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL, + ADS112C14_CONVERSION_CTRL_START); + if (ret) + return ret; + + /* Give it 1ms more than calculated settling time. */ + return regmap_read_poll_timeout(data->regmap, + ADS112C14_REG_STATUS_MSB, reg_val, + FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val), + 1 * USEC_PER_MSEC, + settle_time_us + 1 * USEC_PER_MSEC); +} + static int ads112c14_single_conversion(struct ads112c14_data *data, const struct iio_chan_spec *chan, - u8 *buf, bool for_scan) + u8 *buf, bool en_burnout, bool for_scan) { struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap)); - u32 reg_val; + struct ads112c14_channel_state *channel_state; + u32 settle_time_us; int ret; guard(mutex)(&data->lock); - if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) { - ret = ads112c14_prepare_measurement_channel(data, chan); - if (ret) - return ret; - } else { - ret = ads112c14_prepare_sys_mon_channel(data, chan); - if (ret) - return ret; - } + ret = ads112c14_prepare_channel(data, chan, en_burnout); + if (ret) + return ret; - ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL, - ADS112C14_CONVERSION_CTRL_START); + channel_state = &data->channel_states[chan->scan_index]; + ret = ads112c14_get_settling_time_us(data, channel_state, + channel_state->delay, + &settle_time_us); if (ret) return ret; - ret = regmap_read_poll_timeout(data->regmap, - ADS112C14_REG_STATUS_MSB, reg_val, - FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val), - 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC); + ret = regmap_test_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG, + ADS112C14_DATA_RATE_CFG_GC_EN); + if (ret < 0) + return ret; + + /* Input chopping doubles the settling time. */ + if (ret) + settle_time_us *= 2; + + if (data->drdy_irq) + ret = ads112c14_wait_for_conversion_irq(data, settle_time_us); + else + ret = ads112c14_wait_for_conversion_poll(data, settle_time_us); if (ret) return ret; @@ -619,23 +1103,21 @@ static int ads112c14_single_conversion(struct ads112c14_data *data, * with CRC errors, but rather leave it to userspace to decide what to * do. */ - if (for_scan) { - u8 len = BITS_TO_BYTES(data->chip_info->resolution_bits) + - (data->i2c_crc_enabled ? 1 : 0); - - ret = i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA, - len, buf); - if (ret < 0) - return ret; - - return 0; - } + if (for_scan) + return ads112c14_scan_read(data, buf); return ads112c14_i2c_read_bytes(client, ADS112C14_CMD_RDATA, buf, BITS_TO_BYTES(data->chip_info->resolution_bits), data->i2c_crc_enabled); } +static bool ads112c14_using_drdy_trigger(struct iio_dev *indio_dev) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + + return data->drdy_trig && indio_dev->trig == data->drdy_trig; +} + static int ads112c14_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -665,7 +1147,7 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev, if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; - ret = ads112c14_single_conversion(data, chan, buf, false); + ret = ads112c14_single_conversion(data, chan, buf, false, false); if (ret) return ret; @@ -743,6 +1225,45 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev, */ *val = div_s64((s64)(25 * 405 - 119500) * BIT(fsr_bits), vref_uV); return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: { + struct ads112c14_channel_state *channel_state; + const int (*available)[2]; + u8 i, j; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32: + j = channel_state->filter_osr; + available = data->sinc4_sample_rate_available[j]; + i = channel_state->speed_mode; + break; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + j = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128; + available = data->sinc4_sinc1_sample_rate_available[j]; + i = channel_state->speed_mode; + break; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS: + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: + available = data->sinc4_sinc1_pf1_sample_rate_available; + i = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS; + break; + default: + return -EINVAL; + } + + *val = available[i][0]; + *val2 = available[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + guard(mutex)(&data->lock); + + *val = ads112c14_get_osr(&data->channel_states[chan->scan_index]); + return IIO_VAL_INT; + } default: return -EINVAL; } @@ -753,6 +1274,9 @@ static int ads112c14_read_avail(struct iio_dev *indio_dev, int *type, int *length, long mask) { struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_channel_state *channel_state; + + channel_state = &data->channel_states[chan->scan_index]; switch (mask) { case IIO_CHAN_INFO_SCALE: @@ -778,6 +1302,58 @@ static int ads112c14_read_avail(struct iio_dev *indio_dev, } return -EINVAL; + + case IIO_CHAN_INFO_SAMP_FREQ: { + guard(mutex)(&data->lock); + + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32: + *vals = (const int *)data->sinc4_sample_rate_available[channel_state->filter_osr]; + *length = 2 * ARRAY_SIZE(data->sinc4_sample_rate_available[0]); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + *vals = (const int *)data->sinc4_sinc1_sample_rate_available[channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128]; + *length = 2 * ARRAY_SIZE(data->sinc4_sinc1_sample_rate_available[0]); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS...ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: + *vals = (const int *)data->sinc4_sinc1_pf1_sample_rate_available; + *length = 2 * ARRAY_SIZE(data->sinc4_sinc1_pf1_sample_rate_available); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + guard(mutex)(&data->lock); + + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32: + *vals = ads112c14_sinc4_osr_available; + *length = ARRAY_SIZE(ads112c14_sinc4_osr_available); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + *vals = ads112c14_sinc4_sinc1_osr_available; + *length = ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS: + *vals = ads112c14_sinc4_sinc1_pf1_25sps_osr_available; + *length = ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_25sps_osr_available); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: + *vals = ads112c14_sinc4_sinc1_pf1_20sps_osr_available; + *length = ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_20sps_osr_available); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + } default: return -EINVAL; } @@ -822,6 +1398,99 @@ static int ads112c14_write_raw(struct iio_dev *indio_dev, return -EINVAL; } + case IIO_CHAN_INFO_SAMP_FREQ: { + struct ads112c14_channel_state *channel_state; + const int (*available)[2]; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + if (channel_state->filter_osr < ADS112C14_DATA_RATE_CFG_FLTR_OSR_128) { + u8 idx = channel_state->filter_osr; + + available = data->sinc4_sample_rate_available[idx]; + } else { + u8 idx = channel_state->filter_osr - ADS112C14_DATA_RATE_CFG_FLTR_OSR_128; + + available = data->sinc4_sinc1_sample_rate_available[idx]; + } + + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_fmod_div); i++) { + if (val == available[i][0] && val2 == available[i][1]) { + channel_state->speed_mode = i; + return 0; + } + } + + return -EINVAL; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS: + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: { + available = data->sinc4_sinc1_pf1_sample_rate_available; + + for (unsigned int i = 0; i < ARRAY_SIZE(data->sinc4_sinc1_pf1_sample_rate_available); i++) { + if (val == available[i][0] && val2 == available[i][1]) { + channel_state->filter_osr = i + ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS; + return 0; + } + } + + return -EINVAL; + } + default: + return -EINVAL; + } + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + struct ads112c14_channel_state *channel_state; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32: + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_osr_available); i++) { + if (val == ads112c14_sinc4_osr_available[i]) { + channel_state->filter_osr = i; + return 0; + } + } + + return -EINVAL; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available); i++) { + if (val == ads112c14_sinc4_sinc1_osr_available[i]) { + channel_state->filter_osr = i + ADS112C14_DATA_RATE_CFG_FLTR_OSR_128; + return 0; + } + } + + return -EINVAL; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS: + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_25sps_osr_available); i++) { + if (val == ads112c14_sinc4_sinc1_pf1_25sps_osr_available[i]) { + channel_state->speed_mode = i; + return 0; + } + } + + return -EINVAL; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_pf1_20sps_osr_available); i++) { + if (val == ads112c14_sinc4_sinc1_pf1_20sps_osr_available[i]) { + channel_state->speed_mode = i; + return 0; + } + } + + return -EINVAL; + default: + return -EINVAL; + } + } default: return -EINVAL; } @@ -839,6 +1508,19 @@ static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev, } } +static int ads112c14_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + /* Only continuous mode is limited to a single channel. */ + if (!ads112c14_using_drdy_trigger(indio_dev)) + return 0; + + if (!iio_validate_scan_mask_onehot(indio_dev, scan_mask)) + return -EINVAL; + + return 0; +} + static int ads112c14_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, @@ -893,6 +1575,19 @@ static int ads112c14_read_label(struct iio_dev *indio_dev, return sysfs_emit(label, "%s\n", label_source); } +static const struct iio_chan_spec * +ads112c14_first_active_channel(struct iio_dev *indio_dev) +{ + unsigned int scan_mask_len = iio_get_masklength(indio_dev); + unsigned int i; + + i = find_first_bit(indio_dev->active_scan_mask, scan_mask_len); + if (i == scan_mask_len) + return NULL; + + return &indio_dev->channels[i]; +} + static irqreturn_t ads112c14_trigger_handler(int irq, void *private) { struct iio_poll_func *pf = private; @@ -902,12 +1597,32 @@ static irqreturn_t ads112c14_trigger_handler(int irq, void *private) u32 i; int ret; + if (ads112c14_using_drdy_trigger(indio_dev)) { + const struct iio_chan_spec *chan; + + chan = ads112c14_first_active_channel(indio_dev); + if (!chan) + goto out; + + ret = ads112c14_scan_read(data, (u8 *)&data->scan[0]); + if (ret) { + dev_err_once(indio_dev->dev.parent, + "failed to read channel %d: %pe; additional errors will be suppressed\n", + chan->channel, ERR_PTR(ret)); + goto out; + } + + iio_push_to_buffers_with_ts(indio_dev, data->scan, + sizeof(data->scan), pf->timestamp); + goto out; + } + iio_for_each_active_channel(indio_dev, i) { const struct iio_chan_spec *chan = &indio_dev->channels[i]; ret = ads112c14_single_conversion(data, chan, (u8 *)&data->scan[offset++], - true); + false, true); if (ret) { dev_err_once(indio_dev->dev.parent, "failed to read channel %d: %pe; additional errors will be suppressed\n", @@ -929,10 +1644,258 @@ static const struct iio_info ads112c14_info = { .read_avail = ads112c14_read_avail, .write_raw = ads112c14_write_raw, .write_raw_get_fmt = ads112c14_write_raw_get_fmt, + .update_scan_mode = ads112c14_update_scan_mode, .debugfs_reg_access = ads112c14_debugfs_reg_access, .read_label = ads112c14_read_label, }; +static int ads112c14_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + const struct iio_chan_spec *chan; + int ret; + + if (!ads112c14_using_drdy_trigger(indio_dev)) + return 0; + + chan = ads112c14_first_active_channel(indio_dev); + if (!chan) + return -EINVAL; + + guard(mutex)(&data->lock); + + ret = ads112c14_prepare_channel(data, chan, false); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_CONV_MODE, + FIELD_PREP(ADS112C14_DEVICE_CFG_CONV_MODE, + ADS112C14_DEVICE_CFG_CONV_MODE_CONTINUOUS)); + if (ret) + return ret; + + WRITE_ONCE(data->continuous_mode, true); + + ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL, + ADS112C14_CONVERSION_CTRL_START); + if (ret) { + WRITE_ONCE(data->continuous_mode, false); + regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_CONV_MODE, + FIELD_PREP(ADS112C14_DEVICE_CFG_CONV_MODE, + ADS112C14_DEVICE_CFG_CONV_MODE_SINGLE_SHOT)); + return ret; + } + + return 0; +} + +static int ads112c14_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + int ret; + + if (!ads112c14_using_drdy_trigger(indio_dev)) + return 0; + + guard(mutex)(&data->lock); + + WRITE_ONCE(data->continuous_mode, false); + + ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL, + ADS112C14_CONVERSION_CTRL_STOP); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_CONV_MODE, + FIELD_PREP(ADS112C14_DEVICE_CFG_CONV_MODE, + ADS112C14_DEVICE_CFG_CONV_MODE_SINGLE_SHOT)); +} + +static const struct iio_buffer_setup_ops ads112c14_buffer_setup_ops = { + .postenable = ads112c14_buffer_postenable, + .predisable = ads112c14_buffer_predisable, +}; + +static ssize_t ads112c14_read_burnout_raw(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *chan, + char *buf) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_measurement *measurement; + int ret, ret2, val; + u8 raw_buf[3]; + + if (chan->channel >= ADS112C14_SYS_MON_CHANNEL_BASE) + return -EINVAL; + + measurement = &data->measurements[chan->scan_index]; + if (!measurement->burnout) + return -EINVAL; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_BOCS, + FIELD_PREP(ADS112C14_DEVICE_CFG_BOCS, + measurement->burnout)); + if (ret) + return ret; + + ret = ads112c14_single_conversion(data, chan, raw_buf, true, false); + + /* + * Important to always turn off burnout current even if the conversion + * fails so that it does not affect subsequent measurements. + */ + ret2 = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_BOCS, + FIELD_PREP(ADS112C14_DEVICE_CFG_BOCS, + ADS112C14_DEVICE_CFG_BOCS_DISABLED)); + if (ret < 0) + return ret; + if (ret2) + return ret2; + + switch (data->chip_info->resolution_bits) { + case 16: + val = get_unaligned_be16(raw_buf); + break; + case 24: + val = get_unaligned_be24(raw_buf); + break; + default: + return -EINVAL; + } + + if (measurement->bipolar) + val = sign_extend32(val, data->chip_info->resolution_bits - 1); + + return sysfs_emit(buf, "%d\n", val); +} + +static int ads112c14_get_filter_type_from_state(struct ads112c14_channel_state *channel_state) +{ + switch (channel_state->filter_osr) { + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_16...ADS112C14_DATA_RATE_CFG_FLTR_OSR_32: + return ADS112C14_FILTER_TYPE_SINC4; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_128...ADS112C14_DATA_RATE_CFG_FLTR_OSR_1024: + return ADS112C14_FILTER_TYPE_SINC4_SINC1; + case ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS...ADS112C14_DATA_RATE_CFG_FLTR_OSR_20SPS: + return ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1; + default: + return -EINVAL; + } +} + +static int ads112c14_set_filter_type(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + unsigned int val) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_channel_state *channel_state; + int ret; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + ret = ads112c14_get_filter_type_from_state(channel_state); + if (ret < 0) + return ret; + + /* + * channel_state->filter_osr affects multiple attributes, so don't modify + * it if the filter type is already set to the requested value. + */ + if (ret == val) + return 0; + + /* Otherwise, pick an arbitrary default for each type. */ + switch (val) { + case ADS112C14_FILTER_TYPE_SINC4: + channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16; + break; + case ADS112C14_FILTER_TYPE_SINC4_SINC1: + channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_128; + break; + case ADS112C14_FILTER_TYPE_SINC4_SINC1_PF1: + channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_25SPS; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ads112c14_get_filter_type(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_channel_state *channel_state; + + guard(mutex)(&data->lock); + + channel_state = &data->channel_states[chan->scan_index]; + + return ads112c14_get_filter_type_from_state(channel_state); +} + +static const struct iio_enum ads112c14_filter_type_enum = { + .items = ads112c14_filter_type_names, + .num_items = ARRAY_SIZE(ads112c14_filter_type_names), + .set = ads112c14_set_filter_type, + .get = ads112c14_get_filter_type, +}; + +static const struct iio_chan_spec_ext_info ads112c14_ext_info[] = { + { + .name = "settlingtime", + .read = ads112c14_read_settling_time, + .write = ads112c14_write_settling_time, + .shared = IIO_SEPARATE, + }, + { + .name = "settlingtime_available", + .read = ads112c14_read_settling_time_available, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum), + { } +}; + +static const struct iio_chan_spec_ext_info ads112c14_ext_info_burnout[] = { + { + .name = "burnoutraw", + .read = ads112c14_read_burnout_raw, + }, + { + .name = "settlingtime", + .read = ads112c14_read_settling_time, + .write = ads112c14_write_settling_time, + .shared = IIO_SEPARATE, + }, + { + .name = "settlingtime_available", + .read = ads112c14_read_settling_time_available, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, &ads112c14_filter_type_enum), + { } +}; + static int ads112c14_populate_idac_mag(u32 current_nA, u8 *idac_mag) { u32 current_uA = current_nA / (NANO / MICRO); @@ -955,7 +1918,7 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, struct ads112c14_data *data = iio_priv(indio_dev); struct device *dev = indio_dev->dev.parent; struct iio_chan_spec *channels; - u32 num_child_nodes, i, pair[2]; + u32 num_child_nodes, num_data_chans, i, pair[2]; int ret; *need_avdd_ref = false; @@ -968,8 +1931,15 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, if (!data->measurements) return -ENOMEM; - channels = devm_kcalloc(dev, num_child_nodes + - ARRAY_SIZE(ads112c14_sys_mon_channels) + 1, + num_data_chans = num_child_nodes + ARRAY_SIZE(ads112c14_sys_mon_channels); + + data->channel_states = devm_kcalloc(dev, num_data_chans, + sizeof(*data->channel_states), + GFP_KERNEL); + if (!data->channel_states) + return -ENOMEM; + + channels = devm_kcalloc(dev, num_data_chans + 1, sizeof(*channels), GFP_KERNEL); if (!channels) return -ENOMEM; @@ -978,10 +1948,13 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, device_for_each_named_child_node_scoped(dev, child, "channel") { struct ads112c14_measurement *measurement = &data->measurements[i]; struct iio_chan_spec *spec = &channels[i]; + const char *propname; spec->indexed = 1; spec->scan_index = i; + spec->ext_info = ads112c14_ext_info; measurement->gain_val = 1; + data->channel_states[i].filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16; if (fwnode_property_present(child, "label")) { ret = fwnode_property_read_string(child, "label", &measurement->label); @@ -1101,6 +2074,35 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, measurement->global_chop = fwnode_property_read_bool(child, "input-chopping"); + propname = "burn-out-current-nanoamp"; + if (fwnode_property_present(child, propname)) { + u32 burnout_nA; + + ret = fwnode_property_read_u32(child, propname, &burnout_nA); + if (ret) + return dev_err_probe(dev, ret, + "failed to read %s property\n", + propname); + + switch (burnout_nA) { + case 200: + measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_200_nA; + break; + case 1000: + measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_1_uA; + break; + case 10000: + measurement->burnout = ADS112C14_DEVICE_CFG_BOCS_10_uA; + break; + default: + return dev_err_probe(dev, -EINVAL, + "invalid %s value\n", propname); + } + + if (measurement->burnout != ADS112C14_DEVICE_CFG_BOCS_DISABLED) + spec->ext_info = ads112c14_ext_info_burnout; + } + if (fwnode_property_present(child, "reference-sources")) { ret = fwnode_property_match_property_string(child, "reference-sources", ads112c14_vref_source_names, @@ -1117,8 +2119,13 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, if (measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL) *need_ext_ref = true; - spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE); - spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE); + spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); /* * If reference source is resistor rather than voltage supply, @@ -1152,6 +2159,10 @@ static int ads112c14_parse_channels(struct iio_dev *indio_dev, for (u32 j = 0; j < ARRAY_SIZE(ads112c14_sys_mon_channels); j++) { struct iio_chan_spec *spec = &channels[i]; + struct ads112c14_channel_state *channel_state; + + channel_state = &data->channel_states[i]; + channel_state->filter_osr = ADS112C14_DATA_RATE_CFG_FLTR_OSR_16; /* Update the template that was already copied with dynamic values. */ spec->scan_index = i; @@ -1190,6 +2201,94 @@ static void ads112c14_populate_scale_available(int (*scale_avail)[2], } } +static void ads112c14_populate_odr_tables(struct ads112c14_data *data) +{ + int *available; + u32 osr, fmod_Hz; + u64 odr_uHz; + u32 rem; + + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_osr_available); i++) { + osr = ads112c14_sinc4_osr_available[i]; + + for (unsigned int j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) { + fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j]; + odr_uHz = div_u64((u64)fmod_Hz * MICRO, osr); + available = data->sinc4_sample_rate_available[i][j]; + available[0] = div_u64_rem(odr_uHz, MICRO, &rem); + available[1] = rem; + } + } + + for (unsigned int i = 0; i < ARRAY_SIZE(ads112c14_sinc4_sinc1_osr_available); i++) { + osr = ads112c14_sinc4_sinc1_osr_available[i]; + + for (unsigned int j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) { + fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j]; + odr_uHz = div_u64((u64)fmod_Hz * MICRO, osr); + available = data->sinc4_sinc1_sample_rate_available[i][j]; + available[0] = div_u64_rem(odr_uHz, MICRO, &rem); + available[1] = rem; + } + } + + odr_uHz = div_u64((u64)25 * data->fclk_Hz * MICRO, ADS112C14_INTERNAL_CLK_Hz); + available = data->sinc4_sinc1_pf1_sample_rate_available[0]; + available[0] = div_u64_rem(odr_uHz, MICRO, &rem); + available[1] = rem; + + odr_uHz = div_u64((u64)20 * data->fclk_Hz * MICRO, ADS112C14_INTERNAL_CLK_Hz); + available = data->sinc4_sinc1_pf1_sample_rate_available[1]; + available[0] = div_u64_rem(odr_uHz, MICRO, &rem); + available[1] = rem; +} + +static void ads112c14_populate_settling_range_tables(struct ads112c14_data *data) +{ + s32 (*avail)[2]; + u32 i, j; + + for (i = 0; i < ARRAY_SIZE(ads112c14_sinc_latency_tmod); i++) { + for (j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) { + u64 fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j]; + u64 start_tmod = ads112c14_sinc_latency_tmod[i][j]; + u64 step_tmod = ads112c14_delay_to_tmod(1); + u64 stop_tmod = start_tmod + ads112c14_delay_to_tmod(ADS112C14_DELAY_MAX); + s64 start_us, step_us, stop_us; + + start_us = DIV_ROUND_CLOSEST_ULL(start_tmod * USEC_PER_SEC, fmod_Hz); + step_us = DIV_ROUND_CLOSEST_ULL(step_tmod * USEC_PER_SEC, fmod_Hz); + stop_us = DIV_ROUND_CLOSEST_ULL(stop_tmod * USEC_PER_SEC, fmod_Hz); + + avail = data->sinc_settling_time_range_available[i][j]; + + iio_val_s64_decompose(start_us, &avail[0][0], &avail[0][1]); + iio_val_s64_decompose(step_us, &avail[1][0], &avail[1][1]); + iio_val_s64_decompose(stop_us, &avail[2][0], &avail[2][1]); + } + } + + for (i = 0; i < ARRAY_SIZE(ads112c14_fir_latency_tmod); i++) { + for (j = 0; j < ARRAY_SIZE(ads112c14_fmod_div); j++) { + u64 fmod_Hz = data->fclk_Hz / ads112c14_fmod_div[j]; + u64 start_tmod = ads112c14_fir_latency_tmod[i][j]; + u64 step_tmod = ads112c14_delay_to_tmod(1); + u64 stop_tmod = start_tmod + ads112c14_delay_to_tmod(ADS112C14_DELAY_MAX); + s64 start_us, step_us, stop_us; + + start_us = DIV_ROUND_CLOSEST_ULL(start_tmod * USEC_PER_SEC, fmod_Hz); + step_us = DIV_ROUND_CLOSEST_ULL(step_tmod * USEC_PER_SEC, fmod_Hz); + stop_us = DIV_ROUND_CLOSEST_ULL(stop_tmod * USEC_PER_SEC, fmod_Hz); + + avail = data->fir_settling_time_range_available[i][j]; + + iio_val_s64_decompose(start_us, &avail[0][0], &avail[0][1]); + iio_val_s64_decompose(step_us, &avail[1][0], &avail[1][1]); + iio_val_s64_decompose(stop_us, &avail[2][0], &avail[2][1]); + } + } +} + static void ads112c14_populate_tables(struct ads112c14_data *data) { u32 full_scale, fsr_bits; @@ -1227,6 +2326,8 @@ static void ads112c14_populate_tables(struct ads112c14_data *data) ads112c14_populate_scale_available(data->sys_mon_chan_short_scale_available, full_scale, fsr_bits); + ads112c14_populate_odr_tables(data); + ads112c14_populate_settling_range_tables(data); } static int ads112c14_probe(struct i2c_client *client) @@ -1235,6 +2336,7 @@ static int ads112c14_probe(struct i2c_client *client) const struct ads112c14_chip_info *info; struct iio_dev *indio_dev; struct ads112c14_data *data; + struct clk *clk; bool need_avdd_ref, need_ext_ref; u32 refp_uV = 0; u32 refn_uV = 0; @@ -1327,6 +2429,18 @@ static int ads112c14_probe(struct i2c_client *client) return dev_err_probe(dev, -EINVAL, "external reference measurements require either refp-supply or ti,refp-refn-resistor-ohms property\n"); + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "failed to get clk\n"); + + if (clk) { + data->fclk_Hz = clk_get_rate(clk); + if (!data->fclk_Hz) + return dev_err_probe(dev, -EINVAL, "clk rate is 0\n"); + } else { + data->fclk_Hz = ADS112C14_INTERNAL_CLK_Hz; + } + /* It takes some time for the internal reference to stabilize. */ fsleep(10 * USEC_PER_MSEC); @@ -1391,6 +2505,71 @@ static int ads112c14_probe(struct i2c_client *client) if (ret) return ret; + if (fwnode_property_match_string(dev_fwnode(dev), "interrupt-names", "drdy") >= 0) { + data->drdy_irq = fwnode_irq_get_byname(dev_fwnode(dev), "drdy"); + if (data->drdy_irq < 0) + return dev_err_probe(dev, data->drdy_irq, + "failed to get drdy interrupt\n"); + + if (clk) + return dev_err_probe(dev, -EINVAL, + "cannot use both DRDY and CLK - they share the same pin\n"); + + /* + * REVISIT: would probably need to implement a pin controller in + * order to support open drain option here. + */ + ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_CFG, + ADS112C14_GPIO_CFG_GPIO3_CFG, + FIELD_PREP(ADS112C14_GPIO_CFG_GPIO3_CFG, + ADS112C14_GPIO_CFG_GPIO_CFG_OUTPUT_PUSH_PULL)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_DATA_OUTPUT, + ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC, + FIELD_PREP(ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC, + ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC_DRDY)); + if (ret) + return ret; + + init_completion(&data->drdy_completion); + + data->drdy_trig = devm_iio_trigger_alloc(dev, "%s-dev%d-drdy", + info->name, + iio_device_id(indio_dev)); + if (!data->drdy_trig) + return -ENOMEM; + + data->drdy_trig->ops = &ads112c14_trigger_ops; + iio_trigger_set_drvdata(data->drdy_trig, indio_dev); + + ret = devm_iio_trigger_register(dev, data->drdy_trig); + if (ret) + return ret; + + ret = devm_request_irq(dev, data->drdy_irq, ads112c14_drdy_irq_handler, + 0, dev_name(dev), indio_dev); + if (ret) + return ret; + } + + if (clk) { + ret = regmap_update_bits(data->regmap, ADS112C14_REG_GPIO_CFG, + ADS112C14_GPIO_CFG_GPIO3_CFG, + FIELD_PREP(ADS112C14_GPIO_CFG_GPIO3_CFG, + ADS112C14_GPIO_CFG_GPIO_CFG_INPUT)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_CLK_SEL, + FIELD_PREP(ADS112C14_DEVICE_CFG_CLK_SEL, + ADS112C14_DEVICE_CFG_CLK_SEL_EXTERNAL)); + if (ret) + return ret; + } + ads112c14_populate_tables(data); indio_dev->name = info->name; @@ -1399,7 +2578,8 @@ static int ads112c14_probe(struct i2c_client *client) ret = devm_iio_triggered_buffer_setup(dev, indio_dev, iio_pollfunc_store_time, - ads112c14_trigger_handler, NULL); + ads112c14_trigger_handler, + &ads112c14_buffer_setup_ops); if (ret) return ret; diff --git a/drivers/iio/adc/ti-ads131m02.c b/drivers/iio/adc/ti-ads131m02.c index 2f8f75c8216b..7ecfb27fd7ef 100644 --- a/drivers/iio/adc/ti-ads131m02.c +++ b/drivers/iio/adc/ti-ads131m02.c @@ -897,6 +897,8 @@ static int ads131m_probe(struct spi_device *spi) indio_dev->info = &ads131m_info; config = spi_get_device_match_data(spi); + if (!config) + return -ENODATA; priv->config = config; indio_dev->name = config->name; diff --git a/drivers/iio/adc/ti-ads7950.c b/drivers/iio/adc/ti-ads7950.c index d27c1f2885fd..0d5708e0b831 100644 --- a/drivers/iio/adc/ti-ads7950.c +++ b/drivers/iio/adc/ti-ads7950.c @@ -527,8 +527,10 @@ static int ti_ads7950_probe(struct spi_device *spi) st->spi = spi; info = spi_get_device_match_data(spi); + if (!info) + return -ENODATA; - indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->name = spi->modalias; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = info->channels; indio_dev->num_channels = info->num_channels; diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c index 1516dd332f90..e53c839e87ef 100644 --- a/drivers/iio/adc/ti_am335x_adc.c +++ b/drivers/iio/adc/ti_am335x_adc.c @@ -548,7 +548,7 @@ static int tiadc_request_dma(struct platform_device *pdev, } /* RX buffer */ - dma->buf = dma_alloc_coherent(dma->chan->device->dev, DMA_BUFFER_SIZE, + dma->buf = dma_alloc_coherent(dmaengine_get_dma_device(dma->chan), DMA_BUFFER_SIZE, &dma->addr, GFP_KERNEL); if (!dma->buf) goto err; @@ -688,7 +688,7 @@ static void tiadc_remove(struct platform_device *pdev) u32 step_en; if (dma->chan) { - dma_free_coherent(dma->chan->device->dev, DMA_BUFFER_SIZE, + dma_free_coherent(dmaengine_get_dma_device(dma->chan), DMA_BUFFER_SIZE, dma->buf, dma->addr); dma_release_channel(dma->chan); } diff --git a/drivers/iio/amplifiers/ad8366.c b/drivers/iio/amplifiers/ad8366.c index e12f8604f59f..9adff120de1a 100644 --- a/drivers/iio/amplifiers/ad8366.c +++ b/drivers/iio/amplifiers/ad8366.c @@ -326,6 +326,8 @@ static int ad8366_probe(struct spi_device *spi) st->spi = spi; st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODATA; enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH); if (IS_ERR(enable_gpio)) diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index 31ed2e3e7f52..b9f8b5b3aa6b 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -88,9 +88,6 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, unsigned int i; int nents; - max_size = min(block->size, dmaengine_buffer->max_size); - max_size = round_down(max_size, dmaengine_buffer->align); - if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN) dma_dir = DMA_DEV_TO_MEM; else @@ -257,6 +254,7 @@ static const struct iio_dev_attr *iio_dmaengine_buffer_attrs[] = { */ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct dma_chan *chan) { + struct device *dma_dev = dmaengine_get_dma_device(chan); struct dmaengine_buffer *dmaengine_buffer; unsigned int width, src_width, dest_width; struct dma_slave_caps caps; @@ -284,9 +282,9 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct dma_chan *chan) INIT_LIST_HEAD(&dmaengine_buffer->active); dmaengine_buffer->chan = chan; dmaengine_buffer->align = width; - dmaengine_buffer->max_size = dma_get_max_seg_size(chan->device->dev); + dmaengine_buffer->max_size = dma_get_max_seg_size(dma_dev); - iio_dma_buffer_init(&dmaengine_buffer->queue, chan->device->dev, + iio_dma_buffer_init(&dmaengine_buffer->queue, dma_dev, &iio_dmaengine_default_ops); dmaengine_buffer->queue.buffer.attrs = iio_dmaengine_buffer_attrs; diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c index 7c1ffda38b99..5e2b15463c99 100644 --- a/drivers/iio/chemical/ens160_core.c +++ b/drivers/iio/chemical/ens160_core.c @@ -283,12 +283,7 @@ static int ens160_set_trigger_state(struct iio_trigger *trig, bool state) ENS160_REG_CONFIG_INTDAT | ENS160_REG_CONFIG_INT_CFG; - if (state) - return regmap_set_bits(data->regmap, ENS160_REG_CONFIG, - int_bits); - else - return regmap_clear_bits(data->regmap, ENS160_REG_CONFIG, - int_bits); + return regmap_assign_bits(data->regmap, ENS160_REG_CONFIG, int_bits, state); } static const struct iio_trigger_ops ens160_trigger_ops = { diff --git a/drivers/iio/chemical/sps30.c b/drivers/iio/chemical/sps30.c index 8e15baa31423..b47d08ab4d0f 100644 --- a/drivers/iio/chemical/sps30.c +++ b/drivers/iio/chemical/sps30.c @@ -15,6 +15,7 @@ #include <linux/iio/trigger_consumer.h> #include <linux/iio/triggered_buffer.h> #include <linux/kernel.h> +#include <linux/kstrtox.h> #include <linux/module.h> #include "sps30.h" @@ -193,7 +194,10 @@ static ssize_t start_cleaning_store(struct device *dev, struct sps30_state *state = iio_priv(indio_dev); int val, ret; - if (kstrtoint(buf, 0, &val) || val != 1) + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + if (val != 1) return -EINVAL; guard(mutex)(&state->lock); @@ -230,8 +234,9 @@ static ssize_t cleaning_period_store(struct device *dev, struct device_attribute struct sps30_state *state = iio_priv(indio_dev); int val, ret; - if (kstrtoint(buf, 0, &val)) - return -EINVAL; + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; if ((val < SPS30_AUTO_CLEANING_PERIOD_MIN) || (val > SPS30_AUTO_CLEANING_PERIOD_MAX)) diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c index b971f8b646be..551b86236abc 100644 --- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c @@ -311,7 +311,7 @@ MODULE_DEVICE_TABLE(platform, cros_ec_sensors_ids); static struct platform_driver cros_ec_sensors_platform_driver = { .driver = { .name = "cros-ec-sensors", - .pm = &cros_ec_sensors_pm_ops, + .pm = pm_sleep_ptr(&cros_ec_sensors_pm_ops), }, .probe = cros_ec_sensors_probe, .id_table = cros_ec_sensors_ids, diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c index 5133755c2ea6..d03e40c91582 100644 --- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c @@ -862,7 +862,7 @@ int cros_ec_sensors_core_write(struct cros_ec_sensors_core_state *st, } EXPORT_SYMBOL_GPL(cros_ec_sensors_core_write); -static int __maybe_unused cros_ec_sensors_resume(struct device *dev) +static int cros_ec_sensors_resume(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); @@ -879,8 +879,7 @@ static int __maybe_unused cros_ec_sensors_resume(struct device *dev) return ret; } -SIMPLE_DEV_PM_OPS(cros_ec_sensors_pm_ops, NULL, cros_ec_sensors_resume); -EXPORT_SYMBOL_GPL(cros_ec_sensors_pm_ops); +EXPORT_GPL_SIMPLE_DEV_PM_OPS(cros_ec_sensors_pm_ops, NULL, cros_ec_sensors_resume); MODULE_DESCRIPTION("ChromeOS EC sensor hub core functions"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 3b3c77d8b3e1..e373885e7c7a 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -16,6 +16,8 @@ config AD3530R - AD3530/AD3530R (8-channel) - AD3531/AD3531R (4-channel) - AD3532/AD3532R (16-channel) + - AD3536R (16-channel, 12-bit) + - AD5710R/AD5711R (8-channel configurable IDAC/VDAC) To compile this driver as a module, choose M here: the module will be called ad3530r. @@ -137,6 +139,22 @@ config AD5449 To compile this driver as a module, choose M here: the module will be called ad5449. +config AD5529R + tristate "Analog Devices AD5529R High Voltage DAC driver" + depends on SPI_MASTER + select REGMAP_SPI + help + Say yes here to build support for Analog Devices AD5529R 16-Channel, + 12-Bit/16-Bit, 40V High Voltage Precision Digital to Analog Converter. + + The device features multiple output voltage ranges from -20V to +20V, + built-in 4.096V voltage reference, and digital functions including + toggle, dither, and ramp modes. Supports both 12-bit and 16-bit + resolution variants. + + To compile this driver as a module, choose M here: the + module will be called ad5529r. + config AD5592R_BASE tristate @@ -593,9 +611,13 @@ config MCP47A1 will be called mcp47a1. config MCP47FEB02 - tristate "MCP47F(E/V)B01/02/04/08/11/12/14/18/21/22/24/28 DAC driver" + tristate + +config MCP47FEB02_I2C + tristate "MCP47F(E/V)B01/02/04/08/11/12/14/18/21/22/24/28 I2C DAC driver" depends on I2C select REGMAP_I2C + select MCP47FEB02 help Say yes here if you want to build the driver for the Microchip: - 8-bit DAC: @@ -611,7 +633,31 @@ config MCP47FEB02 (DAC) with I2C interface. To compile this driver as a module, choose M here: the module - will be called mcp47feb02. + will be called mcp47feb02_i2c and you will also get + mcp47feb02_core for the core module. + +config MCP47FEB02_SPI + tristate "MCP48F(E/V)B01/02/04/08/11/12/14/18/21/22/24/28 SPI DAC driver" + depends on SPI + select REGMAP_SPI + select MCP47FEB02 + help + Say yes here if you want to build the driver for the Microchip: + - 8-bit DAC: + MCP48FEB01, MCP48FEB02, MCP48FEB04, MCP48FEB08, + MCP48FVB01, MCP48FVB02, MCP48FVB04, MCP48FVB08 + - 10-bit DAC: + MCP48FEB11, MCP48FEB12, MCP48FEB14, MCP48FEB18, + MCP48FVB11, MCP48FVB12, MCP48FVB14, MCP48FVB18 + - 12-bit DAC: + MCP48FEB21, MCP48FEB22, MCP48FEB24, MCP48FEB28, + MCP48FVB21, MCP48FVB22, MCP48FVB24, MCP48FVB28 + having 1 to 8 channels, 8/10/12-bit digital-to-analog converter + (DAC) with SPI interface. + + To compile this driver as a module, choose M here: the module + will be called mcp47feb02_spi and you will also get + mcp47feb02_core for the core module. config MCP4821 tristate "MCP4801/02/11/12/21/22 DAC driver" diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 992f8930f95c..2cd0fe23db9e 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_AD5446) += ad5446.o obj-$(CONFIG_AD5446_SPI) += ad5446-spi.o obj-$(CONFIG_AD5446_I2C) += ad5446-i2c.o obj-$(CONFIG_AD5449) += ad5449.o +obj-$(CONFIG_AD5529R) += ad5529r.o obj-$(CONFIG_AD5592R_BASE) += ad5592r-base.o obj-$(CONFIG_AD5592R) += ad5592r.o obj-$(CONFIG_AD5593R) += ad5593r.o @@ -56,7 +57,9 @@ obj-$(CONFIG_MCF54415_DAC) += mcf54415_dac.o obj-$(CONFIG_MCP4725) += mcp4725.o obj-$(CONFIG_MCP4728) += mcp4728.o obj-$(CONFIG_MCP47A1) += mcp47a1.o -obj-$(CONFIG_MCP47FEB02) += mcp47feb02.o +obj-$(CONFIG_MCP47FEB02) += mcp47feb02-core.o +obj-$(CONFIG_MCP47FEB02_I2C) += mcp47feb02-i2c.o +obj-$(CONFIG_MCP47FEB02_SPI) += mcp47feb02-spi.o obj-$(CONFIG_MCP4821) += mcp4821.o obj-$(CONFIG_MCP4922) += mcp4922.o obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o diff --git a/drivers/iio/dac/ad3530r.c b/drivers/iio/dac/ad3530r.c index 06b7f280f61b..9e043639801d 100644 --- a/drivers/iio/dac/ad3530r.c +++ b/drivers/iio/dac/ad3530r.c @@ -3,8 +3,11 @@ * AD3530R/AD3530 8-channel, 16-bit Voltage Output DAC Driver * AD3531R/AD3531 4-channel, 16-bit Voltage Output DAC Driver * AD3532R/AD3532 16-channel, 16-bit Voltage Output DAC Driver + * AD3536R 16-channel, 12-bit Voltage Output DAC Driver + * AD5710R 8-channel, 16-bit Configurable IDAC/VDAC Driver + * AD5711R 8-channel, 12-bit Configurable IDAC/VDAC Driver * - * Copyright 2025 Analog Devices Inc. + * Copyright 2025-2026 Analog Devices Inc. */ #include <linux/array_size.h> @@ -27,6 +30,8 @@ #include <linux/types.h> #include <linux/units.h> +#include <dt-bindings/iio/addac/adi,ad74413r.h> + #define AD3530R_INTERFACE_CONFIG_A 0x00 #define AD3530R_OUTPUT_OPERATING_MODE_0 0x20 #define AD3530R_OUTPUT_OPERATING_MODE_1 0x21 @@ -39,6 +44,8 @@ #define AD3531R_SW_LDAC_TRIG_A 0xDD #define AD3531R_INPUT_CH 0xE3 +#define AD5710R_CHN_VMODE_EN 0xFF + /* AD3532R/AD3532 bank 0 registers (channels 0-7) */ #define AD3532R_INTERFACE_CONFIG_A_0 0x1000 #define AD3532R_OUTPUT_OPERATING_MODE_0 0x1020 @@ -61,14 +68,14 @@ #define AD3530R_SLD_TRIG_A BIT(7) #define AD3530R_OUTPUT_CONTROL_RANGE BIT(2) #define AD3530R_REFERENCE_CONTROL_SEL BIT(0) -#define AD3530R_REG_VAL_MASK GENMASK(15, 0) #define AD3530R_OP_MODE_CHAN_MSK(chan) (GENMASK(1, 0) << 2 * (chan)) +#define AD5710R_CHN_VMODE_EN_BIT(chan) BIT(chan) #define AD3530R_SW_RESET (BIT(7) | BIT(0)) #define AD3530R_INTERNAL_VREF_mV 2500 +#define AD5710R_INTERNAL_IREF_mA 50 #define AD3530R_LDAC_PULSE_US 100 -#define AD3530R_DAC_MAX_VAL GENMASK(15, 0) #define AD3530R_CH_PER_REG 4 #define AD3530R_CH_PER_BANK 8 #define AD3531R_MAX_CHANNELS 4 @@ -99,7 +106,9 @@ struct ad3530r_chip_info { unsigned int num_channels; unsigned int num_banks; unsigned int num_op_mode_regs; + unsigned int resolution; bool internal_ref_support; + bool configurable_ch_func; }; struct ad3530r_state { @@ -195,6 +204,23 @@ static const struct iio_enum ad3532r_powerdown_mode_enum = { .set = ad3530r_set_powerdown_mode, }; +static ssize_t ad5710r_read_powerdown_mode(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + int ret; + + /* Voltage output pulls to 15kohm_to_gnd, current goes three_state. */ + ret = regmap_test_bits(st->regmap, AD5710R_CHN_VMODE_EN, + AD5710R_CHN_VMODE_EN_BIT(chan->channel)); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%s\n", ret ? "15kohm_to_gnd" : "three_state"); +} + static ssize_t ad3530r_get_dac_powerdown(struct iio_dev *indio_dev, uintptr_t private, const struct iio_chan_spec *chan, @@ -278,6 +304,61 @@ static ssize_t ad3532r_set_dac_powerdown(struct iio_dev *indio_dev, return len; } +static void ad5710r_get_op_mode_reg(unsigned int channel, unsigned int *reg, + unsigned int *mask) +{ + unsigned int local_ch, reg_in_bank, ch_in_reg; + + local_ch = channel % AD3530R_CH_PER_BANK; + reg_in_bank = local_ch / AD3530R_CH_PER_REG; + ch_in_reg = local_ch % AD3530R_CH_PER_REG; + + *reg = AD3530R_OUTPUT_OPERATING_MODE_0 + reg_in_bank; + *mask = AD3530R_OP_MODE_CHAN_MSK(ch_in_reg); +} + +static ssize_t ad5710r_get_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + unsigned int reg, mask; + int ret; + + ad5710r_get_op_mode_reg(chan->channel, ®, &mask); + + ret = regmap_test_bits(st->regmap, reg, field_prep(mask, 1)); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", ret); +} + +static ssize_t ad5710r_set_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + unsigned int reg, mask; + bool powerdown; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + ad5710r_get_op_mode_reg(chan->channel, ®, &mask); + + ret = regmap_update_bits(st->regmap, reg, mask, + field_prep(mask, powerdown)); + if (ret) + return ret; + + return len; +} + static int ad3530r_trigger_sw_ldac_reg(unsigned int channel) { return AD3530R_SW_LDAC_TRIG_A; @@ -310,7 +391,7 @@ static int ad3530r_dac_write(struct ad3530r_state *st, unsigned int chan, int ret; guard(mutex)(&st->lock); - st->buf = cpu_to_be16(val); + st->buf = cpu_to_be16(val << (16 - st->chip_info->resolution)); ret = regmap_bulk_write(st->regmap, st->chip_info->input_ch_reg(chan), &st->buf, sizeof(st->buf)); @@ -340,12 +421,15 @@ static int ad3530r_read_raw(struct iio_dev *indio_dev, if (ret) return ret; - *val = FIELD_GET(AD3530R_REG_VAL_MASK, be16_to_cpu(st->buf)); + *val = be16_to_cpu(st->buf) >> (16 - st->chip_info->resolution); return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - *val = st->vref_mV; - *val2 = 16; + if (chan->type == IIO_CURRENT) + *val = AD5710R_INTERNAL_IREF_mA; + else + *val = st->vref_mV; + *val2 = st->chip_info->resolution; return IIO_VAL_FRACTIONAL_LOG2; default: @@ -361,7 +445,7 @@ static int ad3530r_write_raw(struct iio_dev *indio_dev, switch (info) { case IIO_CHAN_INFO_RAW: - if (val < 0 || val > AD3530R_DAC_MAX_VAL) + if (val < 0 || val > GENMASK(st->chip_info->resolution - 1, 0)) return -EINVAL; return ad3530r_dac_write(st, chan->channel, val); @@ -420,6 +504,21 @@ static const struct iio_chan_spec_ext_info ad3532r_ext_info[] = { { } }; +static const struct iio_chan_spec_ext_info ad5710r_ext_info[] = { + { + .name = "powerdown", + .shared = IIO_SEPARATE, + .read = ad5710r_get_dac_powerdown, + .write = ad5710r_set_dac_powerdown, + }, + { + .name = "powerdown_mode", + .shared = IIO_SEPARATE, + .read = ad5710r_read_powerdown_mode, + }, + { } +}; + #define AD3530R_CHAN(_chan, _ext_info) \ { \ .type = IIO_VOLTAGE, \ @@ -468,6 +567,17 @@ static const struct iio_chan_spec ad3532r_channels[] = { AD3530R_CHAN(15, ad3532r_ext_info), }; +static const struct iio_chan_spec ad5710r_channels[] = { + AD3530R_CHAN(0, ad5710r_ext_info), + AD3530R_CHAN(1, ad5710r_ext_info), + AD3530R_CHAN(2, ad5710r_ext_info), + AD3530R_CHAN(3, ad5710r_ext_info), + AD3530R_CHAN(4, ad5710r_ext_info), + AD3530R_CHAN(5, ad5710r_ext_info), + AD3530R_CHAN(6, ad5710r_ext_info), + AD3530R_CHAN(7, ad5710r_ext_info), +}; + static const unsigned int ad3530r_if_config[] = { AD3530R_INTERFACE_CONFIG_A, }; @@ -523,8 +633,15 @@ static const struct regmap_config ad3532r_regmap_config = { .max_register = AD3532R_MAX_REG_ADDR, }; +static const struct regmap_config ad5710r_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = AD5710R_CHN_VMODE_EN, +}; + static const struct ad3530r_chip_info ad3530_chip = { .name = "ad3530", + .resolution = 16, .channels = ad3530r_channels, .regmap_config = &ad3530r_regmap_config, .num_channels = ARRAY_SIZE(ad3530r_channels), @@ -537,10 +654,12 @@ static const struct ad3530r_chip_info ad3530_chip = { .num_banks = ARRAY_SIZE(ad3530r_if_config), .num_op_mode_regs = ARRAY_SIZE(ad3530r_op_mode), .internal_ref_support = false, + .configurable_ch_func = false, }; static const struct ad3530r_chip_info ad3530r_chip = { .name = "ad3530r", + .resolution = 16, .channels = ad3530r_channels, .regmap_config = &ad3530r_regmap_config, .num_channels = ARRAY_SIZE(ad3530r_channels), @@ -553,10 +672,12 @@ static const struct ad3530r_chip_info ad3530r_chip = { .num_banks = ARRAY_SIZE(ad3530r_if_config), .num_op_mode_regs = ARRAY_SIZE(ad3530r_op_mode), .internal_ref_support = true, + .configurable_ch_func = false, }; static const struct ad3530r_chip_info ad3531_chip = { .name = "ad3531", + .resolution = 16, .channels = ad3531r_channels, .regmap_config = &ad3530r_regmap_config, .num_channels = ARRAY_SIZE(ad3531r_channels), @@ -569,10 +690,12 @@ static const struct ad3530r_chip_info ad3531_chip = { .num_banks = ARRAY_SIZE(ad3530r_if_config), .num_op_mode_regs = ARRAY_SIZE(ad3531r_op_mode), .internal_ref_support = false, + .configurable_ch_func = false, }; static const struct ad3530r_chip_info ad3531r_chip = { .name = "ad3531r", + .resolution = 16, .channels = ad3531r_channels, .regmap_config = &ad3530r_regmap_config, .num_channels = ARRAY_SIZE(ad3531r_channels), @@ -585,10 +708,12 @@ static const struct ad3530r_chip_info ad3531r_chip = { .num_banks = ARRAY_SIZE(ad3530r_if_config), .num_op_mode_regs = ARRAY_SIZE(ad3531r_op_mode), .internal_ref_support = true, + .configurable_ch_func = false, }; static const struct ad3530r_chip_info ad3532_chip = { .name = "ad3532", + .resolution = 16, .channels = ad3532r_channels, .regmap_config = &ad3532r_regmap_config, .num_channels = ARRAY_SIZE(ad3532r_channels), @@ -601,10 +726,12 @@ static const struct ad3530r_chip_info ad3532_chip = { .num_banks = ARRAY_SIZE(ad3532r_if_config), .num_op_mode_regs = ARRAY_SIZE(ad3532r_op_mode), .internal_ref_support = false, + .configurable_ch_func = false, }; static const struct ad3530r_chip_info ad3532r_chip = { .name = "ad3532r", + .resolution = 16, .channels = ad3532r_channels, .regmap_config = &ad3532r_regmap_config, .num_channels = ARRAY_SIZE(ad3532r_channels), @@ -617,8 +744,141 @@ static const struct ad3530r_chip_info ad3532r_chip = { .num_banks = ARRAY_SIZE(ad3532r_if_config), .num_op_mode_regs = ARRAY_SIZE(ad3532r_op_mode), .internal_ref_support = true, + .configurable_ch_func = false, }; +static const struct ad3530r_chip_info ad3536r_chip = { + .name = "ad3536r", + .resolution = 12, + .channels = ad3532r_channels, + .regmap_config = &ad3532r_regmap_config, + .num_channels = ARRAY_SIZE(ad3532r_channels), + .sw_ldac_trig_reg = ad3532r_trigger_sw_ldac_reg, + .input_ch_reg = ad3532r_input_ch_reg, + .interface_config_a = ad3532r_if_config, + .output_control = ad3532r_out_ctrl, + .reference_control = ad3532r_ref_ctrl, + .op_mode = ad3532r_op_mode, + .num_banks = ARRAY_SIZE(ad3532r_if_config), + .num_op_mode_regs = ARRAY_SIZE(ad3532r_op_mode), + .internal_ref_support = true, + .configurable_ch_func = false, +}; + +static const struct ad3530r_chip_info ad5710r_chip = { + .name = "ad5710r", + .resolution = 16, + .regmap_config = &ad5710r_regmap_config, + .num_channels = ARRAY_SIZE(ad5710r_channels), + .sw_ldac_trig_reg = ad3530r_trigger_sw_ldac_reg, + .input_ch_reg = ad3530r_input_ch_reg, + .interface_config_a = ad3530r_if_config, + .output_control = ad3530r_out_ctrl, + .reference_control = ad3530r_ref_ctrl, + .op_mode = ad3530r_op_mode, + .num_banks = ARRAY_SIZE(ad3530r_if_config), + .num_op_mode_regs = ARRAY_SIZE(ad3530r_op_mode), + .internal_ref_support = true, + .configurable_ch_func = true, +}; + +static const struct ad3530r_chip_info ad5711r_chip = { + .name = "ad5711r", + .resolution = 12, + .regmap_config = &ad5710r_regmap_config, + .num_channels = ARRAY_SIZE(ad5710r_channels), + .sw_ldac_trig_reg = ad3530r_trigger_sw_ldac_reg, + .input_ch_reg = ad3530r_input_ch_reg, + .interface_config_a = ad3530r_if_config, + .output_control = ad3530r_out_ctrl, + .reference_control = ad3530r_ref_ctrl, + .op_mode = ad3530r_op_mode, + .num_banks = ARRAY_SIZE(ad3530r_if_config), + .num_op_mode_regs = ARRAY_SIZE(ad3530r_op_mode), + .internal_ref_support = true, + .configurable_ch_func = true, +}; + +static int ad3530r_parse_channel_cfg(struct iio_dev *indio_dev) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->regmap); + struct iio_chan_spec *channels; + unsigned int num_chan, i; + int ret; + + num_chan = device_get_child_node_count(dev); + if (!num_chan) + return dev_err_probe(dev, -ENOENT, "No channels configured\n"); + + channels = devm_kcalloc(dev, num_chan, sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + i = 0; + device_for_each_child_node_scoped(dev, child) { + unsigned int mode_reg, mode_mask, ch_func; + enum iio_chan_type chan_type; + u32 reg; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read reg property of %pfwP\n", + child); + + if (reg >= st->chip_info->num_channels) + return dev_err_probe(dev, -ECHRNG, + "reg out of range in %pfwP\n", + child); + + ret = fwnode_property_read_u32(child, "adi,ch-func", &ch_func); + if (ret) + return dev_err_probe(dev, ret, + "Missing adi,ch-func property for %pfwP\n", + child); + + switch (ch_func) { + case CH_FUNC_VOLTAGE_OUTPUT: + ret = regmap_set_bits(st->regmap, AD5710R_CHN_VMODE_EN, + AD5710R_CHN_VMODE_EN_BIT(reg)); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set voltage output for %pfwP\n", + child); + + chan_type = IIO_VOLTAGE; + break; + case CH_FUNC_CURRENT_OUTPUT: + chan_type = IIO_CURRENT; + break; + default: + return dev_err_probe(dev, -EINVAL, + "Invalid adi,ch-func %u for %pfwP\n", + ch_func, child); + } + + channels[i] = ad5710r_channels[reg]; + channels[i].type = chan_type; + i++; + + ad5710r_get_op_mode_reg(reg, &mode_reg, &mode_mask); + + /* Enable the channel in normal operation mode */ + ret = regmap_update_bits(st->regmap, mode_reg, mode_mask, + field_prep(mode_mask, AD3530R_NORMAL_OP)); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set normal operating mode for %pfwP\n", + child); + } + + indio_dev->channels = channels; + indio_dev->num_channels = num_chan; + + return 0; +} + static int ad3530r_set_reg_bank_bits(const struct ad3530r_state *st, const unsigned int *regs, unsigned int num_regs, @@ -651,8 +911,9 @@ static int ad3530r_write_reg_banks(const struct ad3530r_state *st, return 0; } -static int ad3530r_setup(struct ad3530r_state *st, int external_vref_uV) +static int ad3530r_setup(struct iio_dev *indio_dev, int external_vref_uV) { + struct ad3530r_state *st = iio_priv(indio_dev); const struct ad3530r_chip_info *chip_info = st->chip_info; struct device *dev = regmap_get_device(st->regmap); struct gpio_desc *reset_gpio; @@ -702,19 +963,29 @@ static int ad3530r_setup(struct ad3530r_state *st, int external_vref_uV) st->vref_mV = range_multiplier * AD3530R_INTERNAL_VREF_mV; } - /* Set normal operating mode for all channels */ - val = FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(0), AD3530R_NORMAL_OP) | - FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(1), AD3530R_NORMAL_OP) | - FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(2), AD3530R_NORMAL_OP) | - FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(3), AD3530R_NORMAL_OP); + if (chip_info->configurable_ch_func) { + /* Channels and their operating mode are configured from DT */ + ret = ad3530r_parse_channel_cfg(indio_dev); + if (ret) + return ret; + } else { + /* Set normal operating mode for all channels */ + val = FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(0), AD3530R_NORMAL_OP) | + FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(1), AD3530R_NORMAL_OP) | + FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(2), AD3530R_NORMAL_OP) | + FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(3), AD3530R_NORMAL_OP); + + ret = ad3530r_write_reg_banks(st, chip_info->op_mode, + chip_info->num_op_mode_regs, val); + if (ret) + return ret; - ret = ad3530r_write_reg_banks(st, st->chip_info->op_mode, - st->chip_info->num_op_mode_regs, val); - if (ret) - return ret; + for (unsigned int i = 0; i < chip_info->num_channels; i++) + st->chan[i].powerdown_mode = AD3530R_POWERDOWN_32K; - for (unsigned int i = 0; i < st->chip_info->num_channels; i++) - st->chan[i].powerdown_mode = AD3530R_POWERDOWN_32K; + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = chip_info->num_channels; + } st->ldac_gpio = devm_gpiod_get_optional(dev, "ldac", GPIOD_OUT_LOW); if (IS_ERR(st->ldac_gpio)) @@ -772,15 +1043,13 @@ static int ad3530r_probe(struct spi_device *spi) if (!st->chip_info->internal_ref_support && external_vref_uV == 0) return -ENODEV; - ret = ad3530r_setup(st, external_vref_uV); + ret = ad3530r_setup(indio_dev, external_vref_uV); if (ret) return ret; indio_dev->name = st->chip_info->name; indio_dev->info = &ad3530r_info; indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = st->chip_info->channels; - indio_dev->num_channels = st->chip_info->num_channels; return devm_iio_device_register(&spi->dev, indio_dev); } @@ -792,6 +1061,9 @@ static const struct spi_device_id ad3530r_id[] = { { .name = "ad3531r", .driver_data = (kernel_ulong_t)&ad3531r_chip }, { .name = "ad3532", .driver_data = (kernel_ulong_t)&ad3532_chip }, { .name = "ad3532r", .driver_data = (kernel_ulong_t)&ad3532r_chip }, + { .name = "ad3536r", .driver_data = (kernel_ulong_t)&ad3536r_chip }, + { .name = "ad5710r", .driver_data = (kernel_ulong_t)&ad5710r_chip }, + { .name = "ad5711r", .driver_data = (kernel_ulong_t)&ad5711r_chip }, { } }; MODULE_DEVICE_TABLE(spi, ad3530r_id); @@ -803,6 +1075,9 @@ static const struct of_device_id ad3530r_of_match[] = { { .compatible = "adi,ad3531r", .data = &ad3531r_chip }, { .compatible = "adi,ad3532", .data = &ad3532_chip }, { .compatible = "adi,ad3532r", .data = &ad3532r_chip }, + { .compatible = "adi,ad3536r", .data = &ad3536r_chip }, + { .compatible = "adi,ad5710r", .data = &ad5710r_chip }, + { .compatible = "adi,ad5711r", .data = &ad5711r_chip }, { } }; MODULE_DEVICE_TABLE(of, ad3530r_of_match); diff --git a/drivers/iio/dac/ad3552r.c b/drivers/iio/dac/ad3552r.c index 16db94fef9d4..039af514adde 100644 --- a/drivers/iio/dac/ad3552r.c +++ b/drivers/iio/dac/ad3552r.c @@ -696,6 +696,6 @@ static struct spi_driver ad3552r_driver = { module_spi_driver(ad3552r_driver); MODULE_AUTHOR("Mihail Chindris <mihail.chindris@analog.com>"); -MODULE_DESCRIPTION("Analog Device AD3552R DAC"); +MODULE_DESCRIPTION("Analog Devices AD3552R DAC"); MODULE_LICENSE("GPL v2"); MODULE_IMPORT_NS("IIO_AD3552R"); diff --git a/drivers/iio/dac/ad5529r.c b/drivers/iio/dac/ad5529r.c new file mode 100644 index 000000000000..8154b87ff831 --- /dev/null +++ b/drivers/iio/dac/ad5529r.c @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5529R Digital-to-Analog Converter Driver + * 16-Channel, 12/16-Bit, 40V High Voltage Precision DAC + * + * Copyright 2026 Analog Devices Inc. + * Author: Janani Sunil <janani.sunil@analog.com> + */ + +#include <linux/array_size.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/spi/spi.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <linux/iio/iio.h> + +#define AD5529R_REG_INTERFACE_CONFIG_A 0x00 +#define AD5529R_INTERFACE_CONFIG_A_SW_RESET (BIT(7) | BIT(0)) +#define AD5529R_INTERFACE_CONFIG_A_ADDR_ASCENSION BIT(5) +#define AD5529R_INTERFACE_CONFIG_A_SDO_ENABLE BIT(4) +#define AD5529R_REG_DEVICE_CONFIG 0x02 +#define AD5529R_REG_CHIP_GRADE 0x06 +#define AD5529R_REG_SCRATCH_PAD 0x0A +#define AD5529R_REG_SPI_REVISION 0x0B +#define AD5529R_REG_VENDOR_H 0x0D +#define AD5529R_REG_STREAM_MODE 0x0E +#define AD5529R_REG_INTERFACE_STATUS_A 0x11 +#define AD5529R_REG_MULTI_DAC_CH_SEL 0x14 +#define AD5529R_REG_OUT_RANGE_BASE 0x3C +#define AD5529R_REG_OUT_RANGE(ch) (AD5529R_REG_OUT_RANGE_BASE + (ch) * 2) +#define AD5529R_REG_DAC_INPUT_A_BASE 0x148 +#define AD5529R_REG_DAC_INPUT_A(ch) (AD5529R_REG_DAC_INPUT_A_BASE + (ch) * 2) +#define AD5529R_REG_DAC_DATA_READBACK_BASE 0x16A +#define AD5529R_REG_TSENS_ALERT_FLAG 0x18C +#define AD5529R_REG_TSENS_SHTD_FLAG 0x18E +#define AD5529R_REG_FUNC_BUSY 0x1A0 +#define AD5529R_REG_REF_SEL 0x1A2 +#define AD5529R_REF_SEL_INTERNAL_REF BIT(0) +#define AD5529R_REG_INIT_CRC_ERR_STAT 0x1A4 +#define AD5529R_REG_MULTI_DAC_HOTPATH_SW_LDAC 0x1A8 + +#define AD5529R_MAX_REGISTER 0x232 +#define AD5529R_8BIT_REG_MAX 0x13 +#define AD5529R_MAX_CHANNELS 16 +#define AD5529R_SPI_READ_FLAG 0x80 +#define AD5529R_ADDR_SHIFT 12 + +struct ad5529r_model_data { + const char *model_name; + unsigned int resolution; +}; + +#define AD5529R_DAC_CHANNEL(chan) ((struct iio_chan_spec) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ +}) + +static const char * const ad5529r_supply_names[] = { + "vdd-io", + "vdd-5v", + "vdd-hv0", + "vdd-hv1", + "vdd-hv2", + "vdd-hv3", +}; + +static const char * const ad5529r_vss_supply_names[] = { + "vss-hv0", + "vss-hv1", + "vss-hv2", + "vss-hv3", +}; + +static const struct ad5529r_model_data ad5529r_16bit_model_data = { + .model_name = "ad5529r-16", + .resolution = 16, +}; + +static const struct ad5529r_model_data ad5529r_12bit_model_data = { + .model_name = "ad5529r-12", + .resolution = 12, +}; + +enum ad5529r_output_range { + AD5529R_RANGE_0V_5V, + AD5529R_RANGE_0V_10V, + AD5529R_RANGE_0V_20V, + AD5529R_RANGE_0V_40V, + AD5529R_RANGE_M5V_5V, + AD5529R_RANGE_M10V_10V, + AD5529R_RANGE_M15V_15V, + AD5529R_RANGE_M20V_20V, +}; + +static const s32 ad5529r_output_ranges_mV[8][2] = { + [AD5529R_RANGE_0V_5V] = { 0, 5000 }, + [AD5529R_RANGE_0V_10V] = { 0, 10000 }, + [AD5529R_RANGE_0V_20V] = { 0, 20000 }, + [AD5529R_RANGE_0V_40V] = { 0, 40000 }, + [AD5529R_RANGE_M5V_5V] = { -5000, 5000 }, + [AD5529R_RANGE_M10V_10V] = { -10000, 10000 }, + [AD5529R_RANGE_M15V_15V] = { -15000, 15000 }, + [AD5529R_RANGE_M20V_20V] = { -20000, 20000 }, +}; + +struct ad5529r_state { + const struct ad5529r_model_data *model_data; + struct regmap *regmap_8bit; + struct regmap *regmap_16bit; + struct iio_chan_spec channels[AD5529R_MAX_CHANNELS]; + unsigned int num_channels; + enum ad5529r_output_range output_range_idx[AD5529R_MAX_CHANNELS]; +}; + +static const struct regmap_range ad5529r_8bit_readable_ranges[] = { + regmap_reg_range(AD5529R_REG_INTERFACE_CONFIG_A, AD5529R_REG_CHIP_GRADE), + regmap_reg_range(AD5529R_REG_SCRATCH_PAD, AD5529R_REG_VENDOR_H), + regmap_reg_range(AD5529R_REG_STREAM_MODE, AD5529R_REG_INTERFACE_STATUS_A), +}; + +static const struct regmap_range ad5529r_16bit_readable_ranges[] = { + regmap_reg_range(AD5529R_REG_MULTI_DAC_CH_SEL, AD5529R_REG_INIT_CRC_ERR_STAT), + regmap_reg_range(AD5529R_REG_MULTI_DAC_HOTPATH_SW_LDAC, AD5529R_MAX_REGISTER), +}; + +static const struct regmap_access_table ad5529r_8bit_readable_table = { + .yes_ranges = ad5529r_8bit_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(ad5529r_8bit_readable_ranges), +}; + +static const struct regmap_access_table ad5529r_16bit_readable_table = { + .yes_ranges = ad5529r_16bit_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(ad5529r_16bit_readable_ranges), +}; + +static const struct regmap_range ad5529r_8bit_read_only_ranges[] = { + regmap_reg_range(AD5529R_REG_DEVICE_CONFIG, AD5529R_REG_CHIP_GRADE), + regmap_reg_range(AD5529R_REG_SPI_REVISION, AD5529R_REG_VENDOR_H), +}; + +static const struct regmap_range ad5529r_16bit_read_only_ranges[] = { + regmap_reg_range(AD5529R_REG_DAC_DATA_READBACK_BASE, + AD5529R_REG_DAC_DATA_READBACK_BASE + + (AD5529R_MAX_CHANNELS - 1) * 2), + regmap_reg_range(AD5529R_REG_TSENS_ALERT_FLAG, AD5529R_REG_TSENS_SHTD_FLAG), + regmap_reg_range(AD5529R_REG_FUNC_BUSY, AD5529R_REG_FUNC_BUSY), + regmap_reg_range(AD5529R_REG_INIT_CRC_ERR_STAT, AD5529R_REG_INIT_CRC_ERR_STAT), +}; + +static const struct regmap_access_table ad5529r_8bit_writeable_table = { + .no_ranges = ad5529r_8bit_read_only_ranges, + .n_no_ranges = ARRAY_SIZE(ad5529r_8bit_read_only_ranges), +}; + +static const struct regmap_access_table ad5529r_16bit_writeable_table = { + .no_ranges = ad5529r_16bit_read_only_ranges, + .n_no_ranges = ARRAY_SIZE(ad5529r_16bit_read_only_ranges), +}; + +static struct regmap *ad5529r_get_regmap(struct ad5529r_state *st, + unsigned int reg) +{ + if (reg <= AD5529R_8BIT_REG_MAX) + return st->regmap_8bit; + + return st->regmap_16bit; +} + +static int ad5529r_reset(struct ad5529r_state *st) +{ + struct regmap *map = st->regmap_8bit; + struct device *dev = regmap_get_device(map); + struct reset_control *rst; + int ret; + + rst = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(rst)) + return PTR_ERR(rst); + + if (rst) { + ret = reset_control_assert(rst); + if (ret) + return ret; + + /* Minimum reset low width (t_reset) is 20 ns per datasheet. */ + ndelay(20); + + ret = reset_control_deassert(rst); + if (ret) + return ret; + } else { + ret = regmap_write(map, AD5529R_REG_INTERFACE_CONFIG_A, + AD5529R_INTERFACE_CONFIG_A_SW_RESET); + if (ret) + return ret; + } + + /* + * Wait 10 ms for digital initialization to complete. + * Per datasheet, Interface Status A register NOT_READY_ERR bit is + * set if SPI transactions are attempted before digital initialization + * completes. + */ + fsleep(10 * USEC_PER_MSEC); + + return regmap_write(map, AD5529R_REG_INTERFACE_CONFIG_A, + AD5529R_INTERFACE_CONFIG_A_SDO_ENABLE | + AD5529R_INTERFACE_CONFIG_A_ADDR_ASCENSION); +} + +static int ad5529r_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad5529r_state *st = iio_priv(indio_dev); + enum ad5529r_output_range range_idx; + unsigned int reg_addr, reg_val_h; + int ret, span_mV; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * Read from DAC_INPUT_A register rather than DAC_DATA_READBACK. + * The DAC operates in transparent mode and directly reflects + * whatever value is written to the INPUT_A register. + */ + reg_addr = AD5529R_REG_DAC_INPUT_A(chan->channel); + ret = regmap_read(st->regmap_16bit, reg_addr, ®_val_h); + if (ret) + return ret; + + *val = reg_val_h; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + range_idx = st->output_range_idx[chan->channel]; + + /* + * The datasheet specifies a 4.096 V external reference, + * matching the nominal output voltage of the internal + * reference. + */ + span_mV = ad5529r_output_ranges_mV[range_idx][1] - + ad5529r_output_ranges_mV[range_idx][0]; + *val = span_mV; + *val2 = st->model_data->resolution; + + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + range_idx = st->output_range_idx[chan->channel]; + + if (ad5529r_output_ranges_mV[range_idx][0] < 0) + *val = -BIT(st->model_data->resolution - 1); + else + *val = 0; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad5529r_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad5529r_state *st = iio_priv(indio_dev); + unsigned int reg_addr; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!in_range(val, 0, BIT(st->model_data->resolution))) + return -EINVAL; + + reg_addr = AD5529R_REG_DAC_INPUT_A(chan->channel); + + return regmap_write(st->regmap_16bit, reg_addr, val); + default: + return -EINVAL; + } +} + +static int ad5529r_find_output_range(const s32 *vals) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(ad5529r_output_ranges_mV); i++) { + const s32 *range = ad5529r_output_ranges_mV[i]; + + if (vals[0] == range[0] * (MICRO / MILLI) && + vals[1] == range[1] * (MICRO / MILLI)) + return i; + } + + return -EINVAL; +} + +static int ad5529r_parse_channel_ranges(struct device *dev, + struct ad5529r_state *st) +{ + unsigned long channel_mask = 0; + + device_for_each_child_node_scoped(dev, child) { + int ret, range_idx; + u32 ch; + + if (st->num_channels == ARRAY_SIZE(st->channels)) + return dev_err_probe(dev, -ENOSPC, "Too many channels\n"); + + ret = fwnode_property_read_u32(child, "reg", &ch); + if (ret) + return dev_err_probe(dev, ret, + "Missing reg property in channel node\n"); + + if (ch >= AD5529R_MAX_CHANNELS) + return dev_err_probe(dev, -ECHRNG, + "Channel %u exceeds maximum %u\n", + ch, AD5529R_MAX_CHANNELS - 1); + + if (__test_and_set_bit(ch, &channel_mask)) + return dev_err_probe(dev, -EEXIST, + "Duplicate channel %u\n", ch); + + if (fwnode_property_present(child, "output-range-microvolt")) { + s32 vals[2]; + + /* + * DT stores cells as raw 32-bit values; signed endpoints are + * encoded by dtc in two's-complement and then interpreted + * here as s32. + */ + ret = fwnode_property_read_u32_array(child, + "output-range-microvolt", + (u32 *)vals, ARRAY_SIZE(vals)); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to read range for ch %u\n", + ch); + + range_idx = ad5529r_find_output_range(vals); + if (range_idx < 0) + return dev_err_probe(dev, range_idx, + "Invalid range [%d %d] for ch %u\n", + vals[0], vals[1], ch); + } else { + range_idx = AD5529R_RANGE_0V_5V; + } + + st->output_range_idx[ch] = range_idx; + ret = regmap_write(st->regmap_16bit, + AD5529R_REG_OUT_RANGE(ch), range_idx); + if (ret) + return dev_err_probe(dev, ret, + "Failed to configure range for ch %u\n", + ch); + + st->channels[st->num_channels++] = AD5529R_DAC_CHANNEL(ch); + } + + return 0; +} + +static int ad5529r_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad5529r_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(ad5529r_get_regmap(st, reg), reg, readval); + + return regmap_write(ad5529r_get_regmap(st, reg), reg, writeval); +} + +static const struct iio_info ad5529r_info = { + .read_raw = ad5529r_read_raw, + .write_raw = ad5529r_write_raw, + .debugfs_reg_access = ad5529r_reg_access, +}; + +static int ad5529r_probe(struct spi_device *spi) +{ + struct regmap_config regmap_16bit_cfg; + struct regmap_config regmap_8bit_cfg; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad5529r_state *st; + bool external_vref; + u32 dev_addr; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->model_data = spi_get_device_match_data(spi); + if (!st->model_data) + return dev_err_probe(dev, -ENODATA, + "Failed to identify device variant\n"); + + if (device_property_present(dev, "spi-device-addr")) { + ret = device_property_read_u32(dev, "spi-device-addr", &dev_addr); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read spi-device-addr\n"); + + if (dev_addr > 3) + return dev_err_probe(dev, -EADDRNOTAVAIL, + "spi-device-addr %u out of range [0, 3]\n", + dev_addr); + } else { + dev_addr = 0; + } + + regmap_8bit_cfg = (struct regmap_config) { + .name = "ad5529r-8bit", + .reg_bits = 16, + .val_bits = 8, + .max_register = AD5529R_8BIT_REG_MAX, + .read_flag_mask = AD5529R_SPI_READ_FLAG, + .rd_table = &ad5529r_8bit_readable_table, + .wr_table = &ad5529r_8bit_writeable_table, + .reg_base = dev_addr << AD5529R_ADDR_SHIFT, + }; + regmap_16bit_cfg = (struct regmap_config) { + .name = "ad5529r-16bit", + .reg_bits = 16, + .val_bits = 16, + .max_register = AD5529R_MAX_REGISTER, + .read_flag_mask = AD5529R_SPI_READ_FLAG, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .rd_table = &ad5529r_16bit_readable_table, + .wr_table = &ad5529r_16bit_writeable_table, + .reg_stride = 2, + .reg_base = dev_addr << AD5529R_ADDR_SHIFT, + }; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad5529r_supply_names), + ad5529r_supply_names); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable regulators\n"); + + for (unsigned int i = 0; i < ARRAY_SIZE(ad5529r_vss_supply_names); i++) { + ret = devm_regulator_get_enable_optional(dev, + ad5529r_vss_supply_names[i]); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, + "Failed to get and enable %s regulator\n", + ad5529r_vss_supply_names[i]); + } + + ret = devm_regulator_get_enable_optional(dev, "vref"); + if (ret == -ENODEV) + external_vref = false; + else if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable vref regulator\n"); + else + external_vref = true; + + /* Wait 10 ms after power-up before the first SPI transaction. */ + fsleep(10 * USEC_PER_MSEC); + + st->regmap_8bit = devm_regmap_init_spi(spi, ®map_8bit_cfg); + if (IS_ERR(st->regmap_8bit)) + return dev_err_probe(dev, PTR_ERR(st->regmap_8bit), + "Failed to initialize 8-bit regmap\n"); + + st->regmap_16bit = devm_regmap_init_spi(spi, ®map_16bit_cfg); + if (IS_ERR(st->regmap_16bit)) + return dev_err_probe(dev, PTR_ERR(st->regmap_16bit), + "Failed to initialize 16-bit regmap\n"); + + ret = ad5529r_reset(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to reset device\n"); + + ret = regmap_assign_bits(st->regmap_16bit, AD5529R_REG_REF_SEL, + AD5529R_REF_SEL_INTERNAL_REF, + !external_vref); + if (ret) + return dev_err_probe(dev, ret, "Failed to configure reference\n"); + + ret = ad5529r_parse_channel_ranges(dev, st); + if (ret) + return ret; + + indio_dev->name = st->model_data->model_name; + indio_dev->info = &ad5529r_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->channels; + indio_dev->num_channels = st->num_channels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad5529r_of_match[] = { + { .compatible = "adi,ad5529r-16", .data = &ad5529r_16bit_model_data }, + { .compatible = "adi,ad5529r-12", .data = &ad5529r_12bit_model_data }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5529r_of_match); + +static const struct spi_device_id ad5529r_id[] = { + { + .name = "ad5529r-16", + .driver_data = (kernel_ulong_t)&ad5529r_16bit_model_data, + }, + { + .name = "ad5529r-12", + .driver_data = (kernel_ulong_t)&ad5529r_12bit_model_data, + }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5529r_id); + +static struct spi_driver ad5529r_driver = { + .driver = { + .name = "ad5529r", + .of_match_table = ad5529r_of_match, + }, + .probe = ad5529r_probe, + .id_table = ad5529r_id, +}; +module_spi_driver(ad5529r_driver); + +MODULE_AUTHOR("Janani Sunil <janani.sunil@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD5529R 12/16-bit DAC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/ad5755.c b/drivers/iio/dac/ad5755.c index 0828b9909099..730289dd8ef6 100644 --- a/drivers/iio/dac/ad5755.c +++ b/drivers/iio/dac/ad5755.c @@ -819,6 +819,9 @@ static int ad5755_probe(struct spi_device *spi) spi_set_drvdata(spi, indio_dev); st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODATA; + st->spi = spi; st->pwr_down = 0xf; diff --git a/drivers/iio/dac/ad5758.c b/drivers/iio/dac/ad5758.c index 44efa373cd80..1e21ad2a050d 100644 --- a/drivers/iio/dac/ad5758.c +++ b/drivers/iio/dac/ad5758.c @@ -9,6 +9,7 @@ #include <linux/bsearch.h> #include <linux/delay.h> #include <linux/kernel.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/property.h> #include <linux/spi/spi.h> @@ -117,7 +118,7 @@ struct ad5758_state { unsigned int dc_dc_ilim; unsigned int slew_time; bool pwr_down; - __be32 d32[3]; + __be32 d32[3] __aligned(IIO_DMA_MINALIGN); }; /* @@ -540,7 +541,7 @@ static int ad5758_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_OFFSET: min = st->out_range.min; max = st->out_range.max; - *val = ((min * (1 << 16)) / (max - min)) / 1000; + *val = div_s64((s64)min * (1 << 16), max - min); return IIO_VAL_INT; default: return -EINVAL; @@ -556,6 +557,9 @@ static int ad5758_write_raw(struct iio_dev *indio_dev, switch (info) { case IIO_CHAN_INFO_RAW: + if (val < 0 || val > U16_MAX) + return -EINVAL; + mutex_lock(&st->lock); ret = ad5758_spi_reg_write(st, AD5758_DAC_INPUT, val); mutex_unlock(&st->lock); diff --git a/drivers/iio/dac/ad8460.c b/drivers/iio/dac/ad8460.c index a2292810e4cf..469294fd1783 100644 --- a/drivers/iio/dac/ad8460.c +++ b/drivers/iio/dac/ad8460.c @@ -304,7 +304,7 @@ static ssize_t ad8460_write_powerdown(struct iio_dev *indio_dev, uintptr_t priva * If powerdown is set, HVDAC is enabled and the HV driver is * enabled via HV_RESET in case it is in shutdown mode, * If powerdown is cleared, HVDAC is set to shutdown state - * as well as the HV driver. Quiescent current decreases and ouput is + * as well as the HV driver. Quiescent current decreases and output is * floating (high impedance). */ diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02-core.c index 8640b0ef4433..88bffbab0854 100644 --- a/drivers/iio/dac/mcp47feb02.c +++ b/drivers/iio/dac/mcp47feb02-core.c @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface + * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C and SPI interface * - * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * Copyright (C) 2025-2026 Microchip Technology Inc. and its subsidiaries * * Author: Ariana Lazar <ariana.lazar@microchip.com> * @@ -16,7 +16,6 @@ #include <linux/bitfield.h> #include <linux/delay.h> #include <linux/err.h> -#include <linux/i2c.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> #include <linux/kstrtox.h> @@ -29,6 +28,8 @@ #include <linux/types.h> #include <linux/units.h> +#include "mcp47feb02.h" + /* Register addresses must be left shifted with 3 positions in order to append command mask */ #define MCP47FEB02_DAC0_REG_ADDR 0x00 #define MCP47FEB02_VREF_REG_ADDR 0x40 @@ -36,7 +37,6 @@ #define MCP47FEB02_DAC_CTRL_MASK GENMASK(1, 0) #define MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR 0x50 -#define MCP47FEB02_GAIN_BIT_MASK BIT(0) #define MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK BIT(6) #define MCP47FEB02_GAIN_BITS_MASK GENMASK(15, 8) @@ -49,12 +49,10 @@ #define MCP47FEB02_NV_I2C_SLAVE_ADDR_MASK GENMASK(7, 0) /* Voltage reference, Power-Down control register and DAC Wiperlock status register fields */ -#define DAC_CTRL_MASK(ch) (GENMASK(1, 0) << (2 * (ch))) -#define DAC_CTRL_VAL(ch, val) ((val) << (2 * (ch))) +#define MCP47FEB02_VREF_PD_MASK(ch) (GENMASK(1, 0) << (2 * (ch))) -/* Gain Control and I2C Slave Address Reguster fields */ +/* Gain Control and I2C Slave Address Register fields */ #define DAC_GAIN_MASK(ch) (BIT(0) << (8 + (ch))) -#define DAC_GAIN_VAL(ch, val) ((val) << (8 + (ch))) #define REG_ADDR(reg) ((reg) << 3) #define NV_REG_ADDR(reg) ((NV_DAC_ADDR_OFFSET + (reg)) << 3) @@ -92,214 +90,6 @@ static const char * const mcp47feb02_powerdown_modes[] = { }; /** - * struct mcp47feb02_features - chip specific data - * @name: device name - * @phys_channels: number of hardware channels - * @resolution: DAC resolution - * @have_ext_vref1: does the hardware have an the second external voltage reference? - * @have_eeprom: does the hardware have an internal eeprom? - */ -struct mcp47feb02_features { - const char *name; - unsigned int phys_channels; - unsigned int resolution; - bool have_ext_vref1; - bool have_eeprom; -}; - -static const struct mcp47feb02_features mcp47feb01_chip_features = { - .name = "mcp47feb01", - .phys_channels = 1, - .resolution = 8, - .have_ext_vref1 = false, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb02_chip_features = { - .name = "mcp47feb02", - .phys_channels = 2, - .resolution = 8, - .have_ext_vref1 = false, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb04_chip_features = { - .name = "mcp47feb04", - .phys_channels = 4, - .resolution = 8, - .have_ext_vref1 = true, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb08_chip_features = { - .name = "mcp47feb08", - .phys_channels = 8, - .resolution = 8, - .have_ext_vref1 = true, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb11_chip_features = { - .name = "mcp47feb11", - .phys_channels = 1, - .resolution = 10, - .have_ext_vref1 = false, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb12_chip_features = { - .name = "mcp47feb12", - .phys_channels = 2, - .resolution = 10, - .have_ext_vref1 = false, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb14_chip_features = { - .name = "mcp47feb14", - .phys_channels = 4, - .resolution = 10, - .have_ext_vref1 = true, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb18_chip_features = { - .name = "mcp47feb18", - .phys_channels = 8, - .resolution = 10, - .have_ext_vref1 = true, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb21_chip_features = { - .name = "mcp47feb21", - .phys_channels = 1, - .resolution = 12, - .have_ext_vref1 = false, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb22_chip_features = { - .name = "mcp47feb22", - .phys_channels = 2, - .resolution = 12, - .have_ext_vref1 = false, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb24_chip_features = { - .name = "mcp47feb24", - .phys_channels = 4, - .resolution = 12, - .have_ext_vref1 = true, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47feb28_chip_features = { - .name = "mcp47feb28", - .phys_channels = 8, - .resolution = 12, - .have_ext_vref1 = true, - .have_eeprom = true, -}; - -static const struct mcp47feb02_features mcp47fvb01_chip_features = { - .name = "mcp47fvb01", - .phys_channels = 1, - .resolution = 8, - .have_ext_vref1 = false, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb02_chip_features = { - .name = "mcp47fvb02", - .phys_channels = 2, - .resolution = 8, - .have_ext_vref1 = false, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb04_chip_features = { - .name = "mcp47fvb04", - .phys_channels = 4, - .resolution = 8, - .have_ext_vref1 = true, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb08_chip_features = { - .name = "mcp47fvb08", - .phys_channels = 8, - .resolution = 8, - .have_ext_vref1 = true, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb11_chip_features = { - .name = "mcp47fvb11", - .phys_channels = 1, - .resolution = 10, - .have_ext_vref1 = false, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb12_chip_features = { - .name = "mcp47fvb12", - .phys_channels = 2, - .resolution = 10, - .have_ext_vref1 = false, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb14_chip_features = { - .name = "mcp47fvb14", - .phys_channels = 4, - .resolution = 10, - .have_ext_vref1 = true, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb18_chip_features = { - .name = "mcp47fvb18", - .phys_channels = 8, - .resolution = 10, - .have_ext_vref1 = true, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb21_chip_features = { - .name = "mcp47fvb21", - .phys_channels = 1, - .resolution = 12, - .have_ext_vref1 = false, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb22_chip_features = { - .name = "mcp47fvb22", - .phys_channels = 2, - .resolution = 12, - .have_ext_vref1 = false, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb24_chip_features = { - .name = "mcp47fvb24", - .phys_channels = 4, - .resolution = 12, - .have_ext_vref1 = true, - .have_eeprom = false, -}; - -static const struct mcp47feb02_features mcp47fvb28_chip_features = { - .name = "mcp47fvb28", - .phys_channels = 8, - .resolution = 12, - .have_ext_vref1 = true, - .have_eeprom = false, -}; - -/** * struct mcp47feb02_channel_data - channel configuration * @ref_mode: chosen voltage for reference * @use_2x_gain: output driver gain control @@ -379,7 +169,7 @@ static const struct regmap_access_table mcp47feb02_volatile_table = { .n_yes_ranges = ARRAY_SIZE(mcp47feb02_volatile_ranges), }; -static const struct regmap_config mcp47feb02_regmap_config = { +const struct regmap_config mcp47feb02_regmap_config = { .name = "mcp47feb02_regmap", .reg_bits = 8, .val_bits = 16, @@ -391,6 +181,7 @@ static const struct regmap_config mcp47feb02_regmap_config = { .cache_type = REGCACHE_MAPLE, .val_format_endian = REGMAP_ENDIAN_BIG, }; +EXPORT_SYMBOL_NS_GPL(mcp47feb02_regmap_config, "IIO_MCP47FEB02"); /* For devices that doesn't have nonvolatile memory */ static const struct regmap_range mcp47fvb02_readable_ranges[] = { @@ -421,7 +212,7 @@ static const struct regmap_access_table mcp47fvb02_volatile_table = { .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_volatile_ranges), }; -static const struct regmap_config mcp47fvb02_regmap_config = { +const struct regmap_config mcp47fvb02_regmap_config = { .name = "mcp47fvb02_regmap", .reg_bits = 8, .val_bits = 16, @@ -433,6 +224,7 @@ static const struct regmap_config mcp47fvb02_regmap_config = { .cache_type = REGCACHE_MAPLE, .val_format_endian = REGMAP_ENDIAN_BIG, }; +EXPORT_SYMBOL_NS_GPL(mcp47fvb02_regmap_config, "IIO_MCP47FEB02"); static int mcp47feb02_write_to_eeprom(struct mcp47feb02_data *data, unsigned int reg, unsigned int val) @@ -443,12 +235,10 @@ static int mcp47feb02_write_to_eeprom(struct mcp47feb02_data *data, unsigned int * Wait until the currently occurring EEPROM Write Cycle is completed. * Only serial commands to the volatile memory are allowed. */ - guard(mutex)(&data->lock); - ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, eewa_val, !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK), - USEC_PER_MSEC, USEC_PER_MSEC * 5); + 1 * USEC_PER_MSEC, 150 * USEC_PER_MSEC); if (ret) return ret; @@ -468,7 +258,9 @@ static ssize_t store_eeprom_store(struct device *dev, struct device_attribute *a return ret; if (!state) - return 0; + return len; + + guard(mutex)(&data->lock); /* * Verify DAC Wiper and DAC Configuration are unlocked. If both are disabled, @@ -508,7 +300,7 @@ static ssize_t store_eeprom_store(struct device *dev, struct device_attribute *a ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, eewa_val, !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK), - USEC_PER_MSEC, USEC_PER_MSEC * 5); + 1 * USEC_PER_MSEC, 150 * USEC_PER_MSEC); if (ret) return ret; @@ -551,11 +343,13 @@ static int mcp47feb02_suspend(struct device *dev) for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) { u8 pd_mode; + u32 mask; data->chdata[ch].powerdown = true; pd_mode = data->chdata[ch].powerdown_mode + 1; + mask = MCP47FEB02_VREF_PD_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, - DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode)); + mask, field_prep(mask, pd_mode)); if (ret) return ret; @@ -576,30 +370,30 @@ static int mcp47feb02_resume(struct device *dev) guard(mutex)(&data->lock); for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) { - u8 pd_mode; + u32 mask; int ret; data->chdata[ch].powerdown = false; - pd_mode = data->chdata[ch].powerdown_mode + 1; ret = regmap_write(data->regmap, REG_ADDR(ch), data->chdata[ch].dac_data); if (ret) return ret; + mask = MCP47FEB02_VREF_PD_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR, - DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode)); + mask, field_prep(mask, data->chdata[ch].ref_mode)); if (ret) return ret; + mask = DAC_GAIN_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, - DAC_GAIN_MASK(ch), - DAC_GAIN_VAL(ch, data->chdata[ch].use_2x_gain)); + mask, field_prep(mask, data->chdata[ch].use_2x_gain)); if (ret) return ret; + mask = MCP47FEB02_VREF_PD_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, - DAC_CTRL_MASK(ch), - DAC_CTRL_VAL(ch, MCP47FEB02_NORMAL_OPERATION)); + mask, field_prep(mask, MCP47FEB02_NORMAL_OPERATION)); if (ret) return ret; } @@ -642,6 +436,7 @@ static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t p u32 reg = ch->address; u8 tmp_pd_mode; bool state; + u32 mask; int ret; guard(mutex)(&data->lock); @@ -655,8 +450,9 @@ static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t p * requires writing normal operation mode (0) to the channel-specific register bits. */ tmp_pd_mode = state ? (data->chdata[reg].powerdown_mode + 1) : MCP47FEB02_NORMAL_OPERATION; + mask = MCP47FEB02_VREF_PD_MASK(reg); ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, - DAC_CTRL_MASK(reg), DAC_CTRL_VAL(reg, tmp_pd_mode)); + mask, field_prep(mask, tmp_pd_mode)); if (ret) return ret; @@ -665,7 +461,8 @@ static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t p return len; } -static DEFINE_SIMPLE_DEV_PM_OPS(mcp47feb02_pm_ops, mcp47feb02_suspend, mcp47feb02_resume); +EXPORT_NS_SIMPLE_DEV_PM_OPS(mcp47feb02_pm_ops, mcp47feb02_suspend, + mcp47feb02_resume, IIO_MCP47FEB02); static const struct iio_enum mcp47febxx_powerdown_mode_enum = { .items = mcp47feb02_powerdown_modes, @@ -800,6 +597,7 @@ static int mcp47feb02_check_scale(struct mcp47feb02_data *data, int val, int val static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale) { int tmp_val, ret; + u32 mask; if (scale == MCP47FEB02_SCALE_VDD) { tmp_val = MCP47FEB02_VREF_VDD; @@ -821,8 +619,9 @@ static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale) tmp_val = MCP47FEB02_INTERNAL_BAND_GAP; } + mask = MCP47FEB02_VREF_PD_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR, - DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, tmp_val)); + mask, field_prep(mask, tmp_val)); if (ret) return ret; @@ -845,6 +644,7 @@ static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale) static int mcp47feb02_set_scale(struct mcp47feb02_data *data, int ch, int scale) { int tmp_val, ret; + u32 mask; ret = mcp47feb02_ch_scale(data, ch, scale); if (ret) @@ -855,8 +655,9 @@ static int mcp47feb02_set_scale(struct mcp47feb02_data *data, int ch, int scale) else tmp_val = MCP47FEB02_GAIN_BIT_X1; + mask = DAC_GAIN_MASK(ch); ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, - DAC_GAIN_MASK(ch), DAC_GAIN_VAL(ch, tmp_val)); + mask, field_prep(mask, tmp_val)); if (ret) return ret; @@ -922,7 +723,10 @@ static int mcp47feb02_read_label(struct iio_dev *indio_dev, struct iio_chan_spec { struct mcp47feb02_data *data = iio_priv(indio_dev); - return sysfs_emit(label, "%s\n", data->labels[ch->address]); + if (data->labels[ch->address]) + return sysfs_emit(label, "%s\n", data->labels[ch->address]); + + return -ENOENT; } static const struct iio_info mcp47feb02_info = { @@ -975,10 +779,11 @@ static int mcp47feb02_parse_fw(struct iio_dev *indio_dev, set_bit(reg, &data->active_channels_mask); - ret = fwnode_property_read_string(child, "label", &data->labels[reg]); - if (ret) - return dev_err_probe(dev, ret, "%pfw: invalid label\n", - fwnode_get_name(child)); + if (fwnode_property_present(child, "label")) { + ret = fwnode_property_read_string(child, "label", &data->labels[reg]); + if (ret) + dev_warn_probe(dev, ret, "%pfw: invalid label\n", child); + } chanspec.address = reg; chanspec.channel = reg; @@ -1016,13 +821,17 @@ static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data) if (ret) return ret; - gain_ch = gain_ch & MCP47FEB02_GAIN_BITS_MASK; for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) { struct device *dev = regmap_get_device(data->regmap); - unsigned int pd_tmp; + unsigned int pd_tmp, dac_val; + + ret = regmap_read(data->regmap, REG_ADDR(i), &dac_val); + if (ret) + return ret; + data->chdata[i].dac_data = dac_val; - data->chdata[i].ref_mode = (vref_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK; - data->chdata[i].use_2x_gain = (gain_ch >> i) & MCP47FEB02_GAIN_BIT_MASK; + data->chdata[i].ref_mode = field_get(MCP47FEB02_VREF_PD_MASK(i), vref_ch); + data->chdata[i].use_2x_gain = field_get(DAC_GAIN_MASK(i), gain_ch); /* * Inform the user that the current voltage reference read from the volatile @@ -1065,7 +874,7 @@ static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data) break; } - pd_tmp = (pd_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK; + pd_tmp = field_get(MCP47FEB02_VREF_PD_MASK(i), pd_ch); data->chdata[i].powerdown_mode = pd_tmp ? (pd_tmp - 1) : pd_tmp; data->chdata[i].powerdown = !!(data->chdata[i].powerdown_mode); } @@ -1090,10 +899,10 @@ static int mcp47feb02_init_ch_scales(struct mcp47feb02_data *data, int vdd_uV, return 0; } -static int mcp47feb02_probe(struct i2c_client *client) +int mcp47feb02_common_probe(const struct mcp47feb02_features *chip_features, + struct regmap *regmap) { - const struct mcp47feb02_features *chip_features; - struct device *dev = &client->dev; + struct device *dev = regmap_get_device(regmap); struct mcp47feb02_data *data; struct iio_dev *indio_dev; int vref1_uV, vref_uV, vdd_uV, ret; @@ -1102,22 +911,16 @@ static int mcp47feb02_probe(struct i2c_client *client) if (!indio_dev) return -ENOMEM; - data = iio_priv(indio_dev); - chip_features = i2c_get_match_data(client); - if (!chip_features) - return -EINVAL; + dev_set_drvdata(dev, indio_dev); + data = iio_priv(indio_dev); data->chip_features = chip_features; + data->regmap = regmap; - if (chip_features->have_eeprom) { - data->regmap = devm_regmap_init_i2c(client, &mcp47feb02_regmap_config); + if (chip_features->have_eeprom) indio_dev->info = &mcp47feb02_info; - } else { - data->regmap = devm_regmap_init_i2c(client, &mcp47fvb02_regmap_config); + else indio_dev->info = &mcp47fvb02_info; - } - if (IS_ERR(data->regmap)) - return dev_err_probe(dev, PTR_ERR(data->regmap), "Error initializing i2c regmap\n"); indio_dev->name = chip_features->name; @@ -1174,76 +977,8 @@ static int mcp47feb02_probe(struct i2c_client *client) return devm_iio_device_register(dev, indio_dev); } - -static const struct i2c_device_id mcp47feb02_id[] = { - { .name = "mcp47feb01", .driver_data = (kernel_ulong_t)&mcp47feb01_chip_features }, - { .name = "mcp47feb02", .driver_data = (kernel_ulong_t)&mcp47feb02_chip_features }, - { .name = "mcp47feb04", .driver_data = (kernel_ulong_t)&mcp47feb04_chip_features }, - { .name = "mcp47feb08", .driver_data = (kernel_ulong_t)&mcp47feb08_chip_features }, - { .name = "mcp47feb11", .driver_data = (kernel_ulong_t)&mcp47feb11_chip_features }, - { .name = "mcp47feb12", .driver_data = (kernel_ulong_t)&mcp47feb12_chip_features }, - { .name = "mcp47feb14", .driver_data = (kernel_ulong_t)&mcp47feb14_chip_features }, - { .name = "mcp47feb18", .driver_data = (kernel_ulong_t)&mcp47feb18_chip_features }, - { .name = "mcp47feb21", .driver_data = (kernel_ulong_t)&mcp47feb21_chip_features }, - { .name = "mcp47feb22", .driver_data = (kernel_ulong_t)&mcp47feb22_chip_features }, - { .name = "mcp47feb24", .driver_data = (kernel_ulong_t)&mcp47feb24_chip_features }, - { .name = "mcp47feb28", .driver_data = (kernel_ulong_t)&mcp47feb28_chip_features }, - { .name = "mcp47fvb01", .driver_data = (kernel_ulong_t)&mcp47fvb01_chip_features }, - { .name = "mcp47fvb02", .driver_data = (kernel_ulong_t)&mcp47fvb02_chip_features }, - { .name = "mcp47fvb04", .driver_data = (kernel_ulong_t)&mcp47fvb04_chip_features }, - { .name = "mcp47fvb08", .driver_data = (kernel_ulong_t)&mcp47fvb08_chip_features }, - { .name = "mcp47fvb11", .driver_data = (kernel_ulong_t)&mcp47fvb11_chip_features }, - { .name = "mcp47fvb12", .driver_data = (kernel_ulong_t)&mcp47fvb12_chip_features }, - { .name = "mcp47fvb14", .driver_data = (kernel_ulong_t)&mcp47fvb14_chip_features }, - { .name = "mcp47fvb18", .driver_data = (kernel_ulong_t)&mcp47fvb18_chip_features }, - { .name = "mcp47fvb21", .driver_data = (kernel_ulong_t)&mcp47fvb21_chip_features }, - { .name = "mcp47fvb22", .driver_data = (kernel_ulong_t)&mcp47fvb22_chip_features }, - { .name = "mcp47fvb24", .driver_data = (kernel_ulong_t)&mcp47fvb24_chip_features }, - { .name = "mcp47fvb28", .driver_data = (kernel_ulong_t)&mcp47fvb28_chip_features }, - { } -}; -MODULE_DEVICE_TABLE(i2c, mcp47feb02_id); - -static const struct of_device_id mcp47feb02_of_match[] = { - { .compatible = "microchip,mcp47feb01", .data = &mcp47feb01_chip_features }, - { .compatible = "microchip,mcp47feb02", .data = &mcp47feb02_chip_features }, - { .compatible = "microchip,mcp47feb04", .data = &mcp47feb04_chip_features }, - { .compatible = "microchip,mcp47feb08", .data = &mcp47feb08_chip_features }, - { .compatible = "microchip,mcp47feb11", .data = &mcp47feb11_chip_features }, - { .compatible = "microchip,mcp47feb12", .data = &mcp47feb12_chip_features }, - { .compatible = "microchip,mcp47feb14", .data = &mcp47feb14_chip_features }, - { .compatible = "microchip,mcp47feb18", .data = &mcp47feb18_chip_features }, - { .compatible = "microchip,mcp47feb21", .data = &mcp47feb21_chip_features }, - { .compatible = "microchip,mcp47feb22", .data = &mcp47feb22_chip_features }, - { .compatible = "microchip,mcp47feb24", .data = &mcp47feb24_chip_features }, - { .compatible = "microchip,mcp47feb28", .data = &mcp47feb28_chip_features }, - { .compatible = "microchip,mcp47fvb01", .data = &mcp47fvb01_chip_features }, - { .compatible = "microchip,mcp47fvb02", .data = &mcp47fvb02_chip_features }, - { .compatible = "microchip,mcp47fvb04", .data = &mcp47fvb04_chip_features }, - { .compatible = "microchip,mcp47fvb08", .data = &mcp47fvb08_chip_features }, - { .compatible = "microchip,mcp47fvb11", .data = &mcp47fvb11_chip_features }, - { .compatible = "microchip,mcp47fvb12", .data = &mcp47fvb12_chip_features }, - { .compatible = "microchip,mcp47fvb14", .data = &mcp47fvb14_chip_features }, - { .compatible = "microchip,mcp47fvb18", .data = &mcp47fvb18_chip_features }, - { .compatible = "microchip,mcp47fvb21", .data = &mcp47fvb21_chip_features }, - { .compatible = "microchip,mcp47fvb22", .data = &mcp47fvb22_chip_features }, - { .compatible = "microchip,mcp47fvb24", .data = &mcp47fvb24_chip_features }, - { .compatible = "microchip,mcp47fvb28", .data = &mcp47fvb28_chip_features }, - { } -}; -MODULE_DEVICE_TABLE(of, mcp47feb02_of_match); - -static struct i2c_driver mcp47feb02_driver = { - .driver = { - .name = "mcp47feb02", - .of_match_table = mcp47feb02_of_match, - .pm = pm_sleep_ptr(&mcp47feb02_pm_ops), - }, - .probe = mcp47feb02_probe, - .id_table = mcp47feb02_id, -}; -module_i2c_driver(mcp47feb02_driver); +EXPORT_SYMBOL_NS(mcp47feb02_common_probe, "IIO_MCP47FEB02"); MODULE_AUTHOR("Ariana Lazar <ariana.lazar@microchip.com>"); -MODULE_DESCRIPTION("IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface"); +MODULE_DESCRIPTION("IIO driver for MCP47FEB02/MCP48FEB02 Multi-Channel DAC"); MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/mcp47feb02-i2c.c b/drivers/iio/dac/mcp47feb02-i2c.c new file mode 100644 index 000000000000..7ec0cccad79b --- /dev/null +++ b/drivers/iio/dac/mcp47feb02-i2c.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface + * + * Copyright (C) 2025-2026 Microchip Technology Inc. and its subsidiaries + * + * Author: Ariana Lazar <ariana.lazar@microchip.com> + * + * Datasheet links for devices with I2C interface: + * [MCP47FEBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005375A.pdf + * [MCP47FVBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005405A.pdf + * [MCP47FxBx4/8] https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP47FXBX48-Data-Sheet-DS200006368A.pdf + */ +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/regmap.h> +#include <linux/types.h> + +#include "mcp47feb02.h" + +/* Parts with EEPROM memory */ +MCP47FEB02_CHIP_INFO(mcp47feb01, 1, 8, false, true); +MCP47FEB02_CHIP_INFO(mcp47feb02, 2, 8, false, true); +MCP47FEB02_CHIP_INFO(mcp47feb04, 4, 8, true, true); +MCP47FEB02_CHIP_INFO(mcp47feb08, 8, 8, true, true); +MCP47FEB02_CHIP_INFO(mcp47feb11, 1, 10, false, true); +MCP47FEB02_CHIP_INFO(mcp47feb12, 2, 10, false, true); +MCP47FEB02_CHIP_INFO(mcp47feb14, 4, 10, true, true); +MCP47FEB02_CHIP_INFO(mcp47feb18, 8, 10, true, true); +MCP47FEB02_CHIP_INFO(mcp47feb21, 1, 12, false, true); +MCP47FEB02_CHIP_INFO(mcp47feb22, 2, 12, false, true); +MCP47FEB02_CHIP_INFO(mcp47feb24, 4, 12, true, true); +MCP47FEB02_CHIP_INFO(mcp47feb28, 8, 12, true, true); + +/* Parts without EEPROM memory */ +MCP47FEB02_CHIP_INFO(mcp47fvb01, 1, 8, false, false); +MCP47FEB02_CHIP_INFO(mcp47fvb02, 2, 8, false, false); +MCP47FEB02_CHIP_INFO(mcp47fvb04, 4, 8, true, false); +MCP47FEB02_CHIP_INFO(mcp47fvb08, 8, 8, true, false); +MCP47FEB02_CHIP_INFO(mcp47fvb11, 1, 10, false, false); +MCP47FEB02_CHIP_INFO(mcp47fvb12, 2, 10, false, false); +MCP47FEB02_CHIP_INFO(mcp47fvb14, 4, 10, true, false); +MCP47FEB02_CHIP_INFO(mcp47fvb18, 8, 10, true, false); +MCP47FEB02_CHIP_INFO(mcp47fvb21, 1, 12, false, false); +MCP47FEB02_CHIP_INFO(mcp47fvb22, 2, 12, false, false); +MCP47FEB02_CHIP_INFO(mcp47fvb24, 4, 12, true, false); +MCP47FEB02_CHIP_INFO(mcp47fvb28, 8, 12, true, false); + +static int mcp47feb02_i2c_probe(struct i2c_client *client) +{ + const struct mcp47feb02_features *chip_features; + struct device *dev = &client->dev; + struct regmap *regmap; + + chip_features = i2c_get_match_data(client); + if (!chip_features) + return dev_err_probe(dev, -ENODATA, "No match data found\n"); + + if (chip_features->have_eeprom) + regmap = devm_regmap_init_i2c(client, &mcp47feb02_regmap_config); + else + regmap = devm_regmap_init_i2c(client, &mcp47fvb02_regmap_config); + + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing I2C regmap\n"); + + return mcp47feb02_common_probe(chip_features, regmap); +} + +static const struct i2c_device_id mcp47feb02_i2c_id[] = { + { .name = "mcp47feb01", .driver_data = (kernel_ulong_t)&mcp47feb01_chip_features }, + { .name = "mcp47feb02", .driver_data = (kernel_ulong_t)&mcp47feb02_chip_features }, + { .name = "mcp47feb04", .driver_data = (kernel_ulong_t)&mcp47feb04_chip_features }, + { .name = "mcp47feb08", .driver_data = (kernel_ulong_t)&mcp47feb08_chip_features }, + { .name = "mcp47feb11", .driver_data = (kernel_ulong_t)&mcp47feb11_chip_features }, + { .name = "mcp47feb12", .driver_data = (kernel_ulong_t)&mcp47feb12_chip_features }, + { .name = "mcp47feb14", .driver_data = (kernel_ulong_t)&mcp47feb14_chip_features }, + { .name = "mcp47feb18", .driver_data = (kernel_ulong_t)&mcp47feb18_chip_features }, + { .name = "mcp47feb21", .driver_data = (kernel_ulong_t)&mcp47feb21_chip_features }, + { .name = "mcp47feb22", .driver_data = (kernel_ulong_t)&mcp47feb22_chip_features }, + { .name = "mcp47feb24", .driver_data = (kernel_ulong_t)&mcp47feb24_chip_features }, + { .name = "mcp47feb28", .driver_data = (kernel_ulong_t)&mcp47feb28_chip_features }, + { .name = "mcp47fvb01", .driver_data = (kernel_ulong_t)&mcp47fvb01_chip_features }, + { .name = "mcp47fvb02", .driver_data = (kernel_ulong_t)&mcp47fvb02_chip_features }, + { .name = "mcp47fvb04", .driver_data = (kernel_ulong_t)&mcp47fvb04_chip_features }, + { .name = "mcp47fvb08", .driver_data = (kernel_ulong_t)&mcp47fvb08_chip_features }, + { .name = "mcp47fvb11", .driver_data = (kernel_ulong_t)&mcp47fvb11_chip_features }, + { .name = "mcp47fvb12", .driver_data = (kernel_ulong_t)&mcp47fvb12_chip_features }, + { .name = "mcp47fvb14", .driver_data = (kernel_ulong_t)&mcp47fvb14_chip_features }, + { .name = "mcp47fvb18", .driver_data = (kernel_ulong_t)&mcp47fvb18_chip_features }, + { .name = "mcp47fvb21", .driver_data = (kernel_ulong_t)&mcp47fvb21_chip_features }, + { .name = "mcp47fvb22", .driver_data = (kernel_ulong_t)&mcp47fvb22_chip_features }, + { .name = "mcp47fvb24", .driver_data = (kernel_ulong_t)&mcp47fvb24_chip_features }, + { .name = "mcp47fvb28", .driver_data = (kernel_ulong_t)&mcp47fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp47feb02_i2c_id); + +static const struct of_device_id mcp47feb02_of_i2c_match[] = { + { .compatible = "microchip,mcp47feb01", .data = &mcp47feb01_chip_features }, + { .compatible = "microchip,mcp47feb02", .data = &mcp47feb02_chip_features }, + { .compatible = "microchip,mcp47feb04", .data = &mcp47feb04_chip_features }, + { .compatible = "microchip,mcp47feb08", .data = &mcp47feb08_chip_features }, + { .compatible = "microchip,mcp47feb11", .data = &mcp47feb11_chip_features }, + { .compatible = "microchip,mcp47feb12", .data = &mcp47feb12_chip_features }, + { .compatible = "microchip,mcp47feb14", .data = &mcp47feb14_chip_features }, + { .compatible = "microchip,mcp47feb18", .data = &mcp47feb18_chip_features }, + { .compatible = "microchip,mcp47feb21", .data = &mcp47feb21_chip_features }, + { .compatible = "microchip,mcp47feb22", .data = &mcp47feb22_chip_features }, + { .compatible = "microchip,mcp47feb24", .data = &mcp47feb24_chip_features }, + { .compatible = "microchip,mcp47feb28", .data = &mcp47feb28_chip_features }, + { .compatible = "microchip,mcp47fvb01", .data = &mcp47fvb01_chip_features }, + { .compatible = "microchip,mcp47fvb02", .data = &mcp47fvb02_chip_features }, + { .compatible = "microchip,mcp47fvb04", .data = &mcp47fvb04_chip_features }, + { .compatible = "microchip,mcp47fvb08", .data = &mcp47fvb08_chip_features }, + { .compatible = "microchip,mcp47fvb11", .data = &mcp47fvb11_chip_features }, + { .compatible = "microchip,mcp47fvb12", .data = &mcp47fvb12_chip_features }, + { .compatible = "microchip,mcp47fvb14", .data = &mcp47fvb14_chip_features }, + { .compatible = "microchip,mcp47fvb18", .data = &mcp47fvb18_chip_features }, + { .compatible = "microchip,mcp47fvb21", .data = &mcp47fvb21_chip_features }, + { .compatible = "microchip,mcp47fvb22", .data = &mcp47fvb22_chip_features }, + { .compatible = "microchip,mcp47fvb24", .data = &mcp47fvb24_chip_features }, + { .compatible = "microchip,mcp47fvb28", .data = &mcp47fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp47feb02_of_i2c_match); + +static struct i2c_driver mcp47feb02_i2c_driver = { + .driver = { + .name = "mcp47feb02", + .of_match_table = mcp47feb02_of_i2c_match, + .pm = pm_sleep_ptr(&mcp47feb02_pm_ops), + }, + .probe = mcp47feb02_i2c_probe, + .id_table = mcp47feb02_i2c_id, +}; +module_i2c_driver(mcp47feb02_i2c_driver); + +MODULE_AUTHOR("Ariana Lazar <ariana.lazar@microchip.com>"); +MODULE_DESCRIPTION("IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_MCP47FEB02"); diff --git a/drivers/iio/dac/mcp47feb02-spi.c b/drivers/iio/dac/mcp47feb02-spi.c new file mode 100644 index 000000000000..974f8fe1b86e --- /dev/null +++ b/drivers/iio/dac/mcp47feb02-spi.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for MCP48FEB02 Multi-Channel DAC with SPI interface + * + * Copyright (C) 2025-2026 Microchip Technology Inc. and its subsidiaries + * + * Author: Ariana Lazar <ariana.lazar@microchip.com> + * + * Datasheet links for devices with SPI interface: + * [MCP48FEBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005429B.pdf + * [MCP48FVBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005466A.pdf + * [MCP48FxBx4/8] https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP48FXBX4-8-Family-Data-Sheet-DS20006362A.pdf + */ +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> +#include <linux/types.h> + +#include "mcp47feb02.h" + +/* Parts with EEPROM memory */ +MCP47FEB02_CHIP_INFO(mcp48feb01, 1, 8, false, true); +MCP47FEB02_CHIP_INFO(mcp48feb02, 2, 8, false, true); +MCP47FEB02_CHIP_INFO(mcp48feb04, 4, 8, true, true); +MCP47FEB02_CHIP_INFO(mcp48feb08, 8, 8, true, true); +MCP47FEB02_CHIP_INFO(mcp48feb11, 1, 10, false, true); +MCP47FEB02_CHIP_INFO(mcp48feb12, 2, 10, false, true); +MCP47FEB02_CHIP_INFO(mcp48feb14, 4, 10, true, true); +MCP47FEB02_CHIP_INFO(mcp48feb18, 8, 10, true, true); +MCP47FEB02_CHIP_INFO(mcp48feb21, 1, 12, false, true); +MCP47FEB02_CHIP_INFO(mcp48feb22, 2, 12, false, true); +MCP47FEB02_CHIP_INFO(mcp48feb24, 4, 12, true, true); +MCP47FEB02_CHIP_INFO(mcp48feb28, 8, 12, true, true); + +/* Parts without EEPROM memory */ +MCP47FEB02_CHIP_INFO(mcp48fvb01, 1, 8, false, false); +MCP47FEB02_CHIP_INFO(mcp48fvb02, 2, 8, false, false); +MCP47FEB02_CHIP_INFO(mcp48fvb04, 4, 8, true, false); +MCP47FEB02_CHIP_INFO(mcp48fvb08, 8, 8, true, false); +MCP47FEB02_CHIP_INFO(mcp48fvb11, 1, 10, false, false); +MCP47FEB02_CHIP_INFO(mcp48fvb12, 2, 10, false, false); +MCP47FEB02_CHIP_INFO(mcp48fvb14, 4, 10, true, false); +MCP47FEB02_CHIP_INFO(mcp48fvb18, 8, 10, true, false); +MCP47FEB02_CHIP_INFO(mcp48fvb21, 1, 12, false, false); +MCP47FEB02_CHIP_INFO(mcp48fvb22, 2, 12, false, false); +MCP47FEB02_CHIP_INFO(mcp48fvb24, 4, 12, true, false); +MCP47FEB02_CHIP_INFO(mcp48fvb28, 8, 12, true, false); + +static int mcp47feb02_spi_probe(struct spi_device *spi) +{ + const struct mcp47feb02_features *chip_features; + struct device *dev = &spi->dev; + struct regmap *regmap; + + chip_features = spi_get_device_match_data(spi); + if (!chip_features) + return dev_err_probe(dev, -ENODATA, "No match data found\n"); + + if (chip_features->have_eeprom) + regmap = devm_regmap_init_spi(spi, &mcp47feb02_regmap_config); + else + regmap = devm_regmap_init_spi(spi, &mcp47fvb02_regmap_config); + + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing SPI regmap\n"); + + return mcp47feb02_common_probe(chip_features, regmap); +} + +static const struct spi_device_id mcp47feb02_spi_id[] = { + { .name = "mcp48feb01", .driver_data = (kernel_ulong_t)&mcp48feb01_chip_features }, + { .name = "mcp48feb02", .driver_data = (kernel_ulong_t)&mcp48feb02_chip_features }, + { .name = "mcp48feb04", .driver_data = (kernel_ulong_t)&mcp48feb04_chip_features }, + { .name = "mcp48feb08", .driver_data = (kernel_ulong_t)&mcp48feb08_chip_features }, + { .name = "mcp48feb11", .driver_data = (kernel_ulong_t)&mcp48feb11_chip_features }, + { .name = "mcp48feb12", .driver_data = (kernel_ulong_t)&mcp48feb12_chip_features }, + { .name = "mcp48feb14", .driver_data = (kernel_ulong_t)&mcp48feb14_chip_features }, + { .name = "mcp48feb18", .driver_data = (kernel_ulong_t)&mcp48feb18_chip_features }, + { .name = "mcp48feb21", .driver_data = (kernel_ulong_t)&mcp48feb21_chip_features }, + { .name = "mcp48feb22", .driver_data = (kernel_ulong_t)&mcp48feb22_chip_features }, + { .name = "mcp48feb24", .driver_data = (kernel_ulong_t)&mcp48feb24_chip_features }, + { .name = "mcp48feb28", .driver_data = (kernel_ulong_t)&mcp48feb28_chip_features }, + { .name = "mcp48fvb01", .driver_data = (kernel_ulong_t)&mcp48fvb01_chip_features }, + { .name = "mcp48fvb02", .driver_data = (kernel_ulong_t)&mcp48fvb02_chip_features }, + { .name = "mcp48fvb04", .driver_data = (kernel_ulong_t)&mcp48fvb04_chip_features }, + { .name = "mcp48fvb08", .driver_data = (kernel_ulong_t)&mcp48fvb08_chip_features }, + { .name = "mcp48fvb11", .driver_data = (kernel_ulong_t)&mcp48fvb11_chip_features }, + { .name = "mcp48fvb12", .driver_data = (kernel_ulong_t)&mcp48fvb12_chip_features }, + { .name = "mcp48fvb14", .driver_data = (kernel_ulong_t)&mcp48fvb14_chip_features }, + { .name = "mcp48fvb18", .driver_data = (kernel_ulong_t)&mcp48fvb18_chip_features }, + { .name = "mcp48fvb21", .driver_data = (kernel_ulong_t)&mcp48fvb21_chip_features }, + { .name = "mcp48fvb22", .driver_data = (kernel_ulong_t)&mcp48fvb22_chip_features }, + { .name = "mcp48fvb24", .driver_data = (kernel_ulong_t)&mcp48fvb24_chip_features }, + { .name = "mcp48fvb28", .driver_data = (kernel_ulong_t)&mcp48fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(spi, mcp47feb02_spi_id); + +static const struct of_device_id mcp47feb02_of_spi_match[] = { + { .compatible = "microchip,mcp48feb01", .data = &mcp48feb01_chip_features }, + { .compatible = "microchip,mcp48feb02", .data = &mcp48feb02_chip_features }, + { .compatible = "microchip,mcp48feb04", .data = &mcp48feb04_chip_features }, + { .compatible = "microchip,mcp48feb08", .data = &mcp48feb08_chip_features }, + { .compatible = "microchip,mcp48feb11", .data = &mcp48feb11_chip_features }, + { .compatible = "microchip,mcp48feb12", .data = &mcp48feb12_chip_features }, + { .compatible = "microchip,mcp48feb14", .data = &mcp48feb14_chip_features }, + { .compatible = "microchip,mcp48feb18", .data = &mcp48feb18_chip_features }, + { .compatible = "microchip,mcp48feb21", .data = &mcp48feb21_chip_features }, + { .compatible = "microchip,mcp48feb22", .data = &mcp48feb22_chip_features }, + { .compatible = "microchip,mcp48feb24", .data = &mcp48feb24_chip_features }, + { .compatible = "microchip,mcp48feb28", .data = &mcp48feb28_chip_features }, + { .compatible = "microchip,mcp48fvb01", .data = &mcp48fvb01_chip_features }, + { .compatible = "microchip,mcp48fvb02", .data = &mcp48fvb02_chip_features }, + { .compatible = "microchip,mcp48fvb04", .data = &mcp48fvb04_chip_features }, + { .compatible = "microchip,mcp48fvb08", .data = &mcp48fvb08_chip_features }, + { .compatible = "microchip,mcp48fvb11", .data = &mcp48fvb11_chip_features }, + { .compatible = "microchip,mcp48fvb12", .data = &mcp48fvb12_chip_features }, + { .compatible = "microchip,mcp48fvb14", .data = &mcp48fvb14_chip_features }, + { .compatible = "microchip,mcp48fvb18", .data = &mcp48fvb18_chip_features }, + { .compatible = "microchip,mcp48fvb21", .data = &mcp48fvb21_chip_features }, + { .compatible = "microchip,mcp48fvb22", .data = &mcp48fvb22_chip_features }, + { .compatible = "microchip,mcp48fvb24", .data = &mcp48fvb24_chip_features }, + { .compatible = "microchip,mcp48fvb28", .data = &mcp48fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp47feb02_of_spi_match); + +static struct spi_driver mcp47feb02_spi_driver = { + .driver = { + .name = "mcp47feb02", + .of_match_table = mcp47feb02_of_spi_match, + .pm = pm_sleep_ptr(&mcp47feb02_pm_ops), + }, + .probe = mcp47feb02_spi_probe, + .id_table = mcp47feb02_spi_id, +}; +module_spi_driver(mcp47feb02_spi_driver); + +MODULE_AUTHOR("Ariana Lazar <ariana.lazar@microchip.com>"); +MODULE_DESCRIPTION("IIO driver for MCP48FEB02 Multi-Channel DAC with SPI interface"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_MCP47FEB02"); diff --git a/drivers/iio/dac/mcp47feb02.h b/drivers/iio/dac/mcp47feb02.h new file mode 100644 index 000000000000..37fa4497c1c1 --- /dev/null +++ b/drivers/iio/dac/mcp47feb02.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#ifndef __DRIVERS_IIO_DAC_MCP47FEB02_H__ +#define __DRIVERS_IIO_DAC_MCP47FEB02_H__ + +#include <linux/pm.h> +#include <linux/types.h> + +#define MCP47FEB02_CHIP_INFO(_name, _channels, _res, _vref1, _eeprom) \ +static const struct mcp47feb02_features _name##_chip_features = { \ + .name = #_name, \ + .phys_channels = _channels, \ + .resolution = _res, \ + .have_ext_vref1 = _vref1, \ + .have_eeprom = _eeprom, \ +} + +/** + * struct mcp47feb02_features - chip specific data + * @name: device name + * @phys_channels: number of hardware channels + * @resolution: DAC resolution + * @have_ext_vref1: does the hardware have an the second external voltage reference? + * @have_eeprom: does the hardware have an internal eeprom? + */ +struct mcp47feb02_features { + const char *name; + unsigned int phys_channels; + unsigned int resolution; + bool have_ext_vref1; + bool have_eeprom; +}; + +extern const struct regmap_config mcp47feb02_regmap_config; +extern const struct regmap_config mcp47fvb02_regmap_config; + +int mcp47feb02_common_probe(const struct mcp47feb02_features *chip_features, + struct regmap *regmap); + +extern const struct dev_pm_ops mcp47feb02_pm_ops; + +#endif /* __DRIVERS_IIO_DAC_MCP47FEB02_H__ */ diff --git a/drivers/iio/dac/mcp4821.c b/drivers/iio/dac/mcp4821.c index 8d8c00cd60e9..59defb1b5e62 100644 --- a/drivers/iio/dac/mcp4821.c +++ b/drivers/iio/dac/mcp4821.c @@ -255,9 +255,13 @@ static const struct iio_info mcp4821_info = { static int mcp4821_probe(struct spi_device *spi) { + const struct mcp4821_chip_info *info; struct iio_dev *indio_dev; struct mcp4821_state *state; - const struct mcp4821_chip_info *info; + + info = spi_get_device_match_data(spi); + if (!info) + return -ENODATA; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state)); if (indio_dev == NULL) @@ -268,7 +272,6 @@ static int mcp4821_probe(struct spi_device *spi) /* default gain is 2x */ state->gain = 2; - info = spi_get_device_match_data(spi); indio_dev->name = info->name; indio_dev->info = &mcp4821_info; indio_dev->modes = INDIO_DIRECT_MODE; diff --git a/drivers/iio/dummy/iio_simple_dummy.c b/drivers/iio/dummy/iio_simple_dummy.c index 19fcdbbc11c6..28ae1e7b593d 100644 --- a/drivers/iio/dummy/iio_simple_dummy.c +++ b/drivers/iio/dummy/iio_simple_dummy.c @@ -116,7 +116,7 @@ static const struct iio_chan_spec iio_dummy_channels[] = { BIT(IIO_CHAN_INFO_OFFSET) | /* * in_voltage0_scale - * Multipler for userspace to apply post offset + * Multiplier for userspace to apply post offset * when converting to standard units (microvolts) */ BIT(IIO_CHAN_INFO_SCALE), diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig index 90c6304c4bcd..994d199080eb 100644 --- a/drivers/iio/frequency/Kconfig +++ b/drivers/iio/frequency/Kconfig @@ -23,6 +23,27 @@ config AD9523 endmenu +menu "Direct Digital Synthesis" + +config AD9910 + tristate "Analog Devices AD9910 Direct Digital Synthesizer" + depends on SPI + depends on GPIOLIB + select CRC32 + select FW_LOADER + select FW_UPLOAD + help + Say yes here to build support for Analog Devices AD9910 + 1 GSPS, 14-Bit DDS with integrated DAC. + + Supports single tone mode with 8 configurable profiles + and digital ramp generation. + + To compile this driver as a module, choose M here: the + module will be called ad9910. + +endmenu + # # Phase-Locked Loop (PLL) frequency synthesizers # diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile index 53b4d01414d8..e3b1d36ed620 100644 --- a/drivers/iio/frequency/Makefile +++ b/drivers/iio/frequency/Makefile @@ -5,6 +5,7 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AD9523) += ad9523.o +obj-$(CONFIG_AD9910) += ad9910.o obj-$(CONFIG_ADF41513) += adf41513.o obj-$(CONFIG_ADF4350) += adf4350.o obj-$(CONFIG_ADF4371) += adf4371.o diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c new file mode 100644 index 000000000000..04a4e184b873 --- /dev/null +++ b/drivers/iio/frequency/ad9910.c @@ -0,0 +1,2347 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD9910 SPI DDS (Direct Digital Synthesizer) driver + * + * Copyright 2026 Analog Devices Inc. + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/crc32.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/device-id/of.h> +#include <linux/device-id/spi.h> +#include <linux/device/devres.h> +#include <linux/err.h> +#include <linux/firmware.h> +#include <linux/gpio/consumer.h> +#include <linux/log2.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/seq_file.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/units.h> +#include <linux/unaligned.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* Register addresses */ +#define AD9910_REG_CFR1 0x00 +#define AD9910_REG_CFR2 0x01 +#define AD9910_REG_CFR3 0x02 +#define AD9910_REG_AUX_DAC 0x03 +#define AD9910_REG_IO_UPDATE_RATE 0x04 +#define AD9910_REG_FTW 0x07 +#define AD9910_REG_POW 0x08 +#define AD9910_REG_ASF 0x09 +#define AD9910_REG_MULTICHIP_SYNC 0x0A +#define AD9910_REG_DRG_LIMIT 0x0B +#define AD9910_REG_DRG_STEP 0x0C +#define AD9910_REG_DRG_RATE 0x0D +#define AD9910_REG_PROFILE0 0x0E +#define AD9910_REG_PROFILE1 0x0F +#define AD9910_REG_PROFILE2 0x10 +#define AD9910_REG_PROFILE3 0x11 +#define AD9910_REG_PROFILE4 0x12 +#define AD9910_REG_PROFILE5 0x13 +#define AD9910_REG_PROFILE6 0x14 +#define AD9910_REG_PROFILE7 0x15 +#define AD9910_REG_RAM 0x16 + +#define AD9910_REG_NUM_CACHED 0x16 +#define AD9910_REG_PROFILE(x) (AD9910_REG_PROFILE0 + (x)) + +/* CFR1 bit definitions */ +#define AD9910_CFR1_RAM_ENABLE_MSK BIT(31) +#define AD9910_CFR1_RAM_PLAYBACK_DEST_MSK GENMASK(30, 29) +#define AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK BIT(23) +#define AD9910_CFR1_INV_SINC_EN_MSK BIT(22) +#define AD9910_CFR1_INT_PROFILE_CTL_MSK GENMASK(20, 17) +#define AD9910_CFR1_SELECT_SINE_MSK BIT(16) +#define AD9910_CFR1_LOAD_LRR_IO_UPDATE_MSK BIT(15) +#define AD9910_CFR1_AUTOCLR_DIG_RAMP_ACCUM_MSK BIT(14) +#define AD9910_CFR1_AUTOCLR_PHASE_ACCUM_MSK BIT(13) +#define AD9910_CFR1_CLEAR_DIG_RAMP_ACCUM_MSK BIT(12) +#define AD9910_CFR1_CLEAR_PHASE_ACCUM_MSK BIT(11) +#define AD9910_CFR1_LOAD_ARR_IO_UPDATE_MSK BIT(10) +#define AD9910_CFR1_OSK_ENABLE_MSK BIT(9) +#define AD9910_CFR1_SELECT_AUTO_OSK_MSK BIT(8) +#define AD9910_CFR1_DIGITAL_POWER_DOWN_MSK BIT(7) +#define AD9910_CFR1_DAC_POWER_DOWN_MSK BIT(6) +#define AD9910_CFR1_REFCLK_INPUT_POWER_DOWN_MSK BIT(5) +#define AD9910_CFR1_AUX_DAC_POWER_DOWN_MSK BIT(4) +#define AD9910_CFR1_EXT_POWER_DOWN_CTL_MSK BIT(3) +#define AD9910_CFR1_SDIO_INPUT_ONLY_MSK BIT(1) +#define AD9910_CFR1_LSB_FIRST_MSK BIT(0) + +/* DIGITAL and REFCLK_INPUT powerdown bits are effective without I/O update */ +#define AD9910_CFR1_SW0_POWER_DOWN_MSK (AD9910_CFR1_DIGITAL_POWER_DOWN_MSK | \ + AD9910_CFR1_REFCLK_INPUT_POWER_DOWN_MSK) +/* DAC and AUX_DAC powerdown require I/O update */ +#define AD9910_CFR1_SW1_POWER_DOWN_MSK (AD9910_CFR1_DAC_POWER_DOWN_MSK | \ + AD9910_CFR1_AUX_DAC_POWER_DOWN_MSK) + +#define AD9910_CFR1_SW_POWER_DOWN_MSK (AD9910_CFR1_SW0_POWER_DOWN_MSK | \ + AD9910_CFR1_SW1_POWER_DOWN_MSK) + +/* CFR2 bit definitions */ +#define AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK BIT(24) +#define AD9910_CFR2_INTERNAL_IO_UPDATE_MSK BIT(23) +#define AD9910_CFR2_SYNC_CLK_EN_MSK BIT(22) +#define AD9910_CFR2_DRG_DEST_MSK GENMASK(21, 20) +#define AD9910_CFR2_DRG_ENABLE_MSK BIT(19) +#define AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK BIT(18) +#define AD9910_CFR2_DRG_NO_DWELL_LOW_MSK BIT(17) +#define AD9910_CFR2_DRG_NO_DWELL_MSK GENMASK(18, 17) +#define AD9910_CFR2_READ_EFFECTIVE_FTW_MSK BIT(16) +#define AD9910_CFR2_IO_UPDATE_RATE_CTL_MSK GENMASK(15, 14) +#define AD9910_CFR2_PDCLK_ENABLE_MSK BIT(11) +#define AD9910_CFR2_PDCLK_INVERT_MSK BIT(10) +#define AD9910_CFR2_TXENABLE_INVERT_MSK BIT(9) +#define AD9910_CFR2_MATCHED_LATENCY_EN_MSK BIT(7) +#define AD9910_CFR2_DATA_ASM_HOLD_LAST_MSK BIT(6) +#define AD9910_CFR2_SYNC_TIMING_VAL_DISABLE_MSK BIT(5) +#define AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK BIT(4) +#define AD9910_CFR2_FM_GAIN_MSK GENMASK(3, 0) + +/* CFR3 bit definitions */ +#define AD9910_CFR3_OPEN_MSK (BIT(27) | GENMASK(18, 16)) +#define AD9910_CFR3_DRV0_MSK GENMASK(29, 28) +#define AD9910_CFR3_VCO_SEL_MSK GENMASK(26, 24) +#define AD9910_CFR3_ICP_MSK GENMASK(21, 19) +#define AD9910_CFR3_REFCLK_DIV_BYPASS_MSK BIT(15) +#define AD9910_CFR3_REFCLK_DIV_RESETB_MSK BIT(14) +#define AD9910_CFR3_PFD_RESET_MSK BIT(10) +#define AD9910_CFR3_PLL_EN_MSK BIT(8) +#define AD9910_CFR3_N_MSK GENMASK(7, 1) + +/* Auxiliary DAC Control Register Bits */ +#define AD9910_AUX_DAC_FSC_MSK GENMASK(7, 0) + +/* POW Register Bits */ +#define AD9910_POW_PP_LSB_MSK GENMASK(7, 0) + +/* ASF Register Bits */ +#define AD9910_ASF_RAMP_RATE_MSK GENMASK(31, 16) +#define AD9910_ASF_SCALE_FACTOR_MSK GENMASK(15, 2) +#define AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK GENMASK(7, 2) +#define AD9910_ASF_STEP_SIZE_MSK GENMASK(1, 0) + +/* Multichip Sync Register Bits */ +#define AD9910_MC_SYNC_VALIDATION_DELAY_MSK GENMASK(31, 28) +#define AD9910_MC_SYNC_RECEIVER_ENABLE_MSK BIT(27) +#define AD9910_MC_SYNC_GENERATOR_ENABLE_MSK BIT(26) +#define AD9910_MC_SYNC_GENERATOR_POLARITY_MSK BIT(25) +#define AD9910_MC_SYNC_STATE_PRESET_MSK GENMASK(23, 18) +#define AD9910_MC_SYNC_OUTPUT_DELAY_MSK GENMASK(15, 11) +#define AD9910_MC_SYNC_INPUT_DELAY_MSK GENMASK(7, 3) + +/* Digital Ramp Limit Register */ +#define AD9910_DRG_LIMIT_UPPER_MSK GENMASK_ULL(63, 32) +#define AD9910_DRG_LIMIT_LOWER_MSK GENMASK_ULL(31, 0) + +/* Digital Ramp Step Register */ +#define AD9910_DRG_STEP_DEC_MSK GENMASK_ULL(63, 32) +#define AD9910_DRG_STEP_INC_MSK GENMASK_ULL(31, 0) + +/* Digital Ramp Rate Register */ +#define AD9910_DRG_RATE_DEC_MSK GENMASK(31, 16) +#define AD9910_DRG_RATE_INC_MSK GENMASK(15, 0) + +/* Profile Register Format (Single Tone Mode) */ +#define AD9910_PROFILE_ST_ASF_MSK GENMASK_ULL(61, 48) +#define AD9910_PROFILE_ST_POW_MSK GENMASK_ULL(47, 32) +#define AD9910_PROFILE_ST_FTW_MSK GENMASK_ULL(31, 0) + +/* Profile Register Format (RAM Mode) */ +#define AD9910_PROFILE_RAM_OPEN_MSK GENMASK_ULL(61, 56) +#define AD9910_PROFILE_RAM_STEP_RATE_MSK GENMASK_ULL(55, 40) +#define AD9910_PROFILE_RAM_END_ADDR_MSK GENMASK_ULL(39, 30) +#define AD9910_PROFILE_RAM_START_ADDR_MSK GENMASK_ULL(23, 14) +#define AD9910_PROFILE_RAM_NO_DWELL_HIGH_MSK BIT_ULL(5) +#define AD9910_PROFILE_RAM_ZERO_CROSSING_MSK BIT_ULL(3) +#define AD9910_PROFILE_RAM_MODE_CONTROL_MSK GENMASK_ULL(2, 0) + +/* Device constants */ +#define AD9910_PI_NANORAD 3141592653UL +#define AD9910_PI_PICORAD 3141592653590ULL + +#define AD9910_MAX_SYSCLK_HZ (1000 * HZ_PER_MHZ) +#define AD9910_MAX_PHASE_MICRORAD (AD9910_PI_NANORAD / 500) + +#define AD9910_ASF_MAX FIELD_MAX(AD9910_PROFILE_ST_ASF_MSK) +#define AD9910_ASF_PP_LSB_MAX FIELD_MAX(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK) +#define AD9910_POW_MAX FIELD_MAX(AD9910_PROFILE_ST_POW_MSK) +#define AD9910_POW_PP_LSB_MAX FIELD_MAX(AD9910_POW_PP_LSB_MSK) +#define AD9910_STEP_RATE_MAX FIELD_MAX(AD9910_DRG_RATE_DEC_MSK) +#define AD9910_NUM_PROFILES 8 + +#define AD9910_RAM_FW_MAGIC 0x00AD9910 +#define AD9910_RAM_FW_V1 0x0001 +#define AD9910_RAM_SIZE_MAX_WORDS 1024 +#define AD9910_RAM_WORD_SIZE sizeof(u32) +#define AD9910_RAM_SIZE_MAX_BYTES (AD9910_RAM_SIZE_MAX_WORDS * AD9910_RAM_WORD_SIZE) +#define AD9910_RAM_ADDR_MAX (AD9910_RAM_SIZE_MAX_WORDS - 1) + +#define AD9910_RAM_ENABLED(st) \ + FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, (st)->reg[AD9910_REG_CFR1].val32) + +#define AD9910_DEST_FREQUENCY 0 +#define AD9910_DEST_PHASE 1 +#define AD9910_DEST_AMPLITUDE 2 +#define AD9910_DEST_POLAR 3 + +/* PLL constants */ +#define AD9910_PLL_MIN_N 12 +#define AD9910_PLL_MAX_N 127 + +#define AD9910_PLL_IN_MIN_FREQ_HZ (3307 * HZ_PER_KHZ) +#define AD9910_PLL_IN_MAX_FREQ_HZ (60 * HZ_PER_MHZ) + +#define AD9910_PLL_OUT_MIN_FREQ_HZ (420 * HZ_PER_MHZ) +#define AD9910_PLL_OUT_MAX_FREQ_HZ AD9910_MAX_SYSCLK_HZ + +#define AD9910_VCO0_RANGE_AUTO_MAX_HZ (457 * HZ_PER_MHZ) +#define AD9910_VCO1_RANGE_AUTO_MAX_HZ (530 * HZ_PER_MHZ) +#define AD9910_VCO2_RANGE_AUTO_MAX_HZ (632 * HZ_PER_MHZ) +#define AD9910_VCO3_RANGE_AUTO_MAX_HZ (775 * HZ_PER_MHZ) +#define AD9910_VCO4_RANGE_AUTO_MAX_HZ (897 * HZ_PER_MHZ) +#define AD9910_VCO_RANGE_NUM 6 + +#define AD9910_ICP_MIN_uA 212 +#define AD9910_ICP_MAX_uA 387 +#define AD9910_ICP_STEP_uA 25 + +#define AD9910_DAC_IOUT_MAX_uA 31590 +#define AD9910_DAC_IOUT_DEFAULT_uA 20070 +#define AD9910_DAC_IOUT_MIN_uA 8640 + +/* altcurrent ABI is in mA */ +#define AD9910_NANO_MILLIAMP_PER_MICROAMP 1000000UL +#define AD9910_PICO_MILLIAMP_PER_MICROAMP 1000000000UL + +#define AD9910_REFDIV2_MIN_FREQ_HZ (25 * HZ_PER_MHZ) +#define AD9910_REFDIV2_MAX_FREQ_HZ (1900 * HZ_PER_MHZ) + +#define AD9910_WAKEUP_DELAY_us 1000 /* 1ms: datasheet Table 1 */ +#define AD9910_RESET_DELAY_us 1 /* 5 sysclk cycles: < 1us */ + +#define AD9910_SPI_DATA_IDX 1 +#define AD9910_SPI_DATA_LEN_MAX AD9910_RAM_SIZE_MAX_BYTES +#define AD9910_SPI_MESSAGE_LEN_MAX (AD9910_SPI_DATA_IDX + AD9910_SPI_DATA_LEN_MAX) +#define AD9910_SPI_READ_MSK BIT(7) +#define AD9910_SPI_ADDR_MSK GENMASK(4, 0) + +/** + * enum ad9910_channel - AD9910 channel identifiers in priority order + * + * @AD9910_CHANNEL_PHY: Physical output channel + * @AD9910_CHANNEL_PROFILE_0: Profile 0 output channel + * @AD9910_CHANNEL_PROFILE_1: Profile 1 output channel + * @AD9910_CHANNEL_PROFILE_2: Profile 2 output channel + * @AD9910_CHANNEL_PROFILE_3: Profile 3 output channel + * @AD9910_CHANNEL_PROFILE_4: Profile 4 output channel + * @AD9910_CHANNEL_PROFILE_5: Profile 5 output channel + * @AD9910_CHANNEL_PROFILE_6: Profile 6 output channel + * @AD9910_CHANNEL_PROFILE_7: Profile 7 output channel + * @AD9910_CHANNEL_PARALLEL: Parallel Data output channel + * @AD9910_CHANNEL_PARALLEL_POLAR: Parallel Polar Data output channel + * @AD9910_CHANNEL_DRG: Digital Ramp Generator output channel + * @AD9910_CHANNEL_DRG_RAMP_UP: DRG ramp up channel + * @AD9910_CHANNEL_DRG_RAMP_DOWN: DRG ramp down channel + * @AD9910_CHANNEL_RAM: RAM control output channel + * @AD9910_CHANNEL_OSK: Output Shift Keying output channel + */ +enum ad9910_channel { + AD9910_CHANNEL_PHY = 100, + AD9910_CHANNEL_PROFILE_0 = 110, + AD9910_CHANNEL_PROFILE_1 = 111, + AD9910_CHANNEL_PROFILE_2 = 112, + AD9910_CHANNEL_PROFILE_3 = 113, + AD9910_CHANNEL_PROFILE_4 = 114, + AD9910_CHANNEL_PROFILE_5 = 115, + AD9910_CHANNEL_PROFILE_6 = 116, + AD9910_CHANNEL_PROFILE_7 = 117, + AD9910_CHANNEL_PARALLEL = 120, + AD9910_CHANNEL_PARALLEL_POLAR = 121, + AD9910_CHANNEL_DRG = 130, + AD9910_CHANNEL_DRG_RAMP_UP = 131, + AD9910_CHANNEL_DRG_RAMP_DOWN = 132, + AD9910_CHANNEL_RAM = 140, + AD9910_CHANNEL_OSK = 150, +}; + +/** + * struct ad9910_ram_fw - AD9910 RAM firmware format + * @magic: Magic number for RAM firmware validation + * @version: Firmware version number + * @wcount: Number of RAM words to be written + * @crc: CRC32 checksum of the RAM data for integrity verification + * @cfr1: Value of CFR1 register to be configured (not all fields are + * used, but this is included here for convenience) + * @profiles: Array of RAM profile configurations + * @words: Array of RAM words to be written. Data pattern should be set in + * reverse order and wcount specifies the number of words in this + * array + */ +struct ad9910_ram_fw { + __be32 magic; + __be16 version; + __be16 wcount; + __be32 crc; + __be32 cfr1; + __be64 profiles[AD9910_NUM_PROFILES]; + __be32 words[] __counted_by_be(wcount); +} __packed; + +enum { + AD9910_CHAN_IDX_PHY, + AD9910_CHAN_IDX_PROFILE_0, + AD9910_CHAN_IDX_PROFILE_1, + AD9910_CHAN_IDX_PROFILE_2, + AD9910_CHAN_IDX_PROFILE_3, + AD9910_CHAN_IDX_PROFILE_4, + AD9910_CHAN_IDX_PROFILE_5, + AD9910_CHAN_IDX_PROFILE_6, + AD9910_CHAN_IDX_PROFILE_7, + AD9910_CHAN_IDX_PARALLEL_AMP, + AD9910_CHAN_IDX_PARALLEL_PHASE, + AD9910_CHAN_IDX_PARALLEL_FREQ, + AD9910_CHAN_IDX_PARALLEL_POLAR_AMP, + AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE, + AD9910_CHAN_IDX_DRG_FREQ, + AD9910_CHAN_IDX_DRG_PHASE, + AD9910_CHAN_IDX_DRG_AMP, + AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP, + AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN, + AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP, + AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN, + AD9910_CHAN_IDX_DRG_AMP_RAMP_UP, + AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN, + AD9910_CHAN_IDX_RAM, + AD9910_CHAN_IDX_OSK, +}; + +enum { + AD9910_POWERDOWN, + AD9910_DWELL_EN, + AD9910_ROC, + AD9910_ROC_AVAIL, +}; + +struct ad9910_data { + u32 sysclk_freq_hz; + u32 output_current_uA; + + u16 pll_charge_pump_current; + u8 refclk_out_drv; + bool pll_enabled; +}; + +union ad9910_reg { + u64 val64; + u32 val32; + u16 val16; +}; + +struct ad9910_state { + struct spi_device *spi; + struct clk *refclk; + struct fw_upload *ram_fwu; + + struct gpio_desc *gpio_pwdown; + struct gpio_desc *gpio_update; + struct gpio_descs *gpio_profile; + + /* cached registers */ + union ad9910_reg reg[AD9910_REG_NUM_CACHED]; + + /* + * alternate profile registers used to store RAM profile settings when + * RAM mode is disabled and Single Tone profile settings when RAM mode + * is enabled. + */ + u64 reg_profile[AD9910_NUM_PROFILES]; + + /* Lock for accessing device registers and state variables */ + struct mutex lock; + + struct ad9910_data data; + u8 profile; + + bool ram_fwu_cancel; + char ram_fwu_name[20]; + + /* + * RAM loading requires a reasonable amount of bytes, at the same time + * DMA capable SPI drivers requires the transfer buffers to live in + * their own cache lines. + */ + u8 tx_buf[AD9910_SPI_MESSAGE_LEN_MAX] __aligned(IIO_DMA_MINALIGN); +}; + +/** + * ad9910_rational_scale() - Perform scaling of input given a reference. + * @input: The input value to be scaled. + * @scale: The numerator of the scaling factor. + * @reference: The denominator of the scaling factor. + * + * Closest rounding with mul_u64_add_u64_div_u64 + * + * Return: The scaled value. + */ +static u64 ad9910_rational_scale(u64 input, u64 scale, u64 reference) +{ + return mul_u64_add_u64_div_u64(input, scale, reference >> 1, reference); +} + +static inline u64 ad9910_ram_profile_val(struct ad9910_state *st) +{ + if (AD9910_RAM_ENABLED(st)) + return st->reg[AD9910_REG_PROFILE(st->profile)].val64; + else + return st->reg_profile[st->profile]; +} + +static inline u64 ad9910_st_profile_val(struct ad9910_state *st, u8 profile) +{ + if (AD9910_RAM_ENABLED(st)) + return st->reg_profile[profile]; + else + return st->reg[AD9910_REG_PROFILE(profile)].val64; +} + +static int ad9910_io_update(struct ad9910_state *st) +{ + if (st->gpio_update) { + gpiod_set_value_cansleep(st->gpio_update, 1); + fsleep(1); + gpiod_set_value_cansleep(st->gpio_update, 0); + } + + return 0; +} + +static int ad9910_spi_read(struct ad9910_state *st, u8 reg, void *data, + size_t len) +{ + u8 inst = AD9910_SPI_READ_MSK | FIELD_PREP(AD9910_SPI_ADDR_MSK, reg); + + return spi_write_then_read(st->spi, &inst, sizeof(inst), data, len); +} + +static int ad9910_spi_write(struct ad9910_state *st, u8 reg, size_t len, + bool update) +{ + int ret; + + st->tx_buf[0] = FIELD_PREP(AD9910_SPI_ADDR_MSK, reg); + ret = spi_write(st->spi, st->tx_buf, AD9910_SPI_DATA_IDX + len); + if (ret) + return ret; + + if (update) + return ad9910_io_update(st); + + return 0; +} + +#define AD9910_REG_READ_FN(nb) \ +static int ad9910_reg##nb##_read(struct ad9910_state *st, u8 reg, \ + u##nb * data) \ +{ \ + __be##nb be_data; \ + int ret; \ + \ + ret = ad9910_spi_read(st, reg, &be_data, sizeof(be_data)); \ + if (ret) \ + return ret; \ + \ + *data = be##nb##_to_cpu(be_data); \ + return ret; \ +} + +AD9910_REG_READ_FN(16) +AD9910_REG_READ_FN(32) +AD9910_REG_READ_FN(64) + +#define AD9910_REG_WRITE_FN(nb) \ +static int ad9910_reg##nb##_write(struct ad9910_state *st, u8 reg, \ + u##nb data, bool update) \ +{ \ + int ret; \ + \ + put_unaligned_be##nb(data, &st->tx_buf[AD9910_SPI_DATA_IDX]); \ + ret = ad9910_spi_write(st, reg, sizeof(data), update); \ + if (ret) \ + return ret; \ + \ + st->reg[reg].val##nb = data; \ + return ret; \ +} + +AD9910_REG_WRITE_FN(16) +AD9910_REG_WRITE_FN(32) +AD9910_REG_WRITE_FN(64) + +#define AD9910_REG_UPDATE_FN(nb) \ +static int ad9910_reg##nb##_update(struct ad9910_state *st, \ + u8 reg, u##nb mask, \ + u##nb data, bool update) \ +{ \ + u##nb reg_val = (st->reg[reg].val##nb & ~mask) | (data & mask); \ + \ + if (reg_val == st->reg[reg].val##nb && !update) \ + return 0; \ + \ + return ad9910_reg##nb##_write(st, reg, reg_val, update); \ +} + +AD9910_REG_UPDATE_FN(16) +AD9910_REG_UPDATE_FN(32) +AD9910_REG_UPDATE_FN(64) + +static int ad9910_set_dac_current(struct ad9910_state *st, u32 val_uA, + bool update) +{ + u32 code; + + /* FSC = (86.4 / Rset) * (1 + CODE/96) where Rset = 10k ohms */ + val_uA = clamp(val_uA, AD9910_DAC_IOUT_MIN_uA, AD9910_DAC_IOUT_MAX_uA); + code = DIV_ROUND_CLOSEST(val_uA - AD9910_DAC_IOUT_MIN_uA, 90); + st->data.output_current_uA = AD9910_DAC_IOUT_MIN_uA + code * 90; + + return ad9910_reg32_write(st, AD9910_REG_AUX_DAC, code, update); +} + +static int ad9910_set_sysclk_freq(struct ad9910_state *st, u32 freq_hz, + bool update) +{ + struct device *dev = &st->spi->dev; + unsigned long refclk_freq_hz; + u32 sysclk_freq_hz; + u32 tmp32, vco_sel; + int ret; + + if (!freq_hz || freq_hz > AD9910_MAX_SYSCLK_HZ) + return -EINVAL; + + refclk_freq_hz = clk_get_rate(st->refclk); + if (st->data.pll_enabled) { + if (refclk_freq_hz < AD9910_PLL_IN_MIN_FREQ_HZ || + refclk_freq_hz > AD9910_PLL_IN_MAX_FREQ_HZ) { + dev_err(dev, + "REF_CLK freq %lu Hz is out of PLL input range\n", + refclk_freq_hz); + return -ERANGE; + } + + tmp32 = DIV_ROUND_CLOSEST(freq_hz, refclk_freq_hz); + tmp32 = clamp(tmp32, DIV_ROUND_UP(AD9910_PLL_OUT_MIN_FREQ_HZ, refclk_freq_hz), + AD9910_PLL_OUT_MAX_FREQ_HZ / refclk_freq_hz); + tmp32 = clamp(tmp32, AD9910_PLL_MIN_N, AD9910_PLL_MAX_N); + sysclk_freq_hz = refclk_freq_hz * tmp32; + + if (sysclk_freq_hz <= AD9910_VCO0_RANGE_AUTO_MAX_HZ) + vco_sel = 0; + else if (sysclk_freq_hz <= AD9910_VCO1_RANGE_AUTO_MAX_HZ) + vco_sel = 1; + else if (sysclk_freq_hz <= AD9910_VCO2_RANGE_AUTO_MAX_HZ) + vco_sel = 2; + else if (sysclk_freq_hz <= AD9910_VCO3_RANGE_AUTO_MAX_HZ) + vco_sel = 3; + else if (sysclk_freq_hz <= AD9910_VCO4_RANGE_AUTO_MAX_HZ) + vco_sel = 4; + else + vco_sel = 5; + + ret = ad9910_reg32_update(st, AD9910_REG_CFR3, + AD9910_CFR3_N_MSK | AD9910_CFR3_VCO_SEL_MSK, + FIELD_PREP(AD9910_CFR3_N_MSK, tmp32) | + FIELD_PREP(AD9910_CFR3_VCO_SEL_MSK, vco_sel), + update); + if (ret) + return ret; + } else { + if (refclk_freq_hz < AD9910_REFDIV2_MIN_FREQ_HZ || + refclk_freq_hz > AD9910_REFDIV2_MAX_FREQ_HZ) { + dev_err(dev, + "REF_CLK freq %lu Hz is out of divider range\n", + refclk_freq_hz); + return -ERANGE; + } + + tmp32 = DIV_ROUND_CLOSEST(refclk_freq_hz, freq_hz); + tmp32 = clamp(tmp32, 1U, 2U); + sysclk_freq_hz = refclk_freq_hz / tmp32; + tmp32 = AD9910_CFR3_VCO_SEL_MSK | + FIELD_PREP(AD9910_CFR3_REFCLK_DIV_BYPASS_MSK, tmp32 % 2); + ret = ad9910_reg32_update(st, AD9910_REG_CFR3, + AD9910_CFR3_VCO_SEL_MSK | + AD9910_CFR3_REFCLK_DIV_BYPASS_MSK, + tmp32, update); + if (ret) + return ret; + } + + st->data.sysclk_freq_hz = sysclk_freq_hz; + + return 0; +} + +static int ad9910_profile_set(struct ad9910_state *st, u8 profile) +{ + DECLARE_BITMAP(values, BITS_PER_TYPE(profile)); + + st->profile = profile; + values[0] = profile; + gpiod_multi_set_value_cansleep(st->gpio_profile, values); + + return 0; +} + +static bool ad9910_sw_powerdown_get(struct ad9910_state *st) +{ + return FIELD_GET(AD9910_CFR1_SW_POWER_DOWN_MSK, + st->reg[AD9910_REG_CFR1].val32) ? true : false; +} + +static int ad9910_sw_powerdown_set(struct ad9910_state *st, bool enable) +{ + int ret; + + if (ad9910_sw_powerdown_get(st) == enable) + return 0; + + /* + * When powering down, the DAC and AUX_DAC (SW1) must be powered down + * first, as they require an I/O update to take effect. The opposite is + * true when powering up, the DAC and AUX_DAC must be powered up last, + * i.e., after the DIGITAL and REFCLK_INPUT (SW0) power up. + */ + if (enable) { + ret = ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_SW1_POWER_DOWN_MSK, + AD9910_CFR1_SW1_POWER_DOWN_MSK, + true); + if (ret) + return ret; + + return ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_SW0_POWER_DOWN_MSK, + AD9910_CFR1_SW0_POWER_DOWN_MSK, + false); + } + + ret = ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_SW0_POWER_DOWN_MSK, 0, false); + if (ret) + return ret; + + return ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_SW1_POWER_DOWN_MSK, 0, true); +} + +static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad9910_state *st = iio_priv(indio_dev); + int val; + + guard(mutex)(&st->lock); + + switch (private) { + case AD9910_POWERDOWN: + val = ad9910_sw_powerdown_get(st); + break; + case AD9910_DWELL_EN: + if (chan->channel == AD9910_CHANNEL_DRG_RAMP_UP) + val = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK, + st->reg[AD9910_REG_CFR2].val32) ? 0 : 1; + else + val = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_LOW_MSK, + st->reg[AD9910_REG_CFR2].val32) ? 0 : 1; + break; + default: + return -EINVAL; + } + + return iio_format_value(buf, IIO_VAL_INT, 1, &val); +} + +static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad9910_state *st = iio_priv(indio_dev); + u32 val32; + int ret; + + ret = kstrtou32(buf, 10, &val32); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + switch (private) { + case AD9910_POWERDOWN: + ret = ad9910_sw_powerdown_set(st, val32 ? true : false); + if (ret) + return ret; + break; + case AD9910_DWELL_EN: + if (chan->channel == AD9910_CHANNEL_DRG_RAMP_UP) { + val32 = val32 ? 0 : AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK; + ret = ad9910_reg32_update(st, AD9910_REG_CFR2, + AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK, + val32, true); + if (ret) + return ret; + } else { + val32 = val32 ? 0 : AD9910_CFR2_DRG_NO_DWELL_LOW_MSK; + ret = ad9910_reg32_update(st, AD9910_REG_CFR2, + AD9910_CFR2_DRG_NO_DWELL_LOW_MSK, + val32, true); + if (ret) + return ret; + } + break; + default: + return -EINVAL; + } + + return len; +} + +static ssize_t ad9910_drg_roc_read(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad9910_state *st = iio_priv(indio_dev); + u64 roc64; + u32 rate; + + guard(mutex)(&st->lock); + + switch (chan->channel) { + case AD9910_CHANNEL_DRG_RAMP_UP: + roc64 = FIELD_GET(AD9910_DRG_STEP_INC_MSK, + st->reg[AD9910_REG_DRG_STEP].val64); + rate = FIELD_GET(AD9910_DRG_RATE_INC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + roc64 = FIELD_GET(AD9910_DRG_STEP_DEC_MSK, + st->reg[AD9910_REG_DRG_STEP].val64); + rate = FIELD_GET(AD9910_DRG_RATE_DEC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; + default: + return -EINVAL; + } + + if (!rate) + return -ERANGE; + + roc64 *= st->data.sysclk_freq_hz; + return sysfs_emit(buf, "%llu\n", div_u64(roc64, 4 * rate)); +} + +static ssize_t ad9910_drg_roc_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad9910_state *st = iio_priv(indio_dev); + u64 tmp64; + u32 rate; + int ret; + + ret = kstrtou64(buf, 10, &tmp64); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + switch (chan->channel) { + case AD9910_CHANNEL_DRG_RAMP_UP: + rate = FIELD_GET(AD9910_DRG_RATE_INC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + if (!rate) + return -ERANGE; + + tmp64 = ad9910_rational_scale(tmp64, 4 * rate, st->data.sysclk_freq_hz); + tmp64 = min_t(u64, tmp64, U32_MAX); + + ret = ad9910_reg64_update(st, AD9910_REG_DRG_STEP, + AD9910_DRG_STEP_INC_MSK, + FIELD_PREP(AD9910_DRG_STEP_INC_MSK, tmp64), + true); + if (ret) + return ret; + break; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + rate = FIELD_GET(AD9910_DRG_RATE_DEC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + if (!rate) + return -ERANGE; + + tmp64 = ad9910_rational_scale(tmp64, 4 * rate, st->data.sysclk_freq_hz); + tmp64 = min_t(u64, tmp64, U32_MAX); + + ret = ad9910_reg64_update(st, AD9910_REG_DRG_STEP, + AD9910_DRG_STEP_DEC_MSK, + FIELD_PREP(AD9910_DRG_STEP_DEC_MSK, tmp64), + true); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return len; +} + +static const u32 ad9910_osk_raw_step[] = { + 0, /* no step: manual mode (NOT pin controlled) */ + 1, /* step size 1: automatic mode (pin controlled) */ + 2, /* step size 2: automatic mode (pin controlled) */ + 4, /* step size 4: automatic mode (pin controlled) */ + 8, /* step size 8: automatic mode (pin controlled) */ + GENMASK(13, 0), /* max step: manual mode (pin controlled) */ +}; + +static ssize_t ad9910_osk_attrs_read(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad9910_state *st = iio_priv(indio_dev); + bool auto_en, pinctrl_en; + u32 rate, step; + u64 roc64; + + guard(mutex)(&st->lock); + + rate = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, st->reg[AD9910_REG_ASF].val32); + if (!rate) + return -ERANGE; + + switch (private) { + case AD9910_ROC: + auto_en = FIELD_GET(AD9910_CFR1_SELECT_AUTO_OSK_MSK, + st->reg[AD9910_REG_CFR1].val32); + pinctrl_en = FIELD_GET(AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK, + st->reg[AD9910_REG_CFR1].val32); + if (auto_en) { + step = FIELD_GET(AD9910_ASF_STEP_SIZE_MSK, + st->reg[AD9910_REG_ASF].val32); + step = ad9910_osk_raw_step[step + 1]; + } else if (pinctrl_en) { + step = ad9910_osk_raw_step[ARRAY_SIZE(ad9910_osk_raw_step) - 1]; + } else { + step = ad9910_osk_raw_step[0]; + } + + roc64 = div_u64((u64)step * st->data.sysclk_freq_hz, 4 * rate); + return sysfs_emit(buf, "%llu\n", roc64); + case AD9910_ROC_AVAIL: { + ssize_t len = 0; + + for (unsigned int i = 0; i < ARRAY_SIZE(ad9910_osk_raw_step); i++) { + roc64 = div_u64((u64)ad9910_osk_raw_step[i] * st->data.sysclk_freq_hz, + 4 * rate); + len += sysfs_emit_at(buf, len, "%llu ", roc64); + } + + buf[len - 1] = '\n'; /* replace last space with a newline */ + return len; + } + default: + return -EINVAL; + } +} + +static ssize_t ad9910_osk_attrs_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad9910_state *st = iio_priv(indio_dev); + u32 idx, reg_val, rate; + u64 step; + int ret; + + ret = kstrtou64(buf, 10, &step); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + rate = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, st->reg[AD9910_REG_ASF].val32); + if (!rate) + return -ERANGE; + + switch (private) { + case AD9910_ROC: + step = ad9910_rational_scale(step, 4 * rate, + st->data.sysclk_freq_hz); + step = min(step, AD9910_ASF_MAX); + idx = find_closest(step, ad9910_osk_raw_step, + ARRAY_SIZE(ad9910_osk_raw_step)); + if (idx == ARRAY_SIZE(ad9910_osk_raw_step) - 1) { + /* manual mode: pin-controlled */ + reg_val = AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK; + } else if (idx == 0) { + /* manual mode, NOT pin-controlled */ + reg_val = 0; + } else { + /* auto mode: pin-controlled */ + reg_val = FIELD_PREP(AD9910_ASF_STEP_SIZE_MSK, idx - 1); + ret = ad9910_reg32_update(st, AD9910_REG_ASF, + AD9910_ASF_STEP_SIZE_MSK, + reg_val, false); + if (ret) + return ret; + + reg_val = AD9910_CFR1_SELECT_AUTO_OSK_MSK; + } + + ret = ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_SELECT_AUTO_OSK_MSK | + AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK, + reg_val, true); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return len; +} + +static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = { + { + .name = "powerdown", + .read = ad9910_ext_info_read, + .write = ad9910_ext_info_write, + .private = AD9910_POWERDOWN, + .shared = IIO_SEPARATE, + }, + { } +}; + +static const struct iio_chan_spec_ext_info ad9910_drg_ramp_ext_info[] = { + { + .name = "dwell_en", + .read = ad9910_ext_info_read, + .write = ad9910_ext_info_write, + .private = AD9910_DWELL_EN, + .shared = IIO_SEPARATE, + }, + { + .name = "raw_roc", + .read = ad9910_drg_roc_read, + .write = ad9910_drg_roc_write, + .private = AD9910_ROC, + .shared = IIO_SEPARATE, + }, + { } +}; + +static const struct iio_chan_spec_ext_info ad9910_osk_ext_info[] = { + { + .name = "raw_roc", + .read = ad9910_osk_attrs_read, + .write = ad9910_osk_attrs_write, + .private = AD9910_ROC, + .shared = IIO_SEPARATE, + }, + { + .name = "raw_roc_available", + .read = ad9910_osk_attrs_read, + .private = AD9910_ROC_AVAIL, + .shared = IIO_SEPARATE, + }, + { } +}; + +#define AD9910_PROFILE_CHAN(idx) { \ + .type = IIO_ALTCURRENT, \ + .indexed = 1, \ + .output = 1, \ + .channel = AD9910_CHANNEL_PROFILE_ ## idx, \ + .address = AD9910_CHAN_IDX_PROFILE_ ## idx, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \ + BIT(IIO_CHAN_INFO_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_PHASE) | \ + BIT(IIO_CHAN_INFO_RAW), \ + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], \ +} + +static const struct iio_chan_spec ad9910_channels[] = { + [AD9910_CHAN_IDX_PHY] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_PHY, + .address = AD9910_CHAN_IDX_PHY, + .info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .ext_info = ad9910_phy_ext_info, + }, + [AD9910_CHAN_IDX_PROFILE_0] = AD9910_PROFILE_CHAN(0), + [AD9910_CHAN_IDX_PROFILE_1] = AD9910_PROFILE_CHAN(1), + [AD9910_CHAN_IDX_PROFILE_2] = AD9910_PROFILE_CHAN(2), + [AD9910_CHAN_IDX_PROFILE_3] = AD9910_PROFILE_CHAN(3), + [AD9910_CHAN_IDX_PROFILE_4] = AD9910_PROFILE_CHAN(4), + [AD9910_CHAN_IDX_PROFILE_5] = AD9910_PROFILE_CHAN(5), + [AD9910_CHAN_IDX_PROFILE_6] = AD9910_PROFILE_CHAN(6), + [AD9910_CHAN_IDX_PROFILE_7] = AD9910_PROFILE_CHAN(7), + [AD9910_CHAN_IDX_PARALLEL_AMP] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_PARALLEL, + .address = AD9910_CHAN_IDX_PARALLEL_AMP, + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_PARALLEL_PHASE] = { + .type = IIO_PHASE, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_PARALLEL, + .address = AD9910_CHAN_IDX_PARALLEL_PHASE, + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_PARALLEL_FREQ] = { + .type = IIO_FREQUENCY, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_PARALLEL, + .address = AD9910_CHAN_IDX_PARALLEL_FREQ, + .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_PARALLEL_POLAR_AMP] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_PARALLEL_POLAR, + .address = AD9910_CHAN_IDX_PARALLEL_POLAR_AMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE] = { + .type = IIO_PHASE, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_PARALLEL_POLAR, + .address = AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE, + .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_DRG_FREQ] = { + .type = IIO_FREQUENCY, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG, + .address = AD9910_CHAN_IDX_DRG_FREQ, + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_DRG_PHASE] = { + .type = IIO_PHASE, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG, + .address = AD9910_CHAN_IDX_DRG_PHASE, + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_DRG_AMP] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG, + .address = AD9910_CHAN_IDX_DRG_AMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_SCALE), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP] = { + .type = IIO_FREQUENCY, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG_RAMP_UP, + .address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_drg_ramp_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_FREQ], + }, + [AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN] = { + .type = IIO_FREQUENCY, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG_RAMP_DOWN, + .address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_drg_ramp_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_FREQ], + }, + [AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP] = { + .type = IIO_PHASE, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG_RAMP_UP, + .address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_drg_ramp_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_PHASE], + }, + [AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN] = { + .type = IIO_PHASE, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG_RAMP_DOWN, + .address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_drg_ramp_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_PHASE], + }, + [AD9910_CHAN_IDX_DRG_AMP_RAMP_UP] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG_RAMP_UP, + .address = AD9910_CHAN_IDX_DRG_AMP_RAMP_UP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_drg_ramp_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP], + }, + [AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_DRG_RAMP_DOWN, + .address = AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_drg_ramp_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP], + }, + [AD9910_CHAN_IDX_RAM] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_RAM, + .address = AD9910_CHAN_IDX_RAM, + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_FREQUENCY) | + BIT(IIO_CHAN_INFO_PHASE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_OSK] = { + .type = IIO_ALTCURRENT, + .indexed = 1, + .output = 1, + .channel = AD9910_CHANNEL_OSK, + .address = AD9910_CHAN_IDX_OSK, + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad9910_osk_ext_info, + .parent = &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, +}; + +static int ad9910_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad9910_state *st = iio_priv(indio_dev); + u64 tmp64; + u32 tmp32; + + guard(mutex)(&st->lock); + + switch (info) { + case IIO_CHAN_INFO_ENABLE: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + if (ad9910_sw_powerdown_get(st)) { + *val = 0; + } else { + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + *val = (tmp32 == st->profile); + } + break; + case AD9910_CHANNEL_DRG: + tmp32 = FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (tmp32 == (chan->address - AD9910_CHAN_IDX_DRG_FREQ)) + *val = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, + st->reg[AD9910_REG_CFR2].val32); + else + *val = 0; + break; + case AD9910_CHANNEL_RAM: + *val = FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, + st->reg[AD9910_REG_CFR1].val32); + break; + case AD9910_CHANNEL_OSK: + *val = FIELD_GET(AD9910_CFR1_OSK_ENABLE_MSK, + st->reg[AD9910_REG_CFR1].val32); + break; + default: + return -EINVAL; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_FREQUENCY: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK, + ad9910_st_profile_val(st, tmp32)); + break; + case AD9910_CHANNEL_RAM: + tmp64 = st->reg[AD9910_REG_FTW].val32; + break; + default: + return -EINVAL; + } + tmp64 *= st->data.sysclk_freq_hz; + *val = tmp64 >> 32; + *val2 = ((tmp64 & GENMASK_ULL(31, 0)) * MICRO) >> 32; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_PHASE: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK, + ad9910_st_profile_val(st, tmp32)); + break; + case AD9910_CHANNEL_RAM: + tmp64 = st->reg[AD9910_REG_POW].val16; + break; + default: + return -EINVAL; + } + tmp32 = (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16; + *val = tmp32 / MICRO; + *val2 = tmp32 % MICRO; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_RAW: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + *val = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK, + ad9910_st_profile_val(st, tmp32)); + return IIO_VAL_INT; + case AD9910_CHANNEL_DRG_RAMP_UP: + tmp64 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK, + st->reg[AD9910_REG_DRG_LIMIT].val64); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_INT_64; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + tmp64 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK, + st->reg[AD9910_REG_DRG_LIMIT].val64); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_INT_64; + case AD9910_CHANNEL_OSK: + *val = FIELD_GET(AD9910_ASF_SCALE_FACTOR_MSK, + st->reg[AD9910_REG_ASF].val32); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->channel) { + case AD9910_CHANNEL_PHY: + *val = st->data.sysclk_freq_hz; + return IIO_VAL_INT; + case AD9910_CHANNEL_DRG_RAMP_UP: + tmp32 = FIELD_GET(AD9910_DRG_RATE_INC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + tmp32 = FIELD_GET(AD9910_DRG_RATE_DEC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; + case AD9910_CHANNEL_RAM: + tmp32 = FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK, + ad9910_ram_profile_val(st)); + break; + case AD9910_CHANNEL_OSK: + tmp32 = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, + st->reg[AD9910_REG_ASF].val32); + break; + default: + return -EINVAL; + } + if (!tmp32) + return -ERANGE; + tmp32 *= 4; + *val = st->data.sysclk_freq_hz / tmp32; + *val2 = div_u64((u64)(st->data.sysclk_freq_hz % tmp32) * MICRO, tmp32); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case AD9910_CHAN_IDX_PHY: + tmp64 = (u64)st->data.output_current_uA * + AD9910_NANO_MILLIAMP_PER_MICROAMP; + *val = 0; + *val2 = tmp64 >> 14; + return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHAN_IDX_PARALLEL_PHASE: + *val = 0; + *val2 = AD9910_PI_NANORAD >> 15; + return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHAN_IDX_PARALLEL_FREQ: + tmp32 = FIELD_GET(AD9910_CFR2_FM_GAIN_MSK, + st->reg[AD9910_REG_CFR2].val32); + tmp64 = (u64)st->data.sysclk_freq_hz << tmp32; + tmp64 = ad9910_rational_scale(tmp64, NANO, BIT_ULL(32)); + *val = div_s64_rem(tmp64, NANO, val2); + return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHAN_IDX_PARALLEL_POLAR_AMP: + tmp64 = (u64)st->data.output_current_uA * + AD9910_NANO_MILLIAMP_PER_MICROAMP; + *val = 0; + *val2 = tmp64 >> 8; + return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE: + *val = 0; + *val2 = AD9910_PI_NANORAD >> 7; + return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHAN_IDX_DRG_FREQ: + tmp64 = ad9910_rational_scale(st->data.sysclk_freq_hz, + PICO, BIT_ULL(32)); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_DECIMAL64_PICO; + case AD9910_CHAN_IDX_DRG_PHASE: + tmp64 = DIV_U64_ROUND_CLOSEST(AD9910_PI_PICORAD, BIT(31)); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_DECIMAL64_PICO; + case AD9910_CHAN_IDX_DRG_AMP: + tmp64 = (u64)st->data.output_current_uA * + AD9910_PICO_MILLIAMP_PER_MICROAMP; + tmp64 = DIV_U64_ROUND_CLOSEST(tmp64 >> 1, BIT(31)); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_DECIMAL64_PICO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->address) { + case AD9910_CHAN_IDX_PARALLEL_FREQ: + tmp64 = (u64)st->reg[AD9910_REG_FTW].val32 * MICRO; + tmp64 >>= FIELD_GET(AD9910_CFR2_FM_GAIN_MSK, + st->reg[AD9910_REG_CFR2].val32); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_DECIMAL64_MICRO; + case AD9910_CHAN_IDX_PARALLEL_POLAR_AMP: + tmp32 = FIELD_GET(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, + st->reg[AD9910_REG_ASF].val32); + iio_val_s64_decompose(MICRO * tmp32 >> 6, val, val2); + return IIO_VAL_DECIMAL64_MICRO; + case AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE: + tmp32 = FIELD_GET(AD9910_POW_PP_LSB_MSK, + st->reg[AD9910_REG_POW].val16); + iio_val_s64_decompose(MICRO * tmp32 >> 8, val, val2); + return IIO_VAL_DECIMAL64_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad9910_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad9910_state *st = iio_priv(indio_dev); + u64 tmp64; + u32 tmp32; + int ret, i; + + guard(mutex)(&st->lock); + + switch (info) { + case IIO_CHAN_INFO_ENABLE: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + if (!val) { + if (tmp32 != st->profile) + return 0; /* nothing to do */ + + return ad9910_sw_powerdown_set(st, true); + } + + ret = ad9910_sw_powerdown_set(st, false); + if (ret) + return ret; + + return ad9910_profile_set(st, tmp32); + case AD9910_CHANNEL_DRG: + tmp32 = chan->address - AD9910_CHAN_IDX_DRG_FREQ; + if (val) { + tmp32 = AD9910_CFR2_DRG_ENABLE_MSK | + FIELD_PREP(AD9910_CFR2_DRG_DEST_MSK, tmp32); + } else { + if (tmp32 != FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32)) + return 0; /* nothing to do */ + tmp32 = 0; + } + + return ad9910_reg32_update(st, AD9910_REG_CFR2, + AD9910_CFR2_DRG_DEST_MSK | + AD9910_CFR2_DRG_ENABLE_MSK, + tmp32, true); + case AD9910_CHANNEL_RAM: + if (AD9910_RAM_ENABLED(st) == !!val) + return 0; + + /* swap profile configs */ + for (i = 0; i < AD9910_NUM_PROFILES; i++) { + tmp64 = st->reg[AD9910_REG_PROFILE(i)].val64; + ret = ad9910_reg64_write(st, + AD9910_REG_PROFILE(i), + st->reg_profile[i], + false); + if (ret) + break; + st->reg_profile[i] = tmp64; + } + + if (ret) { + /* + * After the write failure, profiles 0..i-1 were + * already swapped in SW, but Hw registers are + * still pending an IO update, so swap them back + * in SW to keep the state consistent. + */ + while (i--) { + tmp64 = st->reg[AD9910_REG_PROFILE(i)].val64; + st->reg[AD9910_REG_PROFILE(i)].val64 = st->reg_profile[i]; + st->reg_profile[i] = tmp64; + } + return ret; + } + + tmp32 = FIELD_PREP(AD9910_CFR1_RAM_ENABLE_MSK, !!val); + return ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_RAM_ENABLE_MSK, + tmp32, true); + case AD9910_CHANNEL_OSK: + tmp32 = FIELD_PREP(AD9910_CFR1_OSK_ENABLE_MSK, !!val); + return ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_OSK_ENABLE_MSK, + tmp32, true); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_FREQUENCY: + if (val < 0 || val2 < 0 || val >= st->data.sysclk_freq_hz / 2) + return -EINVAL; + + tmp64 = ad9910_rational_scale((u64)val * MICRO + val2, BIT_ULL(32), + (u64)MICRO * st->data.sysclk_freq_hz); + tmp64 = min_t(u64, tmp64, U32_MAX); + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + if (AD9910_RAM_ENABLED(st)) { + FIELD_MODIFY(AD9910_PROFILE_ST_FTW_MSK, + &st->reg_profile[tmp32], tmp64); + return 0; + } + tmp64 = FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp64); + return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), + AD9910_PROFILE_ST_FTW_MSK, + tmp64, true); + case AD9910_CHANNEL_RAM: + return ad9910_reg32_write(st, AD9910_REG_FTW, tmp64, true); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_PHASE: + if (val < 0 || val2 < 0) + return -EINVAL; + + tmp64 = (u64)val * MICRO + val2; + if (tmp64 >= AD9910_MAX_PHASE_MICRORAD) + return -EINVAL; + + tmp64 = DIV_U64_ROUND_CLOSEST(tmp64 << 16, AD9910_MAX_PHASE_MICRORAD); + tmp64 = min(tmp64, AD9910_POW_MAX); + + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + + if (AD9910_RAM_ENABLED(st)) { + FIELD_MODIFY(AD9910_PROFILE_ST_POW_MSK, + &st->reg_profile[tmp32], tmp64); + return 0; + } + + tmp64 = FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp64); + return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), + AD9910_PROFILE_ST_POW_MSK, + tmp64, true); + case AD9910_CHANNEL_RAM: + return ad9910_reg16_write(st, AD9910_REG_POW, tmp64, true); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + if (val < 0) + return -EINVAL; + + tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 = min_t(u64, val, AD9910_ASF_MAX); + + if (AD9910_RAM_ENABLED(st)) { + FIELD_MODIFY(AD9910_PROFILE_ST_ASF_MSK, + &st->reg_profile[tmp32], tmp64); + return 0; + } + + tmp64 = FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, tmp64); + return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), + AD9910_PROFILE_ST_ASF_MSK, + tmp64, true); + case AD9910_CHANNEL_DRG_RAMP_UP: { + s64 val64 = iio_val_s64_compose(val, val2); + + if (val64 < 0) + return -EINVAL; + + tmp64 = min_t(u64, val64, U32_MAX); + tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_UPPER_MSK, tmp64); + return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT, + AD9910_DRG_LIMIT_UPPER_MSK, + tmp64, true); + } + case AD9910_CHANNEL_DRG_RAMP_DOWN: { + s64 val64 = iio_val_s64_compose(val, val2); + + if (val64 < 0) + return -EINVAL; + + tmp64 = min_t(u64, val64, U32_MAX); + tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_LOWER_MSK, tmp64); + return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT, + AD9910_DRG_LIMIT_LOWER_MSK, + tmp64, true); + } + case AD9910_CHANNEL_OSK: + if (val < 0) + return -EINVAL; + + tmp32 = min_t(u32, val, AD9910_ASF_MAX); + tmp32 = FIELD_PREP(AD9910_ASF_SCALE_FACTOR_MSK, tmp32); + return ad9910_reg32_update(st, AD9910_REG_ASF, + AD9910_ASF_SCALE_FACTOR_MSK, + tmp32, true); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->channel == AD9910_CHANNEL_PHY) + return ad9910_set_sysclk_freq(st, val, true); + + if (val < 0 || val2 < 0 || val > st->data.sysclk_freq_hz / 4) + return -EINVAL; + + tmp64 = ((u64)val * MICRO + val2) * 4; + if (!tmp64) + return -EINVAL; + + tmp64 = DIV64_U64_ROUND_CLOSEST((u64)st->data.sysclk_freq_hz * MICRO, tmp64); + tmp32 = clamp(tmp64, 1U, AD9910_STEP_RATE_MAX); + + switch (chan->channel) { + case AD9910_CHANNEL_DRG_RAMP_UP: + tmp32 = FIELD_PREP(AD9910_DRG_RATE_INC_MSK, tmp32); + return ad9910_reg32_update(st, AD9910_REG_DRG_RATE, + AD9910_DRG_RATE_INC_MSK, + tmp32, true); + case AD9910_CHANNEL_DRG_RAMP_DOWN: + tmp32 = FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, tmp32); + return ad9910_reg32_update(st, AD9910_REG_DRG_RATE, + AD9910_DRG_RATE_DEC_MSK, + tmp32, true); + case AD9910_CHANNEL_RAM: + if (!AD9910_RAM_ENABLED(st)) { + FIELD_MODIFY(AD9910_PROFILE_RAM_STEP_RATE_MSK, + &st->reg_profile[st->profile], tmp32); + return 0; + } + + tmp64 = FIELD_PREP(AD9910_PROFILE_RAM_STEP_RATE_MSK, tmp32); + return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile), + AD9910_PROFILE_RAM_STEP_RATE_MSK, + tmp64, true); + case AD9910_CHANNEL_OSK: + return ad9910_reg32_update(st, AD9910_REG_ASF, + AD9910_ASF_RAMP_RATE_MSK, + FIELD_PREP(AD9910_ASF_RAMP_RATE_MSK, tmp32), + true); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case AD9910_CHAN_IDX_PHY: + if (val != 0 || val2 < 0) + return -EINVAL; + + tmp32 = DIV_U64_ROUND_CLOSEST((u64)val2 << 14, + AD9910_NANO_MILLIAMP_PER_MICROAMP); + return ad9910_set_dac_current(st, tmp32, true); + case AD9910_CHAN_IDX_PARALLEL_FREQ: + if (val < 0 || val2 < 0) + return -EINVAL; + + tmp64 = ad9910_rational_scale((u64)val * NANO + val2, BIT_ULL(32), + (u64)st->data.sysclk_freq_hz * NANO); + tmp64 = roundup_pow_of_two(clamp(tmp64, 1ULL, BIT_ULL(15))); + tmp32 = FIELD_PREP(AD9910_CFR2_FM_GAIN_MSK, ilog2(tmp64)); + return ad9910_reg32_update(st, AD9910_REG_CFR2, + AD9910_CFR2_FM_GAIN_MSK, + tmp32, true); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: { + s64 val64 = iio_val_s64_compose(val, val2); + + if (val64 < 0) + return -EINVAL; + + tmp64 = val64; + switch (chan->address) { + case AD9910_CHAN_IDX_PARALLEL_FREQ: + tmp32 = BIT(FIELD_GET(AD9910_CFR2_FM_GAIN_MSK, + st->reg[AD9910_REG_CFR2].val32)); + tmp64 = ad9910_rational_scale(tmp64, tmp32, MICRO); + tmp64 = min_t(u64, tmp64, U32_MAX); + return ad9910_reg32_write(st, AD9910_REG_FTW, tmp64, true); + case AD9910_CHAN_IDX_PARALLEL_POLAR_AMP: + tmp64 = ad9910_rational_scale(tmp64, BIT(6), MICRO); + tmp64 = min_t(u64, tmp64, AD9910_ASF_PP_LSB_MAX); + tmp32 = FIELD_PREP(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, tmp64); + return ad9910_reg32_update(st, AD9910_REG_ASF, + AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, + tmp32, true); + case AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE: + tmp64 = ad9910_rational_scale(tmp64, BIT(8), MICRO); + tmp64 = min_t(u64, tmp64, AD9910_POW_PP_LSB_MAX); + tmp32 = FIELD_PREP(AD9910_POW_PP_LSB_MSK, tmp64); + return ad9910_reg16_update(st, AD9910_REG_POW, + AD9910_POW_PP_LSB_MSK, + tmp32, true); + default: + return -EINVAL; + } + } + default: + return -EINVAL; + } +} + +static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_ENABLE: + return IIO_VAL_INT; + case IIO_CHAN_INFO_FREQUENCY: + case IIO_CHAN_INFO_PHASE: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_RAW: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + case AD9910_CHANNEL_OSK: + return IIO_VAL_INT; + case AD9910_CHANNEL_DRG_RAMP_UP: + case AD9910_CHANNEL_DRG_RAMP_DOWN: + return IIO_VAL_INT_64; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->channel == AD9910_CHANNEL_PHY) + return IIO_VAL_INT; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + return IIO_VAL_DECIMAL64_MICRO; + default: + return -EINVAL; + } +} + +static int ad9910_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, u64 writeval, + u64 *readval) +{ + struct ad9910_state *st = iio_priv(indio_dev); + union ad9910_reg tmp; + int ret; + + if (reg >= AD9910_REG_RAM) + return -EINVAL; + + guard(mutex)(&st->lock); + + switch (reg) { + case AD9910_REG_DRG_LIMIT: + case AD9910_REG_DRG_STEP: + case AD9910_REG_PROFILE0 ... AD9910_REG_PROFILE7: + if (!readval) + return ad9910_reg64_write(st, reg, writeval, true); + + ret = ad9910_reg64_read(st, reg, &tmp.val64); + if (ret) + return ret; + *readval = tmp.val64; + return 0; + case AD9910_REG_POW: + if (!readval) + return ad9910_reg16_write(st, reg, writeval, true); + + ret = ad9910_reg16_read(st, reg, &tmp.val16); + if (ret) + return ret; + *readval = tmp.val16; + return 0; + default: + if (!readval) + return ad9910_reg32_write(st, reg, writeval, true); + + ret = ad9910_reg32_read(st, reg, &tmp.val32); + if (ret) + return ret; + *readval = tmp.val32; + return 0; + } +} + +static const char * const ad9910_channel_str[] = { + [AD9910_CHAN_IDX_PHY] = "phy", + [AD9910_CHAN_IDX_PROFILE_0] = "profile0", + [AD9910_CHAN_IDX_PROFILE_1] = "profile1", + [AD9910_CHAN_IDX_PROFILE_2] = "profile2", + [AD9910_CHAN_IDX_PROFILE_3] = "profile3", + [AD9910_CHAN_IDX_PROFILE_4] = "profile4", + [AD9910_CHAN_IDX_PROFILE_5] = "profile5", + [AD9910_CHAN_IDX_PROFILE_6] = "profile6", + [AD9910_CHAN_IDX_PROFILE_7] = "profile7", + [AD9910_CHAN_IDX_PARALLEL_AMP] = "parallel_amplitude", + [AD9910_CHAN_IDX_PARALLEL_PHASE] = "parallel_phase", + [AD9910_CHAN_IDX_PARALLEL_FREQ] = "parallel_frequency", + [AD9910_CHAN_IDX_PARALLEL_POLAR_AMP] = "parallel_polar_amplitude", + [AD9910_CHAN_IDX_PARALLEL_POLAR_PHASE] = "parallel_polar_phase", + [AD9910_CHAN_IDX_DRG_FREQ] = "drg_frequency", + [AD9910_CHAN_IDX_DRG_PHASE] = "drg_phase", + [AD9910_CHAN_IDX_DRG_AMP] = "drg_amplitude", + [AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP] = "drg_rising_frequency", + [AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN] = "drg_falling_frequency", + [AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP] = "drg_rising_phase", + [AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN] = "drg_falling_phase", + [AD9910_CHAN_IDX_DRG_AMP_RAMP_UP] = "drg_rising_amplitude", + [AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN] = "drg_falling_amplitude", + [AD9910_CHAN_IDX_RAM] = "ram", + [AD9910_CHAN_IDX_OSK] = "osk", +}; + +static int ad9910_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + char *label) +{ + return sysfs_emit(label, "%s\n", ad9910_channel_str[chan->address]); +} + +static enum fw_upload_err ad9910_ram_fwu_prepare(struct fw_upload *fw_upload, + const u8 *data, u32 size) +{ + struct ad9910_state *st = fw_upload->dd_handle; + const struct ad9910_ram_fw *fw_data = (const struct ad9910_ram_fw *)data; + size_t wcount, bcount; + + scoped_guard(mutex, &st->lock) { + /* cancel requests before prepare() are ignored */ + st->ram_fwu_cancel = false; + } + + if (size < sizeof(struct ad9910_ram_fw)) + return FW_UPLOAD_ERR_INVALID_SIZE; + + if (get_unaligned_be32(&fw_data->magic) != AD9910_RAM_FW_MAGIC) + return FW_UPLOAD_ERR_FW_INVALID; + + if (get_unaligned_be16(&fw_data->version) != AD9910_RAM_FW_V1) + return FW_UPLOAD_ERR_FW_INVALID; + + wcount = get_unaligned_be16(&fw_data->wcount); + bcount = size - sizeof(struct ad9910_ram_fw); + if (wcount > AD9910_RAM_SIZE_MAX_WORDS || + bcount != (wcount * AD9910_RAM_WORD_SIZE)) + return FW_UPLOAD_ERR_INVALID_SIZE; + + bcount += sizeof(fw_data->cfr1) + sizeof(fw_data->profiles); + if (crc32(0, &fw_data->cfr1, bcount) != get_unaligned_be32(&fw_data->crc)) + return FW_UPLOAD_ERR_FW_INVALID; + + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err ad9910_ram_fwu_write(struct fw_upload *fw_upload, + const u8 *data, u32 offset, + u32 size, u32 *written) +{ + const struct ad9910_ram_fw *fw_data = (const struct ad9910_ram_fw *)data; + struct ad9910_state *st = fw_upload->dd_handle; + int ret, ret2, idx, wcount; + u64 tmp64, backup; + + if (offset != 0) + return FW_UPLOAD_ERR_INVALID_SIZE; + + guard(mutex)(&st->lock); + + if (st->ram_fwu_cancel) + return FW_UPLOAD_ERR_CANCELED; + + if (AD9910_RAM_ENABLED(st)) + return FW_UPLOAD_ERR_HW_ERROR; + + for (idx = 0; idx < AD9910_NUM_PROFILES; idx++) + st->reg_profile[idx] = get_unaligned_be64(&fw_data->profiles[idx]) | + AD9910_PROFILE_RAM_OPEN_MSK; + + ret = ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_RAM_PLAYBACK_DEST_MSK | + AD9910_CFR1_INT_PROFILE_CTL_MSK, + get_unaligned_be32(&fw_data->cfr1), true); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + wcount = get_unaligned_be16(&fw_data->wcount); + if (!wcount) { + *written = size; + return FW_UPLOAD_ERR_NONE; /* nothing else to write */ + } + + ret = ad9910_profile_set(st, st->profile); + if (ret) + return FW_UPLOAD_ERR_HW_ERROR; + + /* backup profile register and update it with required address range */ + backup = st->reg[AD9910_REG_PROFILE(st->profile)].val64; + tmp64 = AD9910_PROFILE_RAM_STEP_RATE_MSK | + FIELD_PREP(AD9910_PROFILE_RAM_START_ADDR_MSK, 0) | + FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK, wcount - 1); + ret = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), tmp64, true); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + /* copy data words into spi buffer and transfer */ + memcpy(&st->tx_buf[1], fw_data->words, wcount * AD9910_RAM_WORD_SIZE); + ret = ad9910_spi_write(st, AD9910_REG_RAM, + wcount * AD9910_RAM_WORD_SIZE, false); + + /* restore active single tone profile regardless */ + st->reg[AD9910_REG_PROFILE(st->profile)].val64 = backup; + ret2 = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), backup, true); + if (ret || ret2) + return FW_UPLOAD_ERR_RW_ERROR; + + *written = size; + + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err ad9910_ram_fwu_poll_complete(struct fw_upload *fw_upload) +{ + return FW_UPLOAD_ERR_NONE; +} + +static void ad9910_ram_fwu_cancel(struct fw_upload *fw_upload) +{ + struct ad9910_state *st = fw_upload->dd_handle; + + guard(mutex)(&st->lock); + st->ram_fwu_cancel = true; +} + +static void ad9910_ram_fwu_unregister(void *data) +{ + firmware_upload_unregister(data); +} + +static const struct fw_upload_ops ad9910_ram_fwu_ops = { + .prepare = ad9910_ram_fwu_prepare, + .write = ad9910_ram_fwu_write, + .poll_complete = ad9910_ram_fwu_poll_complete, + .cancel = ad9910_ram_fwu_cancel, +}; + +static const struct iio_info ad9910_info = { + .read_raw = ad9910_read_raw, + .write_raw = ad9910_write_raw, + .write_raw_get_fmt = ad9910_write_raw_get_fmt, + .read_label = ad9910_read_label, + .debugfs_reg64_access = &ad9910_debugfs_reg_access, +}; + +static int ad9910_cfg_sysclk(struct ad9910_state *st, bool update) +{ + u32 cfr3 = AD9910_CFR3_OPEN_MSK; + u32 tmp32; + + cfr3 |= FIELD_PREP(AD9910_CFR3_DRV0_MSK, st->data.refclk_out_drv); + + if (st->data.pll_enabled) { + tmp32 = st->data.pll_charge_pump_current - AD9910_ICP_MIN_uA; + tmp32 = DIV_ROUND_CLOSEST(tmp32, AD9910_ICP_STEP_uA); + cfr3 |= FIELD_PREP(AD9910_CFR3_ICP_MSK, tmp32) | + AD9910_CFR3_PLL_EN_MSK; + } else { + cfr3 |= AD9910_CFR3_ICP_MSK | + AD9910_CFR3_REFCLK_DIV_RESETB_MSK | + AD9910_CFR3_PFD_RESET_MSK; + } + st->reg[AD9910_REG_CFR3].val32 = cfr3; + + return ad9910_set_sysclk_freq(st, AD9910_MAX_SYSCLK_HZ, update); +} + +static int ad9910_parse_fw(struct ad9910_state *st) +{ + static const char * const refclk_out_drv0[] = { + "disabled", "low", "medium", "high", + }; + struct device *dev = &st->spi->dev; + const char *prop; + u32 tmp; + int ret; + + st->data.pll_enabled = device_property_read_bool(dev, "adi,pll-enable"); + if (st->data.pll_enabled) { + prop = "adi,charge-pump-current-microamp"; + if (device_property_present(dev, prop)) { + ret = device_property_read_u32(dev, prop, &tmp); + if (ret) + return dev_err_probe(dev, ret, "property read: %s\n", prop); + + if (tmp < AD9910_ICP_MIN_uA || tmp > AD9910_ICP_MAX_uA) + return dev_err_probe(dev, -ERANGE, + "invalid charge pump current %u\n", tmp); + } else { + tmp = AD9910_ICP_MIN_uA; + } + st->data.pll_charge_pump_current = tmp; + + prop = "adi,refclk-out-drive-strength"; + if (device_property_present(dev, prop)) { + ret = device_property_match_property_string(dev, prop, + refclk_out_drv0, + ARRAY_SIZE(refclk_out_drv0)); + if (ret < 0) + return dev_err_probe(dev, ret, "property read: %s\n", prop); + + st->data.refclk_out_drv = ret; + } + } + + return 0; +} + +static void ad9910_sw_powerdown_action(void *data) +{ + ad9910_sw_powerdown_set(data, true); +} + +static void ad9910_hw_powerdown_action(void *data) +{ + struct ad9910_state *st = data; + + gpiod_set_value_cansleep(st->gpio_pwdown, 1); +} + +static int ad9910_setup(struct device *dev, struct ad9910_state *st, + struct reset_control *dev_rst) +{ + int ret; + + ret = reset_control_assert(dev_rst); + if (ret) + return ret; + + fsleep(AD9910_RESET_DELAY_us); + + ret = reset_control_deassert(dev_rst); + if (ret) + return ret; + + ret = ad9910_reg32_write(st, AD9910_REG_CFR1, + (st->spi->mode & SPI_3WIRE ? 0 : + AD9910_CFR1_SDIO_INPUT_ONLY_MSK), false); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, ad9910_sw_powerdown_action, st); + if (ret) + return ret; + + ret = ad9910_reg32_write(st, AD9910_REG_CFR2, + AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK | + AD9910_CFR2_SYNC_TIMING_VAL_DISABLE_MSK | + AD9910_CFR2_DRG_NO_DWELL_MSK | + AD9910_CFR2_DATA_ASM_HOLD_LAST_MSK | + AD9910_CFR2_SYNC_CLK_EN_MSK | + AD9910_CFR2_PDCLK_ENABLE_MSK, false); + if (ret) + return ret; + + ret = ad9910_cfg_sysclk(st, false); + if (ret) + return ret; + + ret = ad9910_set_dac_current(st, AD9910_DAC_IOUT_DEFAULT_uA, false); + if (ret) + return ret; + + /* configure step rate with default values */ + ret = ad9910_reg32_write(st, AD9910_REG_ASF, + FIELD_PREP(AD9910_ASF_RAMP_RATE_MSK, 1), + false); + if (ret) + return ret; + + ret = ad9910_reg32_write(st, AD9910_REG_DRG_RATE, + FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, 1) | + FIELD_PREP(AD9910_DRG_RATE_INC_MSK, 1), + false); + if (ret) + return ret; + + for (unsigned int i = 0; i < AD9910_NUM_PROFILES; i++) { + st->reg_profile[i] = AD9910_PROFILE_RAM_OPEN_MSK; + st->reg_profile[i] |= FIELD_PREP(AD9910_PROFILE_RAM_STEP_RATE_MSK, 1); + st->reg_profile[i] |= FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK, + AD9910_RAM_ADDR_MAX); + } + + return ad9910_io_update(st); +} + +static inline const char *ad9910_frequency_source_get(struct ad9910_state *st) +{ + bool ram_en, mode_en; + + guard(mutex)(&st->lock); + + /* RAM enabled and data destination is frequency */ + ram_en = AD9910_RAM_ENABLED(st); + if (ram_en && AD9910_DEST_FREQUENCY == + FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK, + st->reg[AD9910_REG_CFR1].val32)) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + + /* DRG enabled and data destination is frequency */ + mode_en = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (mode_en && AD9910_DEST_FREQUENCY == + FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32)) + return ad9910_channel_str[AD9910_CHAN_IDX_DRG_FREQ]; + + /* FTW: RAM enabled and data destination is phase, amplitude, or polar */ + if (ram_en) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + + /* single tone profiles */ + return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile]; +} + +static int ad9910_frequency_source_show(struct seq_file *s, void *ignored) +{ + seq_printf(s, "%s\n", ad9910_frequency_source_get(s->private)); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ad9910_frequency_source); + +static inline const char *ad9910_phase_source_get(struct ad9910_state *st) +{ + bool ram_en, mode_en; + u32 destination; + + guard(mutex)(&st->lock); + + /* RAM enabled and data destination is phase or polar */ + ram_en = AD9910_RAM_ENABLED(st); + if (ram_en) { + destination = FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK, + st->reg[AD9910_REG_CFR1].val32); + if (destination == AD9910_DEST_PHASE || + destination == AD9910_DEST_POLAR) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + } + + /* DRG enabled and data destination is phase */ + mode_en = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (mode_en && AD9910_DEST_PHASE == + FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32)) + return ad9910_channel_str[AD9910_CHAN_IDX_DRG_PHASE]; + + /* POW: RAM enabled and data destination is frequency or amplitude */ + if (ram_en) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + + /* single tone profiles */ + return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile]; +} + +static int ad9910_phase_source_show(struct seq_file *s, void *ignored) +{ + seq_printf(s, "%s\n", ad9910_phase_source_get(s->private)); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ad9910_phase_source); + +static inline const char *ad9910_amplitude_source_get(struct ad9910_state *st) +{ + bool ram_en, mode_en; + u32 destination; + + guard(mutex)(&st->lock); + + /* OSK enabled */ + mode_en = FIELD_GET(AD9910_CFR1_OSK_ENABLE_MSK, + st->reg[AD9910_REG_CFR1].val32); + if (mode_en) + return ad9910_channel_str[AD9910_CHAN_IDX_OSK]; + + /* RAM enabled and data destination is amplitude or polar */ + ram_en = AD9910_RAM_ENABLED(st); + if (ram_en) { + destination = FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK, + st->reg[AD9910_REG_CFR1].val32); + if (destination == AD9910_DEST_AMPLITUDE || + destination == AD9910_DEST_POLAR) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + } + + /* DRG enabled and data destination is amplitude */ + mode_en = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (mode_en && AD9910_DEST_AMPLITUDE == + FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32)) + return ad9910_channel_str[AD9910_CHAN_IDX_DRG_AMP]; + + /* only way to control amplitude at this point is through OSK */ + if (ram_en) + return ad9910_channel_str[AD9910_CHAN_IDX_OSK]; + + /* single tone profiles */ + return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile]; +} + +static int ad9910_amplitude_source_show(struct seq_file *s, void *ignored) +{ + seq_printf(s, "%s\n", ad9910_amplitude_source_get(s->private)); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ad9910_amplitude_source); + +static inline void ad9910_debugfs_init(struct ad9910_state *st, + struct iio_dev *indio_dev) +{ + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + + debugfs_create_file("frequency_source", 0400, d, st, + &ad9910_frequency_source_fops); + debugfs_create_file("phase_source", 0400, d, st, + &ad9910_phase_source_fops); + debugfs_create_file("amplitude_source", 0400, d, st, + &ad9910_amplitude_source_fops); +} + +static int ad9910_probe(struct spi_device *spi) +{ + static const char * const supplies[] = { + "dvdd-io33", "avdd33", "dvdd18", "avdd18", + }; + struct device *dev = &spi->dev; + struct reset_control *dev_rst; + struct gpio_desc *io_rst_gpio; + struct iio_dev *indio_dev; + struct ad9910_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + indio_dev->name = "ad9910"; + indio_dev->info = &ad9910_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad9910_channels; + indio_dev->num_channels = ARRAY_SIZE(ad9910_channels); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(supplies), supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + st->refclk = devm_clk_get_enabled(dev, "ref_clk"); + if (IS_ERR(st->refclk)) + return dev_err_probe(dev, PTR_ERR(st->refclk), + "Failed to get reference clock\n"); + + dev_rst = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(dev_rst)) + return dev_err_probe(dev, PTR_ERR(dev_rst), + "failed to get device reset control\n"); + + /* + * The IO RESET pin is not used in this driver, as we assume that all + * SPI transfers are complete, but if it is wired up, we need to make + * sure it is not floating. We can use either a reset controller or a + * GPIO for this. + */ + io_rst_gpio = devm_gpiod_get_optional(dev, "io-reset", GPIOD_OUT_LOW); + if (IS_ERR(io_rst_gpio)) + return dev_err_probe(dev, PTR_ERR(io_rst_gpio), + "failed to get io reset gpio\n"); + + st->gpio_update = devm_gpiod_get_optional(dev, "update", GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_update)) + return dev_err_probe(dev, PTR_ERR(st->gpio_update), + "failed to get update gpio\n"); + + st->gpio_profile = devm_gpiod_get_array_optional(dev, "profile", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_profile)) + return dev_err_probe(dev, PTR_ERR(st->gpio_profile), + "failed to get profile gpios\n"); + + if (st->gpio_profile && st->gpio_profile->ndescs != 3) + return dev_err_probe(dev, -EINVAL, + "invalid number of profile gpios\n"); + + st->gpio_pwdown = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_pwdown)) + return dev_err_probe(dev, PTR_ERR(st->gpio_pwdown), + "failed to get powerdown gpio\n"); + + ret = devm_add_action_or_reset(dev, ad9910_hw_powerdown_action, st); + if (ret) + return dev_err_probe(dev, ret, + "failed to add hw powerdown action\n"); + + fsleep(AD9910_WAKEUP_DELAY_us); + + ret = ad9910_parse_fw(st); + if (ret) + return ret; + + ret = ad9910_setup(dev, st, dev_rst); + if (ret) + return dev_err_probe(dev, ret, "device setup failed\n"); + + snprintf(st->ram_fwu_name, sizeof(st->ram_fwu_name), "%s:ram", + dev_name(&indio_dev->dev)); + st->ram_fwu = firmware_upload_register(THIS_MODULE, dev, st->ram_fwu_name, + &ad9910_ram_fwu_ops, st); + if (IS_ERR(st->ram_fwu)) + return dev_err_probe(dev, PTR_ERR(st->ram_fwu), + "failed to register ram upload ops\n"); + + ret = devm_add_action_or_reset(dev, ad9910_ram_fwu_unregister, st->ram_fwu); + if (ret) + return dev_err_probe(dev, ret, + "failed to add ram upload unregister action\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + ad9910_debugfs_init(st, indio_dev); + + return 0; +} + +static const struct spi_device_id ad9910_id[] = { + { .name = "ad9910" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad9910_id); + +static const struct of_device_id ad9910_of_match[] = { + { .compatible = "adi,ad9910" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad9910_of_match); + +static struct spi_driver ad9910_driver = { + .driver = { + .name = "ad9910", + .of_match_table = ad9910_of_match, + }, + .probe = ad9910_probe, + .id_table = ad9910_id, +}; +module_spi_driver(ad9910_driver); + +MODULE_AUTHOR("Rodrigo Alencar <rodrigo.alencar@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD9910 DDS driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/frequency/adf4350.c b/drivers/iio/frequency/adf4350.c index 315317d6eec4..ec45c0f65fe3 100644 --- a/drivers/iio/frequency/adf4350.c +++ b/drivers/iio/frequency/adf4350.c @@ -461,7 +461,7 @@ static const struct clk_ops adf4350_clk_ops = { static int adf4350_clk_register(struct adf4350_state *st) { struct spi_device *spi = st->spi; - struct clk_init_data init; + struct clk_init_data init = { }; struct clk *clk; const char *parent_name; int ret; diff --git a/drivers/iio/frequency/adf4377.c b/drivers/iio/frequency/adf4377.c index 7385be62fa6a..52266f83877a 100644 --- a/drivers/iio/frequency/adf4377.c +++ b/drivers/iio/frequency/adf4377.c @@ -1060,6 +1060,9 @@ static int adf4377_probe(struct spi_device *spi) st->regmap = regmap; st->spi = spi; st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODATA; + mutex_init(&st->lock); ret = adf4377_properties_parse(st); diff --git a/drivers/iio/gyro/adxrs290.c b/drivers/iio/gyro/adxrs290.c index 563de2724396..b61cc81122a6 100644 --- a/drivers/iio/gyro/adxrs290.c +++ b/drivers/iio/gyro/adxrs290.c @@ -502,8 +502,8 @@ static irqreturn_t adxrs290_trigger_handler(int irq, void *p) if (ret < 0) break; - iio_push_to_buffers_with_timestamp(indio_dev, &st->buffer, - pf->timestamp); + iio_push_to_buffers_with_ts(indio_dev, &st->buffer, + sizeof(st->buffer), pf->timestamp); } while (0); iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/gyro/bmg160_core.c b/drivers/iio/gyro/bmg160_core.c index d611341a0e2a..b007f46d3fd6 100644 --- a/drivers/iio/gyro/bmg160_core.c +++ b/drivers/iio/gyro/bmg160_core.c @@ -897,8 +897,8 @@ static irqreturn_t bmg160_trigger_handler(int irq, void *p) if (ret < 0) goto err; - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, - pf->timestamp); + iio_push_to_buffers_with_ts(indio_dev, &data->scan, + sizeof(data->scan), pf->timestamp); err: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/gyro/itg3200_buffer.c b/drivers/iio/gyro/itg3200_buffer.c index 87efa2c74ca4..426d04d496b4 100644 --- a/drivers/iio/gyro/itg3200_buffer.c +++ b/drivers/iio/gyro/itg3200_buffer.c @@ -59,7 +59,8 @@ static irqreturn_t itg3200_trigger_handler(int irq, void *p) if (ret < 0) goto error_ret; - iio_push_to_buffers_with_timestamp(indio_dev, &scan, pf->timestamp); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + pf->timestamp); error_ret: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig index 54f11f000b6f..13e267943f67 100644 --- a/drivers/iio/humidity/Kconfig +++ b/drivers/iio/humidity/Kconfig @@ -5,13 +5,15 @@ menu "Humidity sensors" config AM2315 - tristate "Aosong AM2315 relative humidity and temperature sensor" + tristate "Aosong AM2315 and similar relative humidity and temperature sensor" depends on I2C select IIO_BUFFER select IIO_TRIGGERED_BUFFER help - If you say yes here you get support for the Aosong AM2315 - relative humidity and ambient temperature sensor. + If you say yes here you get support for the Aosong relative + humidity and ambient temperature sensors: + - AM2315 + - AM2320 This driver can also be built as a module. If so, the module will be called am2315. diff --git a/drivers/iio/humidity/am2315.c b/drivers/iio/humidity/am2315.c index f29baa251f9f..5c7582f3a4f9 100644 --- a/drivers/iio/humidity/am2315.c +++ b/drivers/iio/humidity/am2315.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Aosong AM2315 relative humidity and temperature + * Aosong AM2315 and similar relative humidity and temperature * * Copyright (c) 2016, Intel Corporation. * @@ -27,7 +27,17 @@ #define AM2315_TEMP_OFFSET 4 #define AM2315_ALL_CHANNEL_MASK GENMASK(1, 0) -#define AM2315_DRIVER_NAME "am2315" +struct am2315_chip_info { + const char *name; +}; + +static const struct am2315_chip_info am2315_chip_info = { + .name = "am2315", +}; + +static const struct am2315_chip_info am2320_chip_info = { + .name = "am2320", +}; struct am2315_data { struct i2c_client *client; @@ -220,9 +230,14 @@ static const struct iio_info am2315_info = { static int am2315_probe(struct i2c_client *client) { int ret; + const struct am2315_chip_info *chip_info; struct iio_dev *indio_dev; struct am2315_data *data; + chip_info = i2c_get_match_data(client); + if (!chip_info) + return -ENODATA; + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; @@ -233,7 +248,7 @@ static int am2315_probe(struct i2c_client *client) mutex_init(&data->lock); indio_dev->info = &am2315_info; - indio_dev->name = AM2315_DRIVER_NAME; + indio_dev->name = chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = am2315_channels; indio_dev->num_channels = ARRAY_SIZE(am2315_channels); @@ -249,8 +264,16 @@ static int am2315_probe(struct i2c_client *client) return devm_iio_device_register(&client->dev, indio_dev); } +static const struct of_device_id am2315_of_match[] = { + { .compatible = "aosong,am2315", .data = &am2315_chip_info }, + { .compatible = "aosong,am2320", .data = &am2320_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, am2315_of_match); + static const struct i2c_device_id am2315_i2c_id[] = { - { .name = "am2315" }, + { .name = "am2315", .driver_data = (kernel_ulong_t)&am2315_chip_info }, + { .name = "am2320", .driver_data = (kernel_ulong_t)&am2320_chip_info }, { } }; MODULE_DEVICE_TABLE(i2c, am2315_i2c_id); @@ -258,6 +281,7 @@ MODULE_DEVICE_TABLE(i2c, am2315_i2c_id); static struct i2c_driver am2315_driver = { .driver = { .name = "am2315", + .of_match_table = am2315_of_match, }, .probe = am2315_probe, .id_table = am2315_i2c_id, @@ -266,5 +290,5 @@ static struct i2c_driver am2315_driver = { module_i2c_driver(am2315_driver); MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>"); -MODULE_DESCRIPTION("Aosong AM2315 relative humidity and temperature"); +MODULE_DESCRIPTION("Aosong AM2315 and similar relative humidity and temperature"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/humidity/ens210.c b/drivers/iio/humidity/ens210.c index 81276195152b..4bdd37e7227b 100644 --- a/drivers/iio/humidity/ens210.c +++ b/drivers/iio/humidity/ens210.c @@ -202,8 +202,8 @@ static int ens210_probe(struct i2c_client *client) int ret; if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_WRITE_BYTE_DATA | - I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_READ_WORD_DATA | I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { return dev_err_probe(&client->dev, -EOPNOTSUPP, "adapter does not support some i2c transactions\n"); diff --git a/drivers/iio/humidity/hts221_core.c b/drivers/iio/humidity/hts221_core.c index bfeb0a60d3af..47a10884d812 100644 --- a/drivers/iio/humidity/hts221_core.c +++ b/drivers/iio/humidity/hts221_core.c @@ -10,7 +10,6 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/device.h> -#include <linux/iio/sysfs.h> #include <linux/delay.h> #include <linux/pm.h> #include <linux/regmap.h> @@ -49,7 +48,7 @@ struct hts221_odr { struct hts221_avg { u8 addr; u8 mask; - u16 avg_avl[HTS221_AVG_DEPTH]; + int avg_avl[HTS221_AVG_DEPTH]; }; static const struct hts221_odr hts221_odr_table[] = { @@ -58,6 +57,8 @@ static const struct hts221_odr hts221_odr_table[] = { { 13, 0x03 }, /* 12.5Hz */ }; +static const int hts221_odr_avail[] = { 1, 7, 13 }; + static const struct hts221_avg hts221_avg_list[] = { { .addr = 0x10, @@ -97,7 +98,11 @@ static const struct iio_chan_spec hts221_channels[] = { BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index = 0, .scan_type = { .sign = 's', @@ -113,7 +118,11 @@ static const struct iio_chan_spec hts221_channels[] = { BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index = 1, .scan_type = { .sign = 's', @@ -127,19 +136,15 @@ static const struct iio_chan_spec hts221_channels[] = { static int hts221_check_whoami(struct hts221_hw *hw) { + struct device *dev = hw->dev; int err, data; err = regmap_read(hw->regmap, HTS221_REG_WHOAMI_ADDR, &data); - if (err < 0) { - dev_err(hw->dev, "failed to read whoami register\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, "failed to read whoami register\n"); - if (data != HTS221_REG_WHOAMI_VAL) { - dev_err(hw->dev, "wrong whoami {%02x vs %02x}\n", - data, HTS221_REG_WHOAMI_VAL); - return -ENODEV; - } + if (data != HTS221_REG_WHOAMI_VAL) + dev_info(dev, "unexpected whoami 0x%02x, continuing\n", data); return 0; } @@ -192,53 +197,35 @@ static int hts221_update_avg(struct hts221_hw *hw, return 0; } -static ssize_t hts221_sysfs_sampling_freq(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int i; - ssize_t len = 0; - - for (i = 0; i < ARRAY_SIZE(hts221_odr_table); i++) - len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", - hts221_odr_table[i].hz); - buf[len - 1] = '\n'; - - return len; -} - -static ssize_t -hts221_sysfs_rh_oversampling_avail(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - const struct hts221_avg *avg = &hts221_avg_list[HTS221_SENSOR_H]; - ssize_t len = 0; - int i; - - for (i = 0; i < ARRAY_SIZE(avg->avg_avl); i++) - len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", - avg->avg_avl[i]); - buf[len - 1] = '\n'; - - return len; -} - -static ssize_t -hts221_sysfs_temp_oversampling_avail(struct device *dev, - struct device_attribute *attr, - char *buf) +static int hts221_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) { - const struct hts221_avg *avg = &hts221_avg_list[HTS221_SENSOR_T]; - ssize_t len = 0; - int i; - - for (i = 0; i < ARRAY_SIZE(avg->avg_avl); i++) - len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", - avg->avg_avl[i]); - buf[len - 1] = '\n'; - - return len; + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + *vals = hts221_avg_list[HTS221_SENSOR_H].avg_avl; + *length = ARRAY_SIZE(hts221_avg_list[HTS221_SENSOR_H].avg_avl); + break; + case IIO_TEMP: + *vals = hts221_avg_list[HTS221_SENSOR_T].avg_avl; + *length = ARRAY_SIZE(hts221_avg_list[HTS221_SENSOR_T].avg_avl); + break; + default: + return -EINVAL; + } + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = hts221_odr_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(hts221_odr_avail); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } } int hts221_set_enable(struct hts221_hw *hw, bool enable) @@ -258,6 +245,7 @@ int hts221_set_enable(struct hts221_hw *hw, bool enable) static int hts221_parse_temp_caldata(struct hts221_hw *hw) { + struct device *dev = hw->dev; int err, *slope, *b_gen, cal0, cal1; s16 cal_x0, cal_x1, cal_y0, cal_y1; __le16 val; @@ -288,10 +276,20 @@ static int hts221_parse_temp_caldata(struct hts221_hw *hw) return err; cal_x1 = le16_to_cpu(val); + if (cal_x1 == cal_x0) + return dev_err_probe(dev, -EINVAL, + "invalid temperature calibration points (x0 %d, x1 %d)\n", + cal_x0, cal_x1); + slope = &hw->sensors[HTS221_SENSOR_T].slope; b_gen = &hw->sensors[HTS221_SENSOR_T].b_gen; *slope = ((cal_y1 - cal_y0) * 8000) / (cal_x1 - cal_x0); + if (!*slope) + return dev_err_probe(dev, -EINVAL, + "invalid temperature calibration slope (y0 %d, y1 %d)\n", + cal_y0, cal_y1); + *b_gen = (((s32)cal_x1 * cal_y0 - (s32)cal_x0 * cal_y1) * 1000) / (cal_x1 - cal_x0); *b_gen *= 8; @@ -301,6 +299,7 @@ static int hts221_parse_temp_caldata(struct hts221_hw *hw) static int hts221_parse_rh_caldata(struct hts221_hw *hw) { + struct device *dev = hw->dev; int err, *slope, *b_gen, data; s16 cal_x0, cal_x1, cal_y0, cal_y1; __le16 val; @@ -327,10 +326,20 @@ static int hts221_parse_rh_caldata(struct hts221_hw *hw) return err; cal_x1 = le16_to_cpu(val); + if (cal_x1 == cal_x0) + return dev_err_probe(dev, -EINVAL, + "invalid rh calibration points (x0 %d, x1 %d)\n", + cal_x0, cal_x1); + slope = &hw->sensors[HTS221_SENSOR_H].slope; b_gen = &hw->sensors[HTS221_SENSOR_H].b_gen; *slope = ((cal_y1 - cal_y0) * 8000) / (cal_x1 - cal_x0); + if (!*slope) + return dev_err_probe(dev, -EINVAL, + "invalid rh calibration slope (y0 %d, y1 %d)\n", + cal_y0, cal_y1); + *b_gen = (((s32)cal_x1 * cal_y0 - (s32)cal_x0 * cal_y1) * 1000) / (cal_x1 - cal_x0); *b_gen *= 8; @@ -521,27 +530,10 @@ static int hts221_validate_trigger(struct iio_dev *iio_dev, return hw->trig == trig ? 0 : -EINVAL; } -static IIO_DEVICE_ATTR(in_humidity_oversampling_ratio_available, S_IRUGO, - hts221_sysfs_rh_oversampling_avail, NULL, 0); -static IIO_DEVICE_ATTR(in_temp_oversampling_ratio_available, S_IRUGO, - hts221_sysfs_temp_oversampling_avail, NULL, 0); -static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(hts221_sysfs_sampling_freq); - -static struct attribute *hts221_attributes[] = { - &iio_dev_attr_sampling_frequency_available.dev_attr.attr, - &iio_dev_attr_in_humidity_oversampling_ratio_available.dev_attr.attr, - &iio_dev_attr_in_temp_oversampling_ratio_available.dev_attr.attr, - NULL, -}; - -static const struct attribute_group hts221_attribute_group = { - .attrs = hts221_attributes, -}; - static const struct iio_info hts221_info = { - .attrs = &hts221_attribute_group, .read_raw = hts221_read_raw, .write_raw = hts221_write_raw, + .read_avail = hts221_read_avail, .validate_trigger = hts221_validate_trigger, }; @@ -608,33 +600,23 @@ int hts221_probe(struct device *dev, int irq, const char *name, /* configure humidity sensor */ err = hts221_parse_rh_caldata(hw); - if (err < 0) { - dev_err(hw->dev, "failed to get rh calibration data\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, "failed to get rh calibration data\n"); data = hts221_avg_list[HTS221_SENSOR_H].avg_avl[3]; err = hts221_update_avg(hw, HTS221_SENSOR_H, data); - if (err < 0) { - dev_err(hw->dev, "failed to set rh oversampling ratio\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, "failed to set rh oversampling ratio\n"); /* configure temperature sensor */ err = hts221_parse_temp_caldata(hw); - if (err < 0) { - dev_err(hw->dev, - "failed to get temperature calibration data\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, "failed to get temperature calibration data\n"); data = hts221_avg_list[HTS221_SENSOR_T].avg_avl[3]; err = hts221_update_avg(hw, HTS221_SENSOR_T, data); - if (err < 0) { - dev_err(hw->dev, - "failed to set temperature oversampling ratio\n"); - return err; - } + if (err < 0) + return dev_err_probe(dev, err, "failed to set temperature oversampling ratio\n"); if (hw->irq > 0) { err = hts221_allocate_buffers(iio_dev); diff --git a/drivers/iio/humidity/hts221_i2c.c b/drivers/iio/humidity/hts221_i2c.c index 40276abc5d2e..e6dc9532cfbd 100644 --- a/drivers/iio/humidity/hts221_i2c.c +++ b/drivers/iio/humidity/hts221_i2c.c @@ -26,17 +26,14 @@ static const struct regmap_config hts221_i2c_regmap_config = { static int hts221_i2c_probe(struct i2c_client *client) { + struct device *dev = &client->dev; struct regmap *regmap; regmap = devm_regmap_init_i2c(client, &hts221_i2c_regmap_config); - if (IS_ERR(regmap)) { - dev_err(&client->dev, "Failed to register i2c regmap %ld\n", - PTR_ERR(regmap)); - return PTR_ERR(regmap); - } + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to register i2c regmap\n"); - return hts221_probe(&client->dev, client->irq, - client->name, regmap); + return hts221_probe(dev, client->irq, client->name, regmap); } static const struct acpi_device_id hts221_acpi_match[] = { diff --git a/drivers/iio/humidity/hts221_spi.c b/drivers/iio/humidity/hts221_spi.c index f962842cc71d..f2c0b1a81a8f 100644 --- a/drivers/iio/humidity/hts221_spi.c +++ b/drivers/iio/humidity/hts221_spi.c @@ -27,17 +27,14 @@ static const struct regmap_config hts221_spi_regmap_config = { static int hts221_spi_probe(struct spi_device *spi) { + struct device *dev = &spi->dev; struct regmap *regmap; regmap = devm_regmap_init_spi(spi, &hts221_spi_regmap_config); - if (IS_ERR(regmap)) { - dev_err(&spi->dev, "Failed to register spi regmap %ld\n", - PTR_ERR(regmap)); - return PTR_ERR(regmap); - } + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to register spi regmap\n"); - return hts221_probe(&spi->dev, spi->irq, - spi->modalias, regmap); + return hts221_probe(dev, spi->irq, spi->modalias, regmap); } static const struct of_device_id hts221_spi_of_match[] = { diff --git a/drivers/iio/iio_core.h b/drivers/iio/iio_core.h index b7d5f4f0fada..59fa1bd9924b 100644 --- a/drivers/iio/iio_core.h +++ b/drivers/iio/iio_core.h @@ -109,4 +109,11 @@ void iio_device_wakeup_eventset(struct iio_dev *indio_dev); struct iio_event_interface; bool iio_event_enabled(const struct iio_event_interface *ev_int); +#if IS_ENABLED(CONFIG_KUNIT) +ssize_t __iio_chan_prefix_emit(struct device *dev, + const struct iio_chan_spec *chan, + enum iio_shared_by shared_by, + char *buf, size_t len); +#endif + #endif diff --git a/drivers/iio/imu/adis16400.c b/drivers/iio/imu/adis16400.c index 8f664d055e9d..79f2974d3777 100644 --- a/drivers/iio/imu/adis16400.c +++ b/drivers/iio/imu/adis16400.c @@ -420,7 +420,10 @@ static int adis16400_initial_setup(struct iio_dev *indio_dev) else st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; st->adis.spi->mode = SPI_MODE_3; - spi_setup(st->adis.spi); + + ret = spi_setup(st->adis.spi); + if (ret) + return ret; ret = __adis_initial_startup(&st->adis); if (ret) @@ -1150,6 +1153,9 @@ static int adis16400_probe(struct spi_device *spi) /* setup the industrialio driver allocated elements */ st->variant = spi_get_device_match_data(spi); + if (!st->variant) + return -ENODATA; + indio_dev->name = spi_get_device_id(spi)->name; indio_dev->channels = st->variant->channels; indio_dev->num_channels = st->variant->num_channels; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h index b55d993f0264..e56943931b6a 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h @@ -179,6 +179,7 @@ struct inv_icm42600_state { struct { s64 gyro; s64 accel; + bool accel_force_odr; } timestamp; struct inv_icm42600_apex apex; struct inv_icm42600_fifo fifo; @@ -356,8 +357,8 @@ struct inv_icm42600_sensor_state { cpu_to_le16((_wm) & GENMASK(11, 0)) /* FIFO is 2048 bytes, let 12 samples for reading latency */ #define INV_ICM42600_FIFO_WATERMARK_MAX (2048 - 12 * 16) -/* INV_ICM42600_FIFO_WATERMARK_MAX / 8 = 232 */ -#define INV_ICM42600_FIFO_WATERMARK_MAX_SAMPLES 232 +/* INV_ICM42600_FIFO_WATERMARK_MAX / 16 = 116 */ +#define INV_ICM42600_FIFO_WATERMARK_MAX_SAMPLES 116 #define INV_ICM42600_REG_INT_CONFIG1 0x0064 #define INV_ICM42600_INT_CONFIG1_TPULSE_DURATION BIT(6) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c index 4b0e3cd8a506..cc8c074777c9 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c @@ -254,15 +254,23 @@ static int inv_icm42600_accel_update_scan_mode(struct iio_dev *indio_dev, fifo_en |= INV_ICM42600_SENSOR_ACCEL; } + /* + * Sleep maximum stabilization time before enabling data in FIFO. + * We need to release the driver lock to not block gyro data processing. + * There is no possible race here since we are under IIO mutex locked. + */ + sleep = max(sleep_accel, sleep_temp); + if (sleep) { + mutex_unlock(&st->lock); + msleep(sleep); + mutex_lock(&st->lock); + } + /* update data FIFO write */ ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); out_unlock: mutex_unlock(&st->lock); - /* sleep maximum required time */ - sleep = max(sleep_accel, sleep_temp); - if (sleep) - msleep(sleep); return ret; } @@ -1227,14 +1235,16 @@ int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev) if (size <= 0) return size; - /* skip packet if no accel data or data is invalid */ - if (accel == NULL || !inv_icm42600_fifo_is_data_valid(accel)) - continue; - /* update odr */ - if (odr & INV_ICM42600_SENSOR_ACCEL) + if (odr & INV_ICM42600_SENSOR_ACCEL) { inv_sensors_timestamp_apply_odr(ts, st->fifo.period, st->fifo.nb.total, no); + st->timestamp.accel_force_odr = false; + } + + /* skip packet if no accel data or data is invalid */ + if (accel == NULL || !inv_icm42600_fifo_is_data_valid(accel)) + continue; memcpy(&buffer.accel, accel, sizeof(buffer.accel)); /* convert 8 bits FIFO temperature in high resolution format */ @@ -1243,5 +1253,10 @@ int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev) iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val); } + if (st->timestamp.accel_force_odr) { + inv_sensors_timestamp_apply_odr(ts, 0, 0, 0); + st->timestamp.accel_force_odr = false; + } + return 0; } diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c index 998d312f7bde..2bf9fc29d7d2 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c @@ -5,6 +5,7 @@ #include <linux/delay.h> #include <linux/device.h> +#include <linux/gcd.h> #include <linux/kernel.h> #include <linux/minmax.h> #include <linux/mutex.h> @@ -131,13 +132,16 @@ int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st, INV_ICM42600_FIFO_CONFIG1_GYRO_EN | INV_ICM42600_FIFO_CONFIG1_ACCEL_EN; - val = 0; - if (fifo_en & INV_ICM42600_SENSOR_GYRO) - val |= INV_ICM42600_FIFO_CONFIG1_GYRO_EN; - if (fifo_en & INV_ICM42600_SENSOR_ACCEL) - val |= INV_ICM42600_FIFO_CONFIG1_ACCEL_EN; - if (fifo_en & INV_ICM42600_SENSOR_TEMP) - val |= INV_ICM42600_FIFO_CONFIG1_TEMP_EN; + /* + * Always enable/disable all bits to ensure we can flawlessly add + * accel/gyro data in the FIFO while it is running. + */ + if (fifo_en) + val = INV_ICM42600_FIFO_CONFIG1_TEMP_EN | + INV_ICM42600_FIFO_CONFIG1_GYRO_EN | + INV_ICM42600_FIFO_CONFIG1_ACCEL_EN; + else + val = 0; ret = regmap_update_bits(st->map, INV_ICM42600_REG_FIFO_CONFIG1, mask, val); if (ret) @@ -149,19 +153,6 @@ int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st, return 0; } -static size_t inv_icm42600_get_packet_size(unsigned int fifo_en) -{ - size_t packet_size; - - if ((fifo_en & INV_ICM42600_SENSOR_GYRO) && - (fifo_en & INV_ICM42600_SENSOR_ACCEL)) - packet_size = INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; - else - packet_size = INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE; - - return packet_size; -} - static unsigned int inv_icm42600_wm_truncate(unsigned int watermark, size_t packet_size) { @@ -185,15 +176,14 @@ static unsigned int inv_icm42600_wm_truncate(unsigned int watermark, * * FIFO watermark threshold is computed based on the required watermark values * set for gyro and accel sensors. Since watermark is all about acceptable data - * latency, use the smallest setting between the 2. It means choosing the - * smallest latency but this is not as simple as choosing the smallest watermark - * value. Latency depends on watermark and ODR. It requires several steps: - * 1) compute gyro and accel latencies and choose the smallest value. - * 2) adapt the chosen latency so that it is a multiple of both gyro and accel - * ones. Otherwise it is possible that you don't meet a requirement. (for - * example with gyro @100Hz wm 4 and accel @100Hz with wm 6, choosing the - * value of 4 will not meet accel latency requirement because 6 is not a - * multiple of 4. You need to use the value 2.) + * latency, we should need to use the smallest latency value. But it is not as + * simple as choosing the smallest watermark value. Latency depends on watermark + * and ODR and IIO buffer watermark adds another requirement. The required steps: + * 1) compute gyro and accel periods and latencies + * 2) Use the smallest period and the GCD of the latencies. GCD is required + * because of the IIO buffer watermark that will prevent send of data if not + * crossed. Thus accel and gyro watermarks must be a multiple of the watermark + * value. Computing the GCD gives us the biggest value that meets this criteria. * 3) Since all periods are multiple of each others, watermark is computed by * dividing this computed latency by the smallest period, which corresponds * to the FIFO frequency. Beware that this is only true because we are not @@ -203,13 +193,13 @@ int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st) { size_t packet_size, wm_size; unsigned int wm_gyro, wm_accel, watermark; - u32 period_gyro, period_accel; + u32 period_gyro, period_accel, period; u32 latency_gyro, latency_accel, latency; bool restore; __le16 raw_wm; int ret; - packet_size = inv_icm42600_get_packet_size(st->fifo.en); + packet_size = INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; /* compute sensors latency, depending on sensor watermark and odr */ wm_gyro = inv_icm42600_wm_truncate(st->fifo.watermark.gyro, packet_size); @@ -231,22 +221,17 @@ int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st) watermark = wm_gyro; st->fifo.watermark.eff_gyro = wm_gyro; } else { - /* compute the smallest latency that is a multiple of both */ - if (latency_gyro <= latency_accel) - latency = latency_gyro - (latency_accel % latency_gyro); - else - latency = latency_accel - (latency_gyro % latency_accel); - /* all this works because periods are multiple of each others */ - watermark = latency / min(period_gyro, period_accel); - if (watermark < 1) - watermark = 1; - /* update effective watermark */ - st->fifo.watermark.eff_gyro = latency / period_gyro; - if (st->fifo.watermark.eff_gyro < 1) - st->fifo.watermark.eff_gyro = 1; - st->fifo.watermark.eff_accel = latency / period_accel; - if (st->fifo.watermark.eff_accel < 1) - st->fifo.watermark.eff_accel = 1; + /* + * In case of both accel and gyro enabled, we need to use the + * shortest period and the gcd of the latencies. Gcd is required + * because of the IIO buffer watermark that will prevent data + * sending if we are not crossing the watermark level. + */ + period = min(period_gyro, period_accel); + latency = gcd(latency_gyro, latency_accel); + watermark = max(latency / period, 1); + st->fifo.watermark.eff_gyro = max(latency / period_gyro, 1); + st->fifo.watermark.eff_accel = max(latency / period_accel, 1); } /* compute watermark value in bytes */ @@ -399,6 +384,8 @@ static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev) mutex_lock(&st->lock); inv_sensors_timestamp_apply_odr(ts, 0, 0, 0); + if (sensor == INV_ICM42600_SENSOR_ACCEL) + st->timestamp.accel_force_odr = false; ret = inv_icm42600_buffer_set_fifo_en(st, st->fifo.en & ~sensor); if (ret) @@ -492,24 +479,21 @@ int inv_icm42600_buffer_fifo_read(struct inv_icm42600_state *st, st->fifo.nb.accel = 0; st->fifo.nb.total = 0; - /* compute maximum FIFO read size (watermark for max = 0 interrupt case) */ - if (max == 0) - max = st->fifo.watermark.value; - max_count = max * inv_icm42600_get_packet_size(st->fifo.en); - - /* read FIFO count value */ - raw_fifo_count = (__be16 *)st->buffer; - ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT, - raw_fifo_count, sizeof(*raw_fifo_count)); - if (ret) - return ret; - st->fifo.count = be16_to_cpup(raw_fifo_count); - - /* check and clamp FIFO count value */ - if (st->fifo.count == 0) - return 0; - if (st->fifo.count > max_count) - st->fifo.count = max_count; + /* read watermark samples for interrupt case (max = 0) or read FIFO count */ + if (max == 0) { + st->fifo.count = st->fifo.watermark.value * + INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; + } else { + raw_fifo_count = (__be16 *)st->buffer; + ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT, + raw_fifo_count, sizeof(*raw_fifo_count)); + if (ret) + return ret; + max_count = max * INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; + st->fifo.count = min(be16_to_cpup(raw_fifo_count), max_count); + if (st->fifo.count == 0) + return 0; + } /* read all FIFO data in internal buffer */ ret = regmap_noinc_read(st->map, INV_ICM42600_REG_FIFO_DATA, diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c index dc97d8a274e3..cd2e53416f5b 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c @@ -8,6 +8,7 @@ #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/kernel.h> +#include <linux/limits.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/pm_runtime.h> @@ -324,6 +325,15 @@ int inv_icm42600_set_accel_conf(struct inv_icm42600_state *st, break; } + /* + * If we change mode from low-power to low-noise because of odr, + * we need to force odr change. + */ + if (oldconf->mode == INV_ICM42600_SENSOR_MODE_LOW_POWER && + conf->mode == INV_ICM42600_SENSOR_MODE_LOW_NOISE && + conf->odr != oldconf->odr) + st->timestamp.accel_force_odr = true; + /* set ACCEL_CONFIG0 register (accel fullscale & odr) */ if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) { val = INV_ICM42600_ACCEL_CONFIG0_FS(conf->fs) | @@ -509,10 +519,20 @@ static int inv_icm42600_setup(struct inv_icm42600_state *st, ret = regmap_read(st->map, INV_ICM42600_REG_WHOAMI, &val); if (ret) return ret; + + /* + * SPI interface has no ack mechanism. + * 0xFF or 0x00 whoami means no response from the device. + */ + if (val == U8_MAX || val == 0) + return dev_err_probe(dev, -ENODEV, + "invalid whoami %#04x expected %#04x (%s)\n", + val, hw->whoami, hw->name); + if (val != hw->whoami) { - dev_err(dev, "invalid whoami %#02x expected %#02x (%s)\n", - val, hw->whoami, hw->name); - return -ENODEV; + dev_info(dev, + "device id %#04x is not the %#04x associated with the FW-specified device (%s), probably using a valid fallback compatible\n", + val, hw->whoami, hw->name); } st->name = hw->name; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c index 253bf571439d..4e20d61d897b 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c @@ -126,15 +126,23 @@ static int inv_icm42600_gyro_update_scan_mode(struct iio_dev *indio_dev, fifo_en |= INV_ICM42600_SENSOR_GYRO; } + /* + * Sleep maximum stabilization time before enabling data in FIFO. + * We need to release the driver lock to not block accel data processing. + * There is no possible race here since we are under IIO mutex locked. + */ + sleep = max(sleep_gyro, sleep_temp); + if (sleep) { + mutex_unlock(&st->lock); + msleep(sleep); + mutex_lock(&st->lock); + } + /* update data FIFO write */ ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); out_unlock: mutex_unlock(&st->lock); - /* sleep maximum required time */ - sleep = max(sleep_gyro, sleep_temp); - if (sleep) - msleep(sleep); return ret; } @@ -808,15 +816,15 @@ int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev) if (size <= 0) return size; - /* skip packet if no gyro data or data is invalid */ - if (gyro == NULL || !inv_icm42600_fifo_is_data_valid(gyro)) - continue; - /* update odr */ if (odr & INV_ICM42600_SENSOR_GYRO) inv_sensors_timestamp_apply_odr(ts, st->fifo.period, st->fifo.nb.total, no); + /* skip packet if no gyro data or data is invalid */ + if (gyro == NULL || !inv_icm42600_fifo_is_data_valid(gyro)) + continue; + memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro)); /* convert 8 bits FIFO temperature in high resolution format */ buffer.temp = temp ? (*temp * 64) : 0; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c index 28552d2db91d..e44ed6dcde02 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c @@ -77,10 +77,10 @@ static const struct i2c_device_id inv_icm42600_id[] = { { .name = "icm42600", .driver_data = INV_CHIP_ICM42600 }, { .name = "icm42602", .driver_data = INV_CHIP_ICM42602 }, { .name = "icm42605", .driver_data = INV_CHIP_ICM42605 }, - { .name = "icm42686", .driver_data = INV_CHIP_ICM42686 }, { .name = "icm42622", .driver_data = INV_CHIP_ICM42622 }, - { .name = "icm42688", .driver_data = INV_CHIP_ICM42688 }, { .name = "icm42631", .driver_data = INV_CHIP_ICM42631 }, + { .name = "icm42686", .driver_data = INV_CHIP_ICM42686 }, + { .name = "icm42688", .driver_data = INV_CHIP_ICM42688 }, { } }; MODULE_DEVICE_TABLE(i2c, inv_icm42600_id); @@ -96,17 +96,17 @@ static const struct of_device_id inv_icm42600_of_matches[] = { .compatible = "invensense,icm42605", .data = (void *)INV_CHIP_ICM42605, }, { - .compatible = "invensense,icm42686", - .data = (void *)INV_CHIP_ICM42686, - }, { .compatible = "invensense,icm42622", .data = (void *)INV_CHIP_ICM42622, }, { - .compatible = "invensense,icm42688", - .data = (void *)INV_CHIP_ICM42688, - }, { .compatible = "invensense,icm42631", .data = (void *)INV_CHIP_ICM42631, + }, { + .compatible = "invensense,icm42686", + .data = (void *)INV_CHIP_ICM42686, + }, { + .compatible = "invensense,icm42688", + .data = (void *)INV_CHIP_ICM42688, }, { } }; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c index faf743bc6444..3e2f4eb42e97 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c @@ -74,10 +74,10 @@ static const struct spi_device_id inv_icm42600_id[] = { { .name = "icm42600", .driver_data = INV_CHIP_ICM42600 }, { .name = "icm42602", .driver_data = INV_CHIP_ICM42602 }, { .name = "icm42605", .driver_data = INV_CHIP_ICM42605 }, - { .name = "icm42686", .driver_data = INV_CHIP_ICM42686 }, { .name = "icm42622", .driver_data = INV_CHIP_ICM42622 }, - { .name = "icm42688", .driver_data = INV_CHIP_ICM42688 }, { .name = "icm42631", .driver_data = INV_CHIP_ICM42631 }, + { .name = "icm42686", .driver_data = INV_CHIP_ICM42686 }, + { .name = "icm42688", .driver_data = INV_CHIP_ICM42688 }, { } }; MODULE_DEVICE_TABLE(spi, inv_icm42600_id); @@ -93,17 +93,17 @@ static const struct of_device_id inv_icm42600_of_matches[] = { .compatible = "invensense,icm42605", .data = (void *)INV_CHIP_ICM42605, }, { - .compatible = "invensense,icm42686", - .data = (void *)INV_CHIP_ICM42686, - }, { .compatible = "invensense,icm42622", .data = (void *)INV_CHIP_ICM42622, }, { - .compatible = "invensense,icm42688", - .data = (void *)INV_CHIP_ICM42688, - }, { .compatible = "invensense,icm42631", .data = (void *)INV_CHIP_ICM42631, + }, { + .compatible = "invensense,icm42686", + .data = (void *)INV_CHIP_ICM42686, + }, { + .compatible = "invensense,icm42688", + .data = (void *)INV_CHIP_ICM42688, }, { } }; diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index 5796896d54cd..38c3f3b398d4 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -13,6 +13,7 @@ #include <linux/irq.h> #include <linux/interrupt.h> #include <linux/acpi.h> +#include <linux/kstrtox.h> #include <linux/platform_device.h> #include <linux/regulator/consumer.h> #include <linux/math64.h> @@ -1303,8 +1304,9 @@ inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr, struct inv_mpu6050_state *st = iio_priv(indio_dev); struct device *pdev = regmap_get_device(st->map); - if (kstrtoint(buf, 10, &fifo_rate)) - return -EINVAL; + result = kstrtoint(buf, 10, &fifo_rate); + if (result) + return result; if (fifo_rate < INV_MPU6050_MIN_FIFO_RATE || fifo_rate > INV_MPU6050_MAX_FIFO_RATE) return -EINVAL; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c index d6a1eeb151ca..cbc47fa5b101 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c @@ -766,8 +766,8 @@ st_lsm6dsx_shub_alloc_iiodev(struct st_lsm6dsx_hw *hw, IIO_CHAN_SOFT_TIMESTAMP(3), }; - ext_channels = devm_kzalloc(hw->dev, sizeof(magn_channels), - GFP_KERNEL); + ext_channels = devm_kcalloc(hw->dev, ARRAY_SIZE(magn_channels), + sizeof(*ext_channels), GFP_KERNEL); if (!ext_channels) return NULL; diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c index f7a4be8ec320..fbf5c39d7655 100644 --- a/drivers/iio/industrialio-backend.c +++ b/drivers/iio/industrialio-backend.c @@ -887,6 +887,39 @@ int iio_backend_num_lanes_set(struct iio_backend *back, unsigned int num_lanes) EXPORT_SYMBOL_NS_GPL(iio_backend_num_lanes_set, "IIO_BACKEND"); /** + * iio_backend_crc_enable - Enable CRC handling + * @back: Backend device + * + * On input data paths, enable backend handling of CRC data generated by the + * frontend. On output data paths, enable the backend to generate CRC data for + * the frontend to verify. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_crc_enable(struct iio_backend *back) +{ + return iio_backend_op_call(back, crc_enable); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_crc_enable, "IIO_BACKEND"); + +/** + * iio_backend_crc_disable - Disable CRC handling + * @back: Backend device + * + * On input data paths, disable backend handling of CRC data generated by the + * frontend. On output data paths, disable CRC generation by the backend. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_crc_disable(struct iio_backend *back) +{ + return iio_backend_op_call(back, crc_disable); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_crc_disable, "IIO_BACKEND"); + +/** * iio_backend_ddr_enable - Enable interface DDR (Double Data Rate) mode * @back: Backend device * diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 767a7794624a..d73f2541375f 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -9,12 +9,15 @@ #define pr_fmt(fmt) "iio-core: " fmt +#include <kunit/visibility.h> + #include <linux/anon_inodes.h> #include <linux/cdev.h> #include <linux/cleanup.h> #include <linux/debugfs.h> #include <linux/device.h> #include <linux/err.h> +#include <linux/export.h> #include <linux/fs.h> #include <linux/idr.h> #include <linux/kdev_t.h> @@ -25,6 +28,7 @@ #include <linux/poll.h> #include <linux/property.h> #include <linux/sched.h> +#include <linux/seq_buf.h> #include <linux/slab.h> #include <linux/wait.h> @@ -100,6 +104,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_ALTCURRENT] = "altcurrent", [IIO_COVERAGE] = "coverage", [IIO_VOLUMEFLOW] = "volumeflow", + [IIO_FREQUENCY] = "frequency", }; static const char * const iio_modifier_names[] = { @@ -199,6 +204,63 @@ static const char * const iio_chan_info_postfix[] = { [IIO_CHAN_INFO_CONVDELAY] = "convdelay", [IIO_CHAN_INFO_POWERFACTOR] = "powerfactor", }; + +VISIBLE_IF_KUNIT +ssize_t __iio_chan_prefix_emit(struct device *dev, + const struct iio_chan_spec *chan, + enum iio_shared_by shared_by, + char *buf, size_t len) +{ + const char *type = iio_chan_type_name_spec[chan->type]; + const char *dir = iio_direction[chan->output]; + struct seq_buf s; + + seq_buf_init(&s, buf, len); + + switch (shared_by) { + case IIO_SHARED_BY_ALL: + break; + case IIO_SHARED_BY_DIR: + seq_buf_printf(&s, "%s", dir); + break; + case IIO_SHARED_BY_TYPE: + seq_buf_printf(&s, "%s_%s", dir, type); + if (chan->differential) + seq_buf_printf(&s, "-%s", type); + break; + case IIO_SEPARATE: + /* Validate differential channel settings */ + if (chan->differential) { + if (!chan->indexed) { + dev_err(dev, "Differential channels must be indexed\n"); + return -EINVAL; + } + if (chan->modified) { + dev_err(dev, "Differential channels can not have modifier\n"); + return -EINVAL; + } + } + + seq_buf_printf(&s, "%s_%s", dir, type); + + if (chan->indexed) + seq_buf_printf(&s, "%d", chan->channel); + + if (chan->differential) + seq_buf_printf(&s, "-%s%d", type, chan->channel2); + + if (chan->modified) + seq_buf_printf(&s, "_%s", iio_modifier_names[chan->channel2]); + + if (chan->extend_name) + seq_buf_printf(&s, "_%s", chan->extend_name); + break; + } + + return seq_buf_has_overflowed(&s) ? -EOVERFLOW : s.len; +} +EXPORT_SYMBOL_IF_KUNIT(__iio_chan_prefix_emit); + /** * iio_device_id() - query the unique ID for the device * @indio_dev: Device structure whose ID is being queried @@ -341,7 +403,8 @@ s64 iio_get_time_ns(const struct iio_dev *indio_dev) case CLOCK_TAI: return ktime_get_clocktai_ns(); default: - BUG(); + WARN_ON_ONCE(1); + return 0; } } EXPORT_SYMBOL(iio_get_time_ns); @@ -388,6 +451,7 @@ static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf, struct iio_dev *indio_dev = file->private_data; struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); unsigned int val = 0; + u64 val64 = 0; int ret; if (*ppos > 0) @@ -395,9 +459,17 @@ static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf, iio_dev_opaque->read_buf, iio_dev_opaque->read_buf_len); - ret = indio_dev->info->debugfs_reg_access(indio_dev, - iio_dev_opaque->cached_reg_addr, - 0, &val); + if (indio_dev->info->debugfs_reg64_access) { + ret = indio_dev->info->debugfs_reg64_access(indio_dev, + iio_dev_opaque->cached_reg_addr, + 0, &val64); + } else { + ret = indio_dev->info->debugfs_reg_access(indio_dev, + iio_dev_opaque->cached_reg_addr, + 0, &val); + val64 = val; + } + if (ret) { dev_err(indio_dev->dev.parent, "%s: read failed\n", __func__); return ret; @@ -405,7 +477,7 @@ static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf, iio_dev_opaque->read_buf_len = snprintf(iio_dev_opaque->read_buf, sizeof(iio_dev_opaque->read_buf), - "0x%X\n", val); + "0x%llX\n", val64); return simple_read_from_buffer(userbuf, count, ppos, iio_dev_opaque->read_buf, @@ -417,8 +489,9 @@ static ssize_t iio_debugfs_write_reg(struct file *file, { struct iio_dev *indio_dev = file->private_data; struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); - unsigned int reg, val; + unsigned int reg; char buf[80]; + u64 val64; int ret; if (*ppos != 0 || count >= sizeof(buf)) @@ -431,7 +504,7 @@ static ssize_t iio_debugfs_write_reg(struct file *file, buf[ret] = '\0'; - ret = sscanf(buf, "%i %i", ®, &val); + ret = sscanf(buf, "%i %lli", ®, &val64); switch (ret) { case 1: @@ -439,8 +512,12 @@ static ssize_t iio_debugfs_write_reg(struct file *file, break; case 2: iio_dev_opaque->cached_reg_addr = reg; - ret = indio_dev->info->debugfs_reg_access(indio_dev, reg, - val, NULL); + if (indio_dev->info->debugfs_reg64_access) + ret = indio_dev->info->debugfs_reg64_access(indio_dev, reg, + val64, NULL); + else + ret = indio_dev->info->debugfs_reg_access(indio_dev, reg, + val64, NULL); if (ret) { dev_err(indio_dev->dev.parent, "%s: write failed\n", __func__); @@ -471,7 +548,8 @@ static void iio_device_register_debugfs(struct iio_dev *indio_dev) { struct iio_dev_opaque *iio_dev_opaque; - if (indio_dev->info->debugfs_reg_access == NULL) + if (!indio_dev->info->debugfs_reg_access && + !indio_dev->info->debugfs_reg64_access) return; if (!iio_debugfs_dentry) @@ -781,6 +859,20 @@ static ssize_t iio_read_channel_label(struct device *dev, to_iio_dev_attr(attr)->c, buf); } +static ssize_t iio_read_channel_parent(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const struct iio_chan_spec *chan = to_iio_dev_attr(attr)->c->parent; + ssize_t len; + + len = __iio_chan_prefix_emit(dev, chan, IIO_SEPARATE, buf, PAGE_SIZE - 1); + if (len < 0) + return len; + + return len + sysfs_emit_at(buf, len, "\n"); +} + static ssize_t iio_read_channel_info(struct device *dev, struct device_attribute *attr, char *buf) @@ -1075,7 +1167,8 @@ static ssize_t iio_write_channel_info(struct device *dev, } static -int __iio_device_attr_init(struct device_attribute *dev_attr, +int __iio_device_attr_init(struct device *dev, + struct device_attribute *dev_attr, const char *postfix, struct iio_chan_spec const *chan, ssize_t (*readfunc)(struct device *dev, @@ -1087,105 +1180,21 @@ int __iio_device_attr_init(struct device_attribute *dev_attr, size_t len), enum iio_shared_by shared_by) { - int ret = 0; - char *name = NULL; - char *full_postfix; + char prefix[NAME_MAX + 1]; + ssize_t ret; sysfs_attr_init(&dev_attr->attr); - /* Build up postfix of <extend_name>_<modifier>_postfix */ - if (chan->modified && (shared_by == IIO_SEPARATE)) { - if (chan->extend_name) - full_postfix = kasprintf(GFP_KERNEL, "%s_%s_%s", - iio_modifier_names[chan->channel2], - chan->extend_name, - postfix); - else - full_postfix = kasprintf(GFP_KERNEL, "%s_%s", - iio_modifier_names[chan->channel2], - postfix); - } else { - if (chan->extend_name == NULL || shared_by != IIO_SEPARATE) - full_postfix = kstrdup(postfix, GFP_KERNEL); - else - full_postfix = kasprintf(GFP_KERNEL, - "%s_%s", - chan->extend_name, - postfix); - } - if (full_postfix == NULL) - return -ENOMEM; - - if (chan->differential) { /* Differential can not have modifier */ - switch (shared_by) { - case IIO_SHARED_BY_ALL: - name = kasprintf(GFP_KERNEL, "%s", full_postfix); - break; - case IIO_SHARED_BY_DIR: - name = kasprintf(GFP_KERNEL, "%s_%s", - iio_direction[chan->output], - full_postfix); - break; - case IIO_SHARED_BY_TYPE: - name = kasprintf(GFP_KERNEL, "%s_%s-%s_%s", - iio_direction[chan->output], - iio_chan_type_name_spec[chan->type], - iio_chan_type_name_spec[chan->type], - full_postfix); - break; - case IIO_SEPARATE: - if (!chan->indexed) { - WARN(1, "Differential channels must be indexed\n"); - ret = -EINVAL; - goto error_free_full_postfix; - } - name = kasprintf(GFP_KERNEL, - "%s_%s%d-%s%d_%s", - iio_direction[chan->output], - iio_chan_type_name_spec[chan->type], - chan->channel, - iio_chan_type_name_spec[chan->type], - chan->channel2, - full_postfix); - break; - } - } else { /* Single ended */ - switch (shared_by) { - case IIO_SHARED_BY_ALL: - name = kasprintf(GFP_KERNEL, "%s", full_postfix); - break; - case IIO_SHARED_BY_DIR: - name = kasprintf(GFP_KERNEL, "%s_%s", - iio_direction[chan->output], - full_postfix); - break; - case IIO_SHARED_BY_TYPE: - name = kasprintf(GFP_KERNEL, "%s_%s_%s", - iio_direction[chan->output], - iio_chan_type_name_spec[chan->type], - full_postfix); - break; + ret = __iio_chan_prefix_emit(dev, chan, shared_by, prefix, sizeof(prefix)); + if (ret < 0) + return ret; - case IIO_SEPARATE: - if (chan->indexed) - name = kasprintf(GFP_KERNEL, "%s_%s%d_%s", - iio_direction[chan->output], - iio_chan_type_name_spec[chan->type], - chan->channel, - full_postfix); - else - name = kasprintf(GFP_KERNEL, "%s_%s_%s", - iio_direction[chan->output], - iio_chan_type_name_spec[chan->type], - full_postfix); - break; - } - } - if (name == NULL) { - ret = -ENOMEM; - goto error_free_full_postfix; - } - dev_attr->attr.name = name; + if (ret) + dev_attr->attr.name = kasprintf(GFP_KERNEL, "%s_%s", prefix, postfix); + else + dev_attr->attr.name = kstrdup(postfix, GFP_KERNEL); + if (!dev_attr->attr.name) + return -ENOMEM; if (readfunc) { dev_attr->attr.mode |= 0444; @@ -1197,10 +1206,7 @@ int __iio_device_attr_init(struct device_attribute *dev_attr, dev_attr->store = writefunc; } -error_free_full_postfix: - kfree(full_postfix); - - return ret; + return 0; } static void __iio_device_attr_deinit(struct device_attribute *dev_attr) @@ -1229,7 +1235,7 @@ int __iio_add_chan_devattr(const char *postfix, iio_attr = kzalloc_obj(*iio_attr); if (iio_attr == NULL) return -ENOMEM; - ret = __iio_device_attr_init(&iio_attr->dev_attr, + ret = __iio_device_attr_init(dev, &iio_attr->dev_attr, postfix, chan, readfunc, writefunc, shared_by); if (ret) @@ -1281,6 +1287,25 @@ static int iio_device_add_channel_label(struct iio_dev *indio_dev, return 1; } +static int iio_device_add_channel_parent_attr(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + int ret; + + if (!chan->parent) + return 0; + + ret = __iio_add_chan_devattr("parent", chan, + &iio_read_channel_parent, NULL, + 0, IIO_SEPARATE, &indio_dev->dev, NULL, + &iio_dev_opaque->channel_attr_list); + if (ret < 0) + return ret; + + return 1; +} + static int iio_device_add_info_mask_type(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, enum iio_shared_by shared_by, @@ -1419,6 +1444,11 @@ static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev, return ret; attrcount += ret; + ret = iio_device_add_channel_parent_attr(indio_dev, chan); + if (ret < 0) + return ret; + attrcount += ret; + if (chan->ext_info) { unsigned int i = 0; @@ -1518,7 +1548,8 @@ static ssize_t current_timestamp_clock_show(struct device *dev, case CLOCK_TAI: break; default: - BUG(); + WARN_ON_ONCE(1); + return -EINVAL; } return sysfs_emit(buf, "%s\n", clock_names[clk]); diff --git a/drivers/iio/industrialio-gts-helper.c b/drivers/iio/industrialio-gts-helper.c index 4f52dc373abf..af6ed37fb34a 100644 --- a/drivers/iio/industrialio-gts-helper.c +++ b/drivers/iio/industrialio-gts-helper.c @@ -5,7 +5,7 @@ */ #include <linux/device.h> -#include <linux/errno.h> +#include <linux/err.h> #include <linux/export.h> #include <linux/minmax.h> #include <linux/module.h> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index f23bbce12c72..462fff1ddef2 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -741,6 +741,20 @@ config VEML6030 To compile this driver as a module, choose M here: the module will be called veml6030. +config VEML6031X00 + tristate "VEML6031X00 ambient light sensor series" + select REGMAP_I2C + select IIO_GTS_HELPER + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6031X00 + ambient light sensor series. + + To compile this driver as a module, choose M here: the + module will be called veml6031x00. + config VEML6040 tristate "VEML6040 RGBW light sensor" select REGMAP_I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 64e354c49ed8..6672ed01a819 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_VCNL4035) += vcnl4035.o obj-$(CONFIG_VEML3235) += veml3235.o obj-$(CONFIG_VEML3328) += veml3328.o obj-$(CONFIG_VEML6030) += veml6030.o +obj-$(CONFIG_VEML6031X00) += veml6031x00.o obj-$(CONFIG_VEML6040) += veml6040.o obj-$(CONFIG_VEML6046X00) += veml6046x00.o obj-$(CONFIG_VEML6070) += veml6070.o diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c index f681b02e4922..7dd41136734f 100644 --- a/drivers/iio/light/apds9306.c +++ b/drivers/iio/light/apds9306.c @@ -61,7 +61,7 @@ #define APDS9306_NUM_REPEAT_RATES 7 #define APDS9306_INT_SRC_CLEAR 0 #define APDS9306_INT_SRC_ALS 1 -#define APDS9306_SAMP_FREQ_10HZ 0 +#define APDS9306_SAMP_FREQ_10HZ 2 /** * struct part_id_gts_multiplier - Part no. and corresponding gts multiplier diff --git a/drivers/iio/light/apds9999.c b/drivers/iio/light/apds9999.c index 43fa9992c9c2..62620c4a2fbc 100644 --- a/drivers/iio/light/apds9999.c +++ b/drivers/iio/light/apds9999.c @@ -100,10 +100,6 @@ static int apds9999_init(struct apds9999_data *data) u8 regval; int ret; - ret = devm_add_action_or_reset(dev, apds9999_standby, client); - if (ret) - return ret; - guard(mutex)(&data->lock); regval = FIELD_PREP(APDS9999_LS_RES_MASK, APDS9999_RES_18BIT) | @@ -121,8 +117,12 @@ static int apds9999_init(struct apds9999_data *data) return ret; data->als_gain_idx = APDS9999_GAIN_3X; - return i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL, - APDS9999_MAIN_CTRL_LS_EN); + ret = i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL, + APDS9999_MAIN_CTRL_LS_EN); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, apds9999_standby, client); } static int apds9999_read_channel(struct apds9999_data *data, u8 reg, diff --git a/drivers/iio/light/cros_ec_light_prox.c b/drivers/iio/light/cros_ec_light_prox.c index 7ab565b1fb9f..b1c92de7922a 100644 --- a/drivers/iio/light/cros_ec_light_prox.c +++ b/drivers/iio/light/cros_ec_light_prox.c @@ -249,7 +249,7 @@ MODULE_DEVICE_TABLE(platform, cros_ec_light_prox_ids); static struct platform_driver cros_ec_light_prox_platform_driver = { .driver = { .name = "cros-ec-light-prox", - .pm = &cros_ec_sensors_pm_ops, + .pm = pm_sleep_ptr(&cros_ec_sensors_pm_ops), }, .probe = cros_ec_light_prox_probe, .id_table = cros_ec_light_prox_ids, diff --git a/drivers/iio/light/iqs621-als.c b/drivers/iio/light/iqs621-als.c index cd5843e3e2c3..f9d215ef1970 100644 --- a/drivers/iio/light/iqs621-als.c +++ b/drivers/iio/light/iqs621-als.c @@ -179,19 +179,6 @@ static int iqs621_als_notifier(struct notifier_block *notifier, return NOTIFY_OK; } -static void iqs621_als_notifier_unregister(void *context) -{ - struct iqs621_als_private *iqs621_als = context; - struct iio_dev *indio_dev = iqs621_als->indio_dev; - int ret; - - ret = blocking_notifier_chain_unregister(&iqs621_als->iqs62x->nh, - &iqs621_als->notifier); - if (ret) - dev_err(indio_dev->dev.parent, - "Failed to unregister notifier: %d\n", ret); -} - static int iqs621_als_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -563,19 +550,14 @@ static int iqs621_als_probe(struct platform_device *pdev) mutex_init(&iqs621_als->lock); iqs621_als->notifier.notifier_call = iqs621_als_notifier; - ret = blocking_notifier_chain_register(&iqs621_als->iqs62x->nh, - &iqs621_als->notifier); + ret = devm_blocking_notifier_chain_register(&pdev->dev, + &iqs621_als->iqs62x->nh, + &iqs621_als->notifier); if (ret) { dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); return ret; } - ret = devm_add_action_or_reset(&pdev->dev, - iqs621_als_notifier_unregister, - iqs621_als); - if (ret) - return ret; - return devm_iio_device_register(&pdev->dev, indio_dev); } diff --git a/drivers/iio/light/isl29018.c b/drivers/iio/light/isl29018.c index 759cb71ed1c5..7f7a563df4d9 100644 --- a/drivers/iio/light/isl29018.c +++ b/drivers/iio/light/isl29018.c @@ -10,6 +10,7 @@ #include <linux/i2c.h> #include <linux/err.h> +#include <linux/kstrtox.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/delay.h> @@ -339,9 +340,11 @@ static ssize_t proximity_on_chip_ambient_infrared_suppression_store struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct isl29018_chip *chip = iio_priv(indio_dev); int val; + int ret; - if (kstrtoint(buf, 10, &val)) - return -EINVAL; + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; if (!(val == 0 || val == 1)) return -EINVAL; diff --git a/drivers/iio/light/isl29028.c b/drivers/iio/light/isl29028.c index 33deb1726689..e481ac908fc1 100644 --- a/drivers/iio/light/isl29028.c +++ b/drivers/iio/light/isl29028.c @@ -342,8 +342,9 @@ static int isl29028_write_raw(struct iio_dev *indio_dev, struct device *dev = regmap_get_device(chip->regmap); int ret; - ret = pm_runtime_resume_and_get(dev); - if (ret < 0) + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) return ret; mutex_lock(&chip->lock); @@ -392,14 +393,7 @@ static int isl29028_write_raw(struct iio_dev *indio_dev, mutex_unlock(&chip->lock); - if (ret < 0) - return ret; - - ret = pm_runtime_put_autosuspend(dev); - if (ret < 0) - return ret; - - return 0; + return ret; } static int isl29028_read_raw(struct iio_dev *indio_dev, @@ -408,10 +402,11 @@ static int isl29028_read_raw(struct iio_dev *indio_dev, { struct isl29028_chip *chip = iio_priv(indio_dev); struct device *dev = regmap_get_device(chip->regmap); - int ret, pm_ret; + int ret; - ret = pm_runtime_resume_and_get(dev); - if (ret < 0) + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) return ret; mutex_lock(&chip->lock); @@ -461,18 +456,6 @@ static int isl29028_read_raw(struct iio_dev *indio_dev, mutex_unlock(&chip->lock); - if (ret < 0) - return ret; - - /** - * Preserve the ret variable if the call to - * pm_runtime_put_autosuspend() is successful so the reading - * (if applicable) is returned to user space. - */ - pm_ret = pm_runtime_put_autosuspend(dev); - if (pm_ret < 0) - return pm_ret; - return ret; } diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c index 99f0b903018c..5db9591a85b9 100644 --- a/drivers/iio/light/lm3533-als.c +++ b/drivers/iio/light/lm3533-als.c @@ -13,6 +13,7 @@ #include <linux/io.h> #include <linux/iio/events.h> #include <linux/iio/iio.h> +#include <linux/kstrtox.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/mfd/core.h> @@ -434,8 +435,9 @@ static ssize_t store_thresh_either_en(struct device *dev, if (!als->irq) return -EBUSY; - if (kstrtoul(buf, 0, &enable)) - return -EINVAL; + ret = kstrtoul(buf, 0, &enable); + if (ret) + return ret; int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); @@ -542,8 +544,9 @@ static ssize_t store_als_attr(struct device *dev, u8 val; int ret; - if (kstrtou8(buf, 0, &val)) - return -EINVAL; + ret = kstrtou8(buf, 0, &val); + if (ret) + return ret; switch (als_attr->type) { case LM3533_ATTR_TYPE_TARGET: diff --git a/drivers/iio/light/ltr390.c b/drivers/iio/light/ltr390.c index bc031f2c3141..f1bb066248e6 100644 --- a/drivers/iio/light/ltr390.c +++ b/drivers/iio/light/ltr390.c @@ -10,12 +10,6 @@ * * Datasheet: * https://optoelectronics.liteon.com/upload/download/DS86-2015-0004/LTR-390UV_Final_%20DS_V1%201.pdf - * - * TODO: - * - Support for configurable gain and resolution - * - Sensor suspend/resume support - * - Add support for reading the ALS - * - Interrupt support */ #include <linux/bitfield.h> diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c index fa0d16890c8b..302be2fc314c 100644 --- a/drivers/iio/light/ltr501.c +++ b/drivers/iio/light/ltr501.c @@ -9,6 +9,7 @@ * TODO: IR LED characteristics */ +#include <linux/array_size.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/err.h> @@ -90,10 +91,11 @@ struct ltr501_samp_table { #define LTR501_RESERVED_GAIN -1 enum { - ltr501 = 0, - ltr559, ltr301, ltr303, + ltr329, + ltr501, + ltr559, }; struct ltr501_gain { @@ -140,6 +142,7 @@ struct ltr501_chip_info { u8 als_mode_active; u8 als_gain_mask; u8 als_gain_shift; + bool no_irq_support; struct iio_chan_spec const *channels; const int no_channels; const struct iio_info *info; @@ -178,6 +181,11 @@ static const struct ltr501_samp_table ltr501_ps_samp_table[] = { {500000, 2000000} }; +static bool ltr501_has_irq_support(const struct ltr501_chip_info *info) +{ + return !info->no_irq_support; +} + static int ltr501_match_samp_freq(const struct ltr501_samp_table *tab, int len, int val, int val2) { @@ -478,7 +486,7 @@ static int ltr501_write_intr_prst(struct ltr501_data *data, if (ret < 0) return ret; - /* period should be atleast equal to sampling period */ + /* period should be at least equal to sampling period */ if (period < samp_period) return -EINVAL; @@ -498,7 +506,7 @@ static int ltr501_write_intr_prst(struct ltr501_data *data, if (ret < 0) return ret; - /* period should be atleast equal to rate */ + /* period should be at least equal to rate */ if (period < samp_period) return -EINVAL; @@ -821,6 +829,9 @@ static int __ltr501_write_raw(struct iio_dev *indio_dev, if (ret < 0) return ret; + if (!ltr501_has_irq_support(info)) + return ret; + /* update persistence count when changing frequency */ ret = ltr501_write_intr_prst(data, chan->type, 0, data->als_period); @@ -840,6 +851,9 @@ static int __ltr501_write_raw(struct iio_dev *indio_dev, if (ret < 0) return ret; + if (!ltr501_has_irq_support(info)) + return ret; + /* update persistence count when changing frequency */ ret = ltr501_write_intr_prst(data, chan->type, 0, data->ps_period); @@ -1205,6 +1219,42 @@ static const struct iio_info ltr301_info = { }; static const struct ltr501_chip_info ltr501_chip_info_tbl[] = { + [ltr301] = { + .partid = 0x08, + .als_gain = ltr501_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr501_als_gain_tbl), + .als_mode_active = BIT(0) | BIT(1), + .als_gain_mask = BIT(3), + .als_gain_shift = 3, + .info = <r301_info, + .info_no_irq = <r301_info_no_irq, + .channels = ltr301_channels, + .no_channels = ARRAY_SIZE(ltr301_channels), + }, + [ltr303] = { + .partid = 0x0A, + .als_gain = ltr559_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr559_als_gain_tbl), + .als_mode_active = BIT(0), + .als_gain_mask = BIT(2) | BIT(3) | BIT(4), + .als_gain_shift = 2, + .info = <r301_info, + .info_no_irq = <r301_info_no_irq, + .channels = ltr301_channels, + .no_channels = ARRAY_SIZE(ltr301_channels), + }, + [ltr329] = { + .partid = 0x0A, + .als_gain = ltr559_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr559_als_gain_tbl), + .als_mode_active = BIT(0), + .als_gain_mask = BIT(2) | BIT(3) | BIT(4), + .als_gain_shift = 2, + .no_irq_support = true, + .info_no_irq = <r301_info_no_irq, + .channels = ltr301_channels, + .no_channels = ARRAY_SIZE(ltr301_channels), + }, [ltr501] = { .partid = 0x08, .als_gain = ltr501_als_gain_tbl, @@ -1233,30 +1283,6 @@ static const struct ltr501_chip_info ltr501_chip_info_tbl[] = { .channels = ltr501_channels, .no_channels = ARRAY_SIZE(ltr501_channels), }, - [ltr301] = { - .partid = 0x08, - .als_gain = ltr501_als_gain_tbl, - .als_gain_tbl_size = ARRAY_SIZE(ltr501_als_gain_tbl), - .als_mode_active = BIT(0) | BIT(1), - .als_gain_mask = BIT(3), - .als_gain_shift = 3, - .info = <r301_info, - .info_no_irq = <r301_info_no_irq, - .channels = ltr301_channels, - .no_channels = ARRAY_SIZE(ltr301_channels), - }, - [ltr303] = { - .partid = 0x0A, - .als_gain = ltr559_als_gain_tbl, - .als_gain_tbl_size = ARRAY_SIZE(ltr559_als_gain_tbl), - .als_mode_active = BIT(0), - .als_gain_mask = BIT(2) | BIT(3) | BIT(4), - .als_gain_shift = 2, - .info = <r301_info, - .info_no_irq = <r301_info_no_irq, - .channels = ltr301_channels, - .no_channels = ARRAY_SIZE(ltr301_channels), - }, }; static int ltr501_write_contr(struct ltr501_data *data, u8 als_val, u8 ps_val) @@ -1369,13 +1395,15 @@ static int ltr501_init(struct ltr501_data *data) data->ps_contr = status | LTR501_CONTR_ACTIVE; - ret = ltr501_read_intr_prst(data, IIO_INTENSITY, &data->als_period); - if (ret < 0) - return ret; + if (ltr501_has_irq_support(data->chip_info)) { + ret = ltr501_read_intr_prst(data, IIO_INTENSITY, &data->als_period); + if (ret < 0) + return ret; - ret = ltr501_read_intr_prst(data, IIO_PROXIMITY, &data->ps_period); - if (ret < 0) - return ret; + ret = ltr501_read_intr_prst(data, IIO_PROXIMITY, &data->ps_period); + if (ret < 0) + return ret; + } return ltr501_write_contr(data, data->als_contr, data->ps_contr); } @@ -1530,6 +1558,11 @@ static int ltr501_probe(struct i2c_client *client) if (ret < 0) return ret; + if (!ltr501_has_irq_support(data->chip_info) && client->irq > 0) { + client->irq = 0; + dev_warn(&client->dev, "chip doesn't support IRQ"); + } + if (client->irq > 0) { ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, ltr501_interrupt_handler, @@ -1538,7 +1571,7 @@ static int ltr501_probe(struct i2c_client *client) "ltr501_thresh_event", indio_dev); if (ret) - return ret; + goto powerdown_on_error; } else { indio_dev->info = data->chip_info->info_no_irq; } @@ -1597,19 +1630,21 @@ static const struct acpi_device_id ltr_acpi_match[] = { MODULE_DEVICE_TABLE(acpi, ltr_acpi_match); static const struct i2c_device_id ltr501_id[] = { - { .name = "ltr501", .driver_data = ltr501 }, - { .name = "ltr559", .driver_data = ltr559 }, { .name = "ltr301", .driver_data = ltr301 }, { .name = "ltr303", .driver_data = ltr303 }, + { .name = "ltr329", .driver_data = ltr329 }, + { .name = "ltr501", .driver_data = ltr501 }, + { .name = "ltr559", .driver_data = ltr559 }, { } }; MODULE_DEVICE_TABLE(i2c, ltr501_id); static const struct of_device_id ltr501_of_match[] = { - { .compatible = "liteon,ltr501", }, - { .compatible = "liteon,ltr559", }, { .compatible = "liteon,ltr301", }, { .compatible = "liteon,ltr303", }, + { .compatible = "liteon,ltr329", }, + { .compatible = "liteon,ltr501", }, + { .compatible = "liteon,ltr559", }, { } }; MODULE_DEVICE_TABLE(of, ltr501_of_match); diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c index 7c8a1d2b2ed0..0a16e326e939 100644 --- a/drivers/iio/light/stk3310.c +++ b/drivers/iio/light/stk3310.c @@ -40,7 +40,13 @@ #define STK3310_REG_PS_DATA_LSB 0x12 #define STK3310_REG_ALS_DATA_MSB 0x13 #define STK3310_REG_ALS_DATA_LSB 0x14 +#define STK36C61_REG_RED_DATA_MSB 0x15 +#define STK36C61_REG_GREEN_DATA_MSB 0x17 +#define STK36C61_REG_BLUE_DATA_MSB 0x19 +#define STK36C61_REG_CLEAR_DATA_MSB 0x1B +#define STK36C61_REG_CLEAR_DATA_LSB 0x1C #define STK3310_REG_ID 0x3E +#define STK36C61_REG_GAINCTRL 0x4E #define STK3310_MAX_REG 0x80 #define STK3310_STATE_EN_PS BIT(0) @@ -54,6 +60,7 @@ #define STK3311S34_CHIP_ID_VAL 0x1E #define STK3311X_CHIP_ID_VAL 0x12 #define STK3335_CHIP_ID_VAL 0x51 +#define STK36C61_CHIP_ID_VAL 0x95 #define STK3310_PSINT_EN 0x01 #define STK3310_PS_MAX_VAL 0xFFFF @@ -83,6 +90,8 @@ static const struct reg_field stk3310_reg_field_als_gain = REG_FIELD(STK3310_REG_ALSCTRL, 4, 5); static const struct reg_field stk3310_reg_field_ps_gain = REG_FIELD(STK3310_REG_PSCTRL, 4, 5); +static const struct reg_field stk3310_reg_field_clear_gain = + REG_FIELD(STK36C61_REG_GAINCTRL, 4, 5); static const struct reg_field stk3310_reg_field_als_it = REG_FIELD(STK3310_REG_ALSCTRL, 0, 3); static const struct reg_field stk3310_reg_field_ps_it = @@ -102,6 +111,7 @@ static const u8 stk3310_chip_ids[] = { STK3311X_CHIP_ID_VAL, STK3311_CHIP_ID_VAL, STK3335_CHIP_ID_VAL, + STK36C61_CHIP_ID_VAL, }; /* Estimate maximum proximity values with regard to measurement scale. */ @@ -132,6 +142,7 @@ struct stk3310_data { struct regmap_field *reg_state; struct regmap_field *reg_als_gain; struct regmap_field *reg_ps_gain; + struct regmap_field *reg_clear_gain; struct regmap_field *reg_als_it; struct regmap_field *reg_ps_it; struct regmap_field *reg_int_ps; @@ -181,24 +192,78 @@ static const struct iio_chan_spec_ext_info stk3310_ext_info[] = { { } }; +#define STK3310_LIGHT_CHANNEL { \ + .type = IIO_LIGHT, \ + .address = STK3310_REG_ALS_DATA_MSB, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ +} + +#define STK3310_PROXIMITY_CHANNEL { \ + .type = IIO_PROXIMITY, \ + .address = STK3310_REG_PS_DATA_MSB, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .event_spec = stk3310_events, \ + .num_event_specs = ARRAY_SIZE(stk3310_events), \ + .ext_info = stk3310_ext_info, \ +} + +#define STK36C61_INTENSITY_CHANNEL(_mod, _reg) { \ + .type = IIO_INTENSITY, \ + .address = _reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_LIGHT_##_mod, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ +} + static const struct iio_chan_spec stk3310_channels[] = { - { - .type = IIO_LIGHT, - .info_mask_separate = - BIT(IIO_CHAN_INFO_RAW) | - BIT(IIO_CHAN_INFO_SCALE) | - BIT(IIO_CHAN_INFO_INT_TIME), - }, - { - .type = IIO_PROXIMITY, - .info_mask_separate = - BIT(IIO_CHAN_INFO_RAW) | - BIT(IIO_CHAN_INFO_SCALE) | - BIT(IIO_CHAN_INFO_INT_TIME), - .event_spec = stk3310_events, - .num_event_specs = ARRAY_SIZE(stk3310_events), - .ext_info = stk3310_ext_info, - } + STK3310_LIGHT_CHANNEL, + STK3310_PROXIMITY_CHANNEL, +}; + +static const struct iio_chan_spec stk36c61_channels[] = { + STK3310_LIGHT_CHANNEL, + STK3310_PROXIMITY_CHANNEL, + STK36C61_INTENSITY_CHANNEL(RED, STK36C61_REG_RED_DATA_MSB), + STK36C61_INTENSITY_CHANNEL(GREEN, STK36C61_REG_GREEN_DATA_MSB), + STK36C61_INTENSITY_CHANNEL(BLUE, STK36C61_REG_BLUE_DATA_MSB), + STK36C61_INTENSITY_CHANNEL(CLEAR, STK36C61_REG_CLEAR_DATA_MSB), +}; + +/** + * struct stk3310_chip_info - chip-specific data + * @name: device name reported to the IIO core + * @channels: channel specification + * @num_channels: number of channels + */ +struct stk3310_chip_info { + const char *name; + const struct iio_chan_spec *channels __counted_by_ptr(num_channels); + unsigned int num_channels; +}; + +static const struct stk3310_chip_info stk3310_chip_info = { + .name = STK3310_DRIVER_NAME, + .channels = stk3310_channels, + .num_channels = ARRAY_SIZE(stk3310_channels), +}; + +static const struct stk3310_chip_info stk36c61_chip_info = { + .name = "stk36c61", + .channels = stk36c61_channels, + .num_channels = ARRAY_SIZE(stk36c61_channels), }; static IIO_CONST_ATTR(in_illuminance_scale_available, STK3310_SCALE_AVAILABLE); @@ -370,25 +435,21 @@ static int stk3310_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { - u8 reg; __be16 buf; int ret; unsigned int index; struct stk3310_data *data = iio_priv(indio_dev); struct i2c_client *client = data->client; + struct regmap *map = data->regmap; - if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY) + if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY && + chan->type != IIO_INTENSITY) return -EINVAL; switch (mask) { case IIO_CHAN_INFO_RAW: - if (chan->type == IIO_LIGHT) - reg = STK3310_REG_ALS_DATA_MSB; - else - reg = STK3310_REG_PS_DATA_MSB; - mutex_lock(&data->lock); - ret = regmap_bulk_read(data->regmap, reg, &buf, sizeof(buf)); + ret = regmap_bulk_read(map, chan->address, &buf, sizeof(buf)); if (ret < 0) { dev_err(&client->dev, "register read failed\n"); mutex_unlock(&data->lock); @@ -398,10 +459,10 @@ static int stk3310_read_raw(struct iio_dev *indio_dev, mutex_unlock(&data->lock); return IIO_VAL_INT; case IIO_CHAN_INFO_INT_TIME: - if (chan->type == IIO_LIGHT) - ret = regmap_field_read(data->reg_als_it, &index); - else + if (chan->type == IIO_PROXIMITY) ret = regmap_field_read(data->reg_ps_it, &index); + else + ret = regmap_field_read(data->reg_als_it, &index); if (ret < 0) return ret; @@ -409,10 +470,12 @@ static int stk3310_read_raw(struct iio_dev *indio_dev, *val2 = stk3310_it_table[index][1]; return IIO_VAL_INT_PLUS_MICRO; case IIO_CHAN_INFO_SCALE: - if (chan->type == IIO_LIGHT) - ret = regmap_field_read(data->reg_als_gain, &index); - else + if (chan->type == IIO_PROXIMITY) ret = regmap_field_read(data->reg_ps_gain, &index); + else if (chan->channel2 == IIO_MOD_LIGHT_CLEAR) + ret = regmap_field_read(data->reg_clear_gain, &index); + else + ret = regmap_field_read(data->reg_als_gain, &index); if (ret < 0) return ret; @@ -432,7 +495,8 @@ static int stk3310_write_raw(struct iio_dev *indio_dev, int index; struct stk3310_data *data = iio_priv(indio_dev); - if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY) + if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY && + chan->type != IIO_INTENSITY) return -EINVAL; switch (mask) { @@ -443,10 +507,10 @@ static int stk3310_write_raw(struct iio_dev *indio_dev, if (index < 0) return -EINVAL; mutex_lock(&data->lock); - if (chan->type == IIO_LIGHT) - ret = regmap_field_write(data->reg_als_it, index); - else + if (chan->type == IIO_PROXIMITY) ret = regmap_field_write(data->reg_ps_it, index); + else + ret = regmap_field_write(data->reg_als_it, index); if (ret < 0) dev_err(&data->client->dev, "sensor configuration failed\n"); @@ -460,10 +524,12 @@ static int stk3310_write_raw(struct iio_dev *indio_dev, if (index < 0) return -EINVAL; mutex_lock(&data->lock); - if (chan->type == IIO_LIGHT) - ret = regmap_field_write(data->reg_als_gain, index); - else + if (chan->type == IIO_PROXIMITY) ret = regmap_field_write(data->reg_ps_gain, index); + else if (chan->channel2 == IIO_MOD_LIGHT_CLEAR) + ret = regmap_field_write(data->reg_clear_gain, index); + else + ret = regmap_field_write(data->reg_als_gain, index); if (ret < 0) dev_err(&data->client->dev, "sensor configuration failed\n"); @@ -474,9 +540,31 @@ static int stk3310_write_raw(struct iio_dev *indio_dev, return -EINVAL; } +static int stk3310_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)stk3310_scale_table; + *length = 2 * ARRAY_SIZE(stk3310_scale_table); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_INT_TIME: + *vals = (const int *)stk3310_it_table; + *length = 2 * ARRAY_SIZE(stk3310_it_table); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + static const struct iio_info stk3310_info = { .read_raw = stk3310_read_raw, .write_raw = stk3310_write_raw, + .read_avail = stk3310_read_avail, .attrs = &stk3310_attribute_group, .read_event_value = stk3310_read_event, .write_event_value = stk3310_write_event, @@ -548,6 +636,7 @@ static bool stk3310_is_volatile_reg(struct device *dev, unsigned int reg) switch (reg) { case STK3310_REG_ALS_DATA_MSB: case STK3310_REG_ALS_DATA_LSB: + case STK36C61_REG_RED_DATA_MSB ... STK36C61_REG_CLEAR_DATA_LSB: case STK3310_REG_PS_DATA_LSB: case STK3310_REG_PS_DATA_MSB: case STK3310_REG_FLAG: @@ -582,6 +671,7 @@ static int stk3310_regmap_init(struct stk3310_data *data) STK3310_REGFIELD(state); STK3310_REGFIELD(als_gain); STK3310_REGFIELD(ps_gain); + STK3310_REGFIELD(clear_gain); STK3310_REGFIELD(als_it); STK3310_REGFIELD(ps_it); STK3310_REGFIELD(int_ps); @@ -635,10 +725,16 @@ out: static int stk3310_probe(struct i2c_client *client) { + const struct stk3310_chip_info *chip_info; + struct device *dev = &client->dev; int ret; struct iio_dev *indio_dev; struct stk3310_data *data; + chip_info = i2c_get_match_data(client); + if (!chip_info) + return dev_err_probe(dev, -ENODATA, "missing driver data\n"); + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; @@ -657,10 +753,10 @@ static int stk3310_probe(struct i2c_client *client) return ret; indio_dev->info = &stk3310_info; - indio_dev->name = STK3310_DRIVER_NAME; + indio_dev->name = chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = stk3310_channels; - indio_dev->num_channels = ARRAY_SIZE(stk3310_channels); + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = chip_info->num_channels; ret = stk3310_init(indio_dev); if (ret < 0) @@ -766,28 +862,29 @@ static DEFINE_SIMPLE_DEV_PM_OPS(stk3310_pm_ops, stk3310_suspend, stk3310_resume); static const struct i2c_device_id stk3310_i2c_id[] = { - { .name = "STK3013" }, - { .name = "STK3310" }, - { .name = "STK3311" }, - { .name = "STK3335" }, + { .name = "stk3013", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, + { .name = "stk3310", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, + { .name = "stk3311", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, + { .name = "stk3335", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, + { .name = "stk36c61", .driver_data = (kernel_ulong_t)&stk36c61_chip_info }, { } }; MODULE_DEVICE_TABLE(i2c, stk3310_i2c_id); static const struct acpi_device_id stk3310_acpi_id[] = { - {"STK3013", 0}, - {"STK3310", 0}, - {"STK3311", 0}, + { .id = "STK3013", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, + { .id = "STK3310", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, + { .id = "STK3311", .driver_data = (kernel_ulong_t)&stk3310_chip_info }, { } }; - MODULE_DEVICE_TABLE(acpi, stk3310_acpi_id); static const struct of_device_id stk3310_of_match[] = { - { .compatible = "sensortek,stk3013", }, - { .compatible = "sensortek,stk3310", }, - { .compatible = "sensortek,stk3311", }, - { .compatible = "sensortek,stk3335", }, + { .compatible = "sensortek,stk3013", .data = &stk3310_chip_info }, + { .compatible = "sensortek,stk3310", .data = &stk3310_chip_info }, + { .compatible = "sensortek,stk3311", .data = &stk3310_chip_info }, + { .compatible = "sensortek,stk3335", .data = &stk3310_chip_info }, + { .compatible = "sensortek,stk36c61", .data = &stk36c61_chip_info }, { } }; MODULE_DEVICE_TABLE(of, stk3310_of_match); diff --git a/drivers/iio/light/tsl2583.c b/drivers/iio/light/tsl2583.c index 53fd423aa7ae..2d041b7530e6 100644 --- a/drivers/iio/light/tsl2583.c +++ b/drivers/iio/light/tsl2583.c @@ -7,10 +7,11 @@ * Copyright (c) 2016-2017 Brian Masney <masneyb@onstation.org> */ -#include <linux/kernel.h> #include <linux/i2c.h> #include <linux/errno.h> #include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/kstrtox.h> #include <linux/string.h> #include <linux/mutex.h> #include <linux/unistd.h> @@ -483,9 +484,12 @@ static ssize_t in_illuminance_input_target_store(struct device *dev, { struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct tsl2583_chip *chip = iio_priv(indio_dev); - int value; + int value, ret; - if (kstrtoint(buf, 0, &value) || !value) + ret = kstrtoint(buf, 0, &value); + if (ret) + return ret; + if (!value) return -EINVAL; mutex_lock(&chip->als_mutex); @@ -503,7 +507,10 @@ static ssize_t in_illuminance_calibrate_store(struct device *dev, struct tsl2583_chip *chip = iio_priv(indio_dev); int value, ret; - if (kstrtoint(buf, 0, &value) || value != 1) + ret = kstrtoint(buf, 0, &value); + if (ret) + return ret; + if (value != 1) return -EINVAL; mutex_lock(&chip->als_mutex); diff --git a/drivers/iio/light/tsl2772.c b/drivers/iio/light/tsl2772.c index 4486a1d9d84d..c8a7afc65c1f 100644 --- a/drivers/iio/light/tsl2772.c +++ b/drivers/iio/light/tsl2772.c @@ -13,6 +13,7 @@ #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/kernel.h> +#include <linux/kstrtox.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/property.h> @@ -951,8 +952,9 @@ static ssize_t in_illuminance0_target_input_store(struct device *dev, u16 value; int ret; - if (kstrtou16(buf, 0, &value)) - return -EINVAL; + ret = kstrtou16(buf, 0, &value); + if (ret) + return ret; chip->settings.als_cal_target = value; ret = tsl2772_invoke_change(indio_dev); @@ -970,7 +972,10 @@ static ssize_t in_illuminance0_calibrate_store(struct device *dev, bool value; int ret; - if (kstrtobool(buf, &value) || !value) + ret = kstrtobool(buf, &value); + if (ret) + return ret; + if (!value) return -EINVAL; ret = tsl2772_als_calibrate(indio_dev); @@ -1061,7 +1066,10 @@ static ssize_t in_proximity0_calibrate_store(struct device *dev, bool value; int ret; - if (kstrtobool(buf, &value) || !value) + ret = kstrtobool(buf, &value); + if (ret) + return ret; + if (!value) return -EINVAL; ret = tsl2772_prox_cal(indio_dev); diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c index 336468d59ea7..911f8d1c74d4 100644 --- a/drivers/iio/light/vcnl4000.c +++ b/drivers/iio/light/vcnl4000.c @@ -26,6 +26,7 @@ #include <linux/module.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> +#include <linux/sysfs.h> #include <linux/units.h> #include <linux/iio/buffer.h> @@ -1504,7 +1505,7 @@ static ssize_t vcnl4000_read_near_level(struct iio_dev *indio_dev, { struct vcnl4000_data *data = iio_priv(indio_dev); - return sprintf(buf, "%u\n", data->near_level); + return sysfs_emit(buf, "%u\n", data->near_level); } static irqreturn_t vcnl4010_irq_thread(int irq, void *p) diff --git a/drivers/iio/light/veml3328.c b/drivers/iio/light/veml3328.c index 7ff1753925c4..df36c48204be 100644 --- a/drivers/iio/light/veml3328.c +++ b/drivers/iio/light/veml3328.c @@ -32,6 +32,11 @@ #define VEML3328_REG_DATA_B 0x07 #define VEML3328_REG_DATA_IR 0x08 +#define VEML3328_CONT_IT_50MSECS 0 +#define VEML3328_CONT_IT_100MSECS 1 +#define VEML3328_CONT_IT_200MSECS 2 +#define VEML3328_CONT_IT_400MSECS 3 + #define VEML3328_CONF_IT_MASK GENMASK(5, 4) #define VEML3328_CONF_GAIN_MASK GENMASK(11, 10) @@ -89,21 +94,40 @@ static const struct iio_chan_spec veml3328_channels[] = { * Precomputed scale values (micro units). * Formula for calculation: 0.384 * (50000 / IT_us) * (1 / Gain) * Gain indexes: 0 (x0.5), 1 (x1), 2 (x2), 3 (x4) - * IT indexes: 0 (50ms), 1 (100ms), 2 (200ms), 3 (400ms) */ -static const int veml3328_scale_vals[4][8] = { - { 0, 768000, 0, 384000, 0, 192000, 0, 96000 }, - { 0, 384000, 0, 192000, 0, 96000, 0, 48000 }, - { 0, 192000, 0, 96000, 0, 48000, 0, 24000 }, - { 0, 96000, 0, 48000, 0, 24000, 0, 12000 }, +static const int veml3328_scale_vals[4][4][2] = { + [VEML3328_CONT_IT_50MSECS] = { + { 0, 768000 }, + { 0, 384000 }, + { 0, 192000 }, + { 0, 96000 }, + }, + [VEML3328_CONT_IT_100MSECS] = { + { 0, 384000 }, + { 0, 192000 }, + { 0, 96000 }, + { 0, 48000 }, + }, + [VEML3328_CONT_IT_200MSECS] = { + { 0, 192000 }, + { 0, 96000 }, + { 0, 48000 }, + { 0, 24000 }, + }, + [VEML3328_CONT_IT_400MSECS] = { + { 0, 96000 }, + { 0, 48000 }, + { 0, 24000 }, + { 0, 12000 }, + }, }; /* integration times in microseconds */ static const int veml3328_it_times[][2] = { - { 0, 50 * USEC_PER_MSEC }, - { 0, 100 * USEC_PER_MSEC }, - { 0, 200 * USEC_PER_MSEC }, - { 0, 400 * USEC_PER_MSEC }, + [VEML3328_CONT_IT_50MSECS] = { 0, 50 * USEC_PER_MSEC }, + [VEML3328_CONT_IT_100MSECS] = { 0, 100 * USEC_PER_MSEC }, + [VEML3328_CONT_IT_200MSECS] = { 0, 200 * USEC_PER_MSEC }, + [VEML3328_CONT_IT_400MSECS] = { 0, 400 * USEC_PER_MSEC }, }; static int veml3328_power_down(struct veml3328_data *data) @@ -184,9 +208,8 @@ static int veml3328_read_raw(struct iio_dev *indio_dev, if (it_inx >= ARRAY_SIZE(veml3328_it_times) || gain_inx >= 4) return -EINVAL; - /* Stride by 2 through the flattened array to match (val, val2) */ - *val = veml3328_scale_vals[it_inx][gain_inx * 2]; - *val2 = veml3328_scale_vals[it_inx][gain_inx * 2 + 1]; + *val = veml3328_scale_vals[it_inx][gain_inx][0]; + *val2 = veml3328_scale_vals[it_inx][gain_inx][1]; return IIO_VAL_INT_PLUS_MICRO; @@ -282,8 +305,8 @@ static int veml3328_write_raw(struct iio_dev *indio_dev, return -EINVAL; for (i = 0; i < 4; i++) { - if (val == veml3328_scale_vals[it_inx][i * 2] && - val2 == veml3328_scale_vals[it_inx][i * 2 + 1]) + if (val == veml3328_scale_vals[it_inx][i][0] && + val2 == veml3328_scale_vals[it_inx][i][1]) break; } diff --git a/drivers/iio/light/veml6031x00.c b/drivers/iio/light/veml6031x00.c new file mode 100644 index 000000000000..f6308498e422 --- /dev/null +++ b/drivers/iio/light/veml6031x00.c @@ -0,0 +1,1387 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VEML6031X00 Ambient Light Sensor + * + * Copyright (c) 2026, Javier Carrasco <javier.carrasco.cruz@gmail.com> + */ + +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/limits.h> +#include <linux/lockdep.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> + +#include <asm/byteorder.h> + +#include <linux/iio/iio.h> +#include <linux/iio/events.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/iio-gts-helper.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> + +/* Device registers */ +#define VEML6031X00_REG_CONF0 0x00 +#define VEML6031X00_REG_CONF1 0x01 +#define VEML6031X00_REG_WH_L 0x04 +#define VEML6031X00_REG_WH_H 0x05 +#define VEML6031X00_REG_WL_L 0x06 +#define VEML6031X00_REG_WL_H 0x07 +#define VEML6031X00_REG_ALS_L 0x10 +#define VEML6031X00_REG_ALS_H 0x11 +#define VEML6031X00_REG_IR_L 0x12 +#define VEML6031X00_REG_IR_H 0x13 +#define VEML6031X00_REG_ID_L 0x14 +#define VEML6031X00_REG_ID_H 0x15 +#define VEML6031X00_REG_INT 0x17 + +/* Bit masks for specific functionality */ +#define VEML6031X00_CONF0_SD BIT(0) +#define VEML6031X00_CONF0_AF_TRIG BIT(2) +#define VEML6031X00_CONF0_AF BIT(3) +#define VEML6031X00_CONF1_ALS_CAL BIT(0) +#define VEML6031X00_CONF1_IR_SD BIT(7) +#define VEML6031X00_INT_TH_H BIT(1) +#define VEML6031X00_INT_TH_L BIT(2) +#define VEML6031X00_INT_DRDY BIT(3) +#define VEML6031X00_INT_MASK \ + (VEML6031X00_INT_TH_L | VEML6031X00_INT_TH_H | VEML6031X00_INT_DRDY) + +#define VEML6031X00_GAIN_SEL(pd_div4, gain) (((pd_div4) << 2) | (gain)) + +enum veml6031x00_scan { + VEML6031X00_SCAN_ALS, + VEML6031X00_SCAN_IR, + VEML6031X00_SCAN_TIMESTAMP, +}; + +struct veml6031x00_rf { + struct regmap_field *gain; + struct regmap_field *int_en; + struct regmap_field *it; + struct regmap_field *pd_div4; + struct regmap_field *pers; +}; + +struct veml6031x00_chip { + const char *name; + const unsigned int part_id; +}; + +struct veml6031x00_data { + struct iio_gts gts; + struct regmap *regmap; + struct iio_trigger *trig; + struct veml6031x00_rf rf; + const struct veml6031x00_chip *chip; + /* + * Serialize access to scale register fields scattered across multiple + * registers (rf.gain, rf.pd_div4, rf.it) to read and write them as a + * consistent set. + */ + struct mutex scale_lock; + /* + * Serialize access to irq enable/disable by events and trigger + * (shared line). + */ + struct mutex irq_lock; + unsigned int interrupt_users; + bool ev_en; + bool trig_en; +}; + +static const struct iio_itime_sel_mul veml6031x00_it_sel[] = { + GAIN_SCALE_ITIME_US(3125, 0, 1), + GAIN_SCALE_ITIME_US(6250, 1, 2), + GAIN_SCALE_ITIME_US(12500, 2, 4), + GAIN_SCALE_ITIME_US(25000, 3, 8), + GAIN_SCALE_ITIME_US(50000, 4, 16), + GAIN_SCALE_ITIME_US(100000, 5, 32), + GAIN_SCALE_ITIME_US(200000, 6, 64), + GAIN_SCALE_ITIME_US(400000, 7, 128), +}; + +/* + * The gain selector encodes (PD_D4 << 2) | GAIN to identify each gain setting. + * Gains are multiplied by 8 to work with integers. The values in the iio-gts + * tables don't need corrections because the maximum value of the scale refers + * to GAIN = x1, and the rest of the values are obtained from the resulting + * linear function. + * TODO: add support for GAIN_0_165 and GAIN_0_660 + */ +#define VEML6031X00_SEL_GAIN_0_125 0x07 +#define VEML6031X00_SEL_GAIN_0_250 0x04 +#define VEML6031X00_SEL_GAIN_0_500 0x03 +#define VEML6031X00_SEL_GAIN_1_000 0x00 +#define VEML6031X00_SEL_GAIN_2_000 0x01 +static const struct iio_gain_sel_pair veml6031x00_gain_sel[] = { + GAIN_SCALE_GAIN(1, VEML6031X00_SEL_GAIN_0_125), + GAIN_SCALE_GAIN(2, VEML6031X00_SEL_GAIN_0_250), + GAIN_SCALE_GAIN(4, VEML6031X00_SEL_GAIN_0_500), + GAIN_SCALE_GAIN(8, VEML6031X00_SEL_GAIN_1_000), + GAIN_SCALE_GAIN(16, VEML6031X00_SEL_GAIN_2_000), +}; + +static IIO_CONST_ATTR(in_illuminance_thresh_either_period_available, "1 2 4 8"); + +static struct attribute *veml6031x00_event_attributes[] = { + &iio_const_attr_in_illuminance_thresh_either_period_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group veml6031x00_event_attr_group = { + .attrs = veml6031x00_event_attributes, +}; + +/* + * The shutdown bits (SD and ALS_IR_SD) are in different registers, and both + * must be updated when changing the device power state. + */ +static int veml6031x00_set_power(struct veml6031x00_data *data, bool state) +{ + int ret; + + ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0, + VEML6031X00_CONF0_SD, !state); + if (ret) + return ret; + + return regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF1, + VEML6031X00_CONF1_IR_SD, !state); +} + +static void veml6031x00_als_shutdown_action(void *data) +{ + struct veml6031x00_data *priv = data; + struct device *dev = regmap_get_device(priv->regmap); + int ret; + + ret = veml6031x00_set_power(priv, false); + if (ret) + dev_warn(dev, "Failed to shut down device: %d\n", ret); +} + +static const struct iio_event_spec veml6031x00_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec veml6031x00_channels[] = { + { + .type = IIO_LIGHT, + .address = VEML6031X00_REG_ALS_L, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6031X00_SCAN_ALS, + .scan_type = { + .format = IIO_SCAN_FORMAT_UNSIGNED_INT, + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6031X00_REG_IR_L, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = VEML6031X00_SCAN_IR, + .scan_type = { + .format = IIO_SCAN_FORMAT_UNSIGNED_INT, + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(VEML6031X00_SCAN_TIMESTAMP), +}; + +static const struct iio_chan_spec veml6031x00_channels_irq[] = { + { + .type = IIO_LIGHT, + .address = VEML6031X00_REG_ALS_L, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = veml6031x00_event_spec, + .num_event_specs = ARRAY_SIZE(veml6031x00_event_spec), + .scan_index = VEML6031X00_SCAN_ALS, + .scan_type = { + .format = IIO_SCAN_FORMAT_UNSIGNED_INT, + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6031X00_REG_IR_L, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = VEML6031X00_SCAN_IR, + .scan_type = { + .format = IIO_SCAN_FORMAT_UNSIGNED_INT, + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(VEML6031X00_SCAN_TIMESTAMP), +}; + +static const struct regmap_range veml6031x00_readable_ranges[] = { + regmap_reg_range(VEML6031X00_REG_CONF0, VEML6031X00_REG_CONF1), + regmap_reg_range(VEML6031X00_REG_WH_L, VEML6031X00_REG_WL_H), + regmap_reg_range(VEML6031X00_REG_ALS_L, VEML6031X00_REG_ID_H), + regmap_reg_range(VEML6031X00_REG_INT, VEML6031X00_REG_INT), +}; + +static const struct regmap_access_table veml6031x00_readable_table = { + .yes_ranges = veml6031x00_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6031x00_readable_ranges), +}; + +static const struct regmap_range veml6031x00_writable_ranges[] = { + regmap_reg_range(VEML6031X00_REG_CONF0, VEML6031X00_REG_CONF1), + regmap_reg_range(VEML6031X00_REG_WH_L, VEML6031X00_REG_WL_H), +}; + +static const struct regmap_access_table veml6031x00_writable_table = { + .yes_ranges = veml6031x00_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6031x00_writable_ranges), +}; + +static const struct regmap_range veml6031x00_volatile_ranges[] = { + /* AF_TRIG in CONF0 is volatile */ + regmap_reg_range(VEML6031X00_REG_CONF0, VEML6031X00_REG_CONF0), + regmap_reg_range(VEML6031X00_REG_ALS_L, VEML6031X00_REG_IR_H), + regmap_reg_range(VEML6031X00_REG_INT, VEML6031X00_REG_INT), +}; + +static const struct regmap_access_table veml6031x00_volatile_table = { + .yes_ranges = veml6031x00_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6031x00_volatile_ranges), +}; + +static const struct regmap_range veml6031x00_precious_ranges[] = { + regmap_reg_range(VEML6031X00_REG_INT, VEML6031X00_REG_INT), +}; + +static const struct regmap_access_table veml6031x00_precious_table = { + .yes_ranges = veml6031x00_precious_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6031x00_precious_ranges), +}; + +static const struct regmap_config veml6031x00_regmap_config = { + .name = "veml6031x00_regmap", + .reg_bits = 8, + .val_bits = 8, + .rd_table = &veml6031x00_readable_table, + .wr_table = &veml6031x00_writable_table, + .volatile_table = &veml6031x00_volatile_table, + .precious_table = &veml6031x00_precious_table, + .max_register = VEML6031X00_REG_INT, + .cache_type = REGCACHE_MAPLE, +}; + +static const struct reg_field veml6031x00_rf_int_en = + REG_FIELD(VEML6031X00_REG_CONF0, 1, 1); + +static const struct reg_field veml6031x00_rf_it = + REG_FIELD(VEML6031X00_REG_CONF0, 4, 6); + +static const struct reg_field veml6031x00_rf_pers = + REG_FIELD(VEML6031X00_REG_CONF1, 1, 2); + +static const struct reg_field veml6031x00_rf_gain = + REG_FIELD(VEML6031X00_REG_CONF1, 3, 4); + +static const struct reg_field veml6031x00_rf_pd_div4 = + REG_FIELD(VEML6031X00_REG_CONF1, 6, 6); + +static int veml6031x00_regfield_init(struct veml6031x00_data *data) +{ + struct regmap *map = data->regmap; + struct device *dev = regmap_get_device(map); + struct regmap_field *rm_field; + struct veml6031x00_rf *rf = &data->rf; + + rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_gain); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->gain = rm_field; + + rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_it); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->it = rm_field; + + rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_int_en); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->int_en = rm_field; + + rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_pd_div4); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->pd_div4 = rm_field; + + rm_field = devm_regmap_field_alloc(dev, map, veml6031x00_rf_pers); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->pers = rm_field; + + return 0; +} + +static int __veml6031x00_get_it(struct veml6031x00_data *data, int *val2) +{ + unsigned int it_idx; + int ret; + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = iio_gts_find_int_time_by_sel(&data->gts, it_idx); + if (ret < 0) + return ret; + + *val2 = ret; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6031x00_get_it(struct veml6031x00_data *data, int *val2) +{ + guard(mutex)(&data->scale_lock); + + return __veml6031x00_get_it(data, val2); +} + +static int veml6031x00_write_gain(struct veml6031x00_data *data, unsigned int gain) +{ + int ret; + + ret = regmap_field_write(data->rf.pd_div4, gain >> 2); + if (ret) + return ret; + + return regmap_field_write(data->rf.gain, gain & 0x03); +} + +static int veml6031x00_set_it(struct iio_dev *iio, int val, int val2) +{ + struct veml6031x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret, gain_sel, new_gain, prev_gain, prev_it; + unsigned int gain_reg, it_idx, pd_div4; + struct iio_gts *gts = &data->gts; + bool gain_in_range; + + if (val || !iio_gts_valid_time(gts, val2)) + return -EINVAL; + + guard(mutex)(&data->scale_lock); + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = regmap_field_read(data->rf.gain, &gain_reg); + if (ret) + return ret; + + ret = regmap_field_read(data->rf.pd_div4, &pd_div4); + if (ret) + return ret; + + prev_it = iio_gts_find_int_time_by_sel(gts, it_idx); + if (prev_it < 0) + return prev_it; + + if (prev_it == val2) + return 0; + + gain_sel = VEML6031X00_GAIN_SEL(pd_div4, gain_reg); + prev_gain = iio_gts_find_gain_by_sel(gts, gain_sel); + if (prev_gain < 0) + return prev_gain; + + ret = iio_gts_find_new_gain_by_gain_time_min(gts, prev_gain, prev_it, val2, + &new_gain, &gain_in_range); + if (ret) + return ret; + + if (!gain_in_range) + dev_dbg(dev, "Optimal gain out of range\n"); + + ret = iio_gts_find_sel_by_int_time(gts, val2); + if (ret < 0) + return ret; + + ret = regmap_field_write(data->rf.it, ret); + if (ret) + return ret; + + gain_sel = iio_gts_find_sel_by_gain(gts, new_gain); + if (gain_sel < 0) + return gain_sel; + + return veml6031x00_write_gain(data, gain_sel); +} + +static int veml6031x00_read_period(struct iio_dev *iio, int *val) +{ + struct veml6031x00_data *data = iio_priv(iio); + unsigned int regval; + int ret; + + ret = regmap_field_read(data->rf.pers, ®val); + if (ret) + return ret; + + *val = BIT(regval); + + return IIO_VAL_INT; +} + +static int veml6031x00_write_period(struct iio_dev *iio, int val) +{ + struct veml6031x00_data *data = iio_priv(iio); + + if (val < 0 || val > 8 || !is_power_of_2(val)) + return -EINVAL; + + return regmap_field_write(data->rf.pers, ilog2(val)); +} + +static int veml6031x00_set_scale(struct iio_dev *iio, int val, int val2) +{ + struct veml6031x00_data *data = iio_priv(iio); + struct iio_gts *gts = &data->gts; + int gain_sel, it_sel, ret; + + ret = iio_gts_find_gain_time_sel_for_scale(gts, val, val2, &gain_sel, &it_sel); + if (ret) + return ret; + + guard(mutex)(&data->scale_lock); + + ret = regmap_field_write(data->rf.it, it_sel); + if (ret) + return ret; + + return veml6031x00_write_gain(data, gain_sel); +} + +static int veml6031x00_get_scale(struct veml6031x00_data *data, int *val, int *val2) +{ + unsigned int gain_reg, pd_div4, it_reg; + int gain, it, ret, sel; + + scoped_guard(mutex, &data->scale_lock) { + ret = regmap_field_read(data->rf.gain, &gain_reg); + if (ret) + return ret; + + ret = regmap_field_read(data->rf.pd_div4, &pd_div4); + if (ret) + return ret; + + sel = VEML6031X00_GAIN_SEL(pd_div4, gain_reg); + gain = iio_gts_find_gain_by_sel(&data->gts, sel); + if (gain < 0) + return gain; + + ret = regmap_field_read(data->rf.it, &it_reg); + if (ret) + return ret; + } + + it = iio_gts_find_int_time_by_sel(&data->gts, it_reg); + if (it < 0) + return it; + + ret = iio_gts_get_scale(&data->gts, gain, it, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int veml6031x00_read_th(struct iio_dev *iio, enum iio_event_direction dir, + int *val, int *val2) +{ + struct veml6031x00_data *data = iio_priv(iio); + __le16 regval; + int ret; + + if (dir == IIO_EV_DIR_RISING) + ret = regmap_bulk_read(data->regmap, VEML6031X00_REG_WH_L, + ®val, sizeof(regval)); + else + ret = regmap_bulk_read(data->regmap, VEML6031X00_REG_WL_L, + ®val, sizeof(regval)); + if (ret) + return ret; + + *val = le16_to_cpu(regval); + + return IIO_VAL_INT; +} + +static int veml6031x00_write_th(struct iio_dev *iio, enum iio_event_direction dir, + int val, int val2) +{ + struct veml6031x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + __le16 regval; + int ret; + + if (val < 0 || val > U16_MAX || val2) + return -EINVAL; + + regval = cpu_to_le16(val); + + if (dir == IIO_EV_DIR_RISING) { + ret = regmap_bulk_write(data->regmap, VEML6031X00_REG_WH_L, + ®val, sizeof(regval)); + if (ret) + dev_dbg(dev, "Failed to set high threshold %d\n", ret); + } else { + ret = regmap_bulk_write(data->regmap, VEML6031X00_REG_WL_L, + ®val, sizeof(regval)); + if (ret) + dev_dbg(dev, "Failed to set low threshold %d\n", ret); + } + + return ret; +} + +static int veml6031x00_single_read(struct iio_dev *iio, enum iio_chan_type type, + int *val) +{ + struct veml6031x00_data *data = iio_priv(iio); + unsigned int addr; + int it_usec, ret; + __le16 regval; + + switch (type) { + case IIO_LIGHT: + addr = VEML6031X00_REG_ALS_L; + break; + case IIO_INTENSITY: + addr = VEML6031X00_REG_IR_L; + break; + default: + return -EINVAL; + } + + IIO_DEV_ACQUIRE_DIRECT_MODE(iio, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + guard(mutex)(&data->scale_lock); + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(regmap_get_device(data->regmap), pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = __veml6031x00_get_it(data, &it_usec); + if (ret < 0) + return ret; + + /* integration time + 10% to ensure completion */ + fsleep(it_usec + (it_usec / 10)); + + ret = regmap_bulk_read(data->regmap, addr, ®val, sizeof(regval)); + if (ret) + return ret; + + *val = le16_to_cpu(regval); + + return IIO_VAL_INT; +} + +static int veml6031x00_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct veml6031x00_data *data = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return veml6031x00_single_read(iio, chan->type, val); + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + return veml6031x00_get_it(data, val2); + case IIO_CHAN_INFO_SCALE: + return veml6031x00_get_scale(data, val, val2); + default: + return -EINVAL; + } +} + +static int veml6031x00_read_avail(struct iio_dev *iio, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml6031x00_data *data = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + default: + return -EINVAL; + } +} + +static int veml6031x00_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + IIO_DEV_ACQUIRE_DIRECT_MODE(iio, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml6031x00_set_it(iio, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml6031x00_set_scale(iio, val, val2); + default: + return -EINVAL; + } +} + +static int veml6031x00_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int veml6031x00_set_interrupt(struct veml6031x00_data *data, bool state) + __must_hold(&data->irq_lock) +{ + int ret; + + lockdep_assert_held(&data->irq_lock); + + if (state) { + data->interrupt_users++; + if (data->interrupt_users > 1) + return 0; + } else { + if (!data->interrupt_users) + return 0; + + data->interrupt_users--; + if (data->interrupt_users > 0) + return 0; + } + + ret = regmap_field_write(data->rf.int_en, state); + if (ret) { + if (state) + data->interrupt_users--; + else + data->interrupt_users++; + } + + return ret; +} + +static int veml6031x00_read_event_val(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + switch (type) { + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_EITHER && info == IIO_EV_INFO_PERIOD) + return veml6031x00_read_period(iio, val); + + return veml6031x00_read_th(iio, dir, val, val2); + default: + return -EINVAL; + } +} + +static int veml6031x00_write_event_val(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + return veml6031x00_write_th(iio, dir, val, val2); + case IIO_EV_INFO_PERIOD: + return veml6031x00_write_period(iio, val); + default: + return -EINVAL; + } +} + +static int veml6031x00_read_event_config(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct veml6031x00_data *data = iio_priv(iio); + + guard(mutex)(&data->irq_lock); + + return data->ev_en; +} + +static int veml6031x00_event_enable(struct veml6031x00_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = veml6031x00_set_interrupt(data, true); + if (ret) { + pm_runtime_put_autosuspend(dev); + return ret; + } + + data->ev_en = true; + + return 0; +} + +static int veml6031x00_event_disable(struct veml6031x00_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = veml6031x00_set_interrupt(data, false); + if (ret) + return ret; + + data->ev_en = false; + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static int veml6031x00_write_event_config(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct veml6031x00_data *data = iio_priv(iio); + + guard(mutex)(&data->irq_lock); + + /* avoid multiple increments/decrements from one source */ + if (state == data->ev_en) + return 0; + + if (state) + return veml6031x00_event_enable(data); + + return veml6031x00_event_disable(data); +} + +static void veml6031x00_disable_event_action(void *arg) +{ + struct veml6031x00_data *data = arg; + struct device *dev = regmap_get_device(data->regmap); + int ret; + + guard(mutex)(&data->irq_lock); + + if (!data->ev_en) + return; + + ret = veml6031x00_set_interrupt(data, false); + if (ret) + dev_err(dev, "Failed to disable events: %d\n", ret); + else + data->ev_en = false; + + pm_runtime_put_sync(dev); +} + +static const struct iio_info veml6031x00_info = { + .read_raw = veml6031x00_read_raw, + .read_avail = veml6031x00_read_avail, + .write_raw = veml6031x00_write_raw, + .write_raw_get_fmt = veml6031x00_write_raw_get_fmt, + .read_event_value = veml6031x00_read_event_val, + .write_event_value = veml6031x00_write_event_val, + .read_event_config = veml6031x00_read_event_config, + .write_event_config = veml6031x00_write_event_config, + .event_attrs = &veml6031x00_event_attr_group, +}; + +static const struct iio_info veml6031x00_info_no_irq = { + .read_raw = veml6031x00_read_raw, + .read_avail = veml6031x00_read_avail, + .write_raw = veml6031x00_write_raw, + .write_raw_get_fmt = veml6031x00_write_raw_get_fmt, +}; + +static irqreturn_t veml6031x00_irq(int irq, void *private) +{ + struct iio_dev *iio = private; + struct veml6031x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + s64 timestamp; + unsigned int regval; + int ret; + bool trigger_poll; + + ret = pm_runtime_get_if_active(dev); + if (ret <= 0) + return IRQ_NONE; + + scoped_guard(mutex, &data->irq_lock) { + ret = regmap_read(data->regmap, VEML6031X00_REG_INT, ®val); + if (ret) { + dev_dbg(dev, "Failed to read interrupt register %d\n", ret); + pm_runtime_put(dev); + return IRQ_NONE; + } + + if (!(regval & VEML6031X00_INT_MASK)) { + pm_runtime_put(dev); + return IRQ_NONE; + } + + timestamp = iio_get_time_ns(iio); + + if (data->ev_en && + (regval & (VEML6031X00_INT_TH_H | VEML6031X00_INT_TH_L))) { + if (regval & VEML6031X00_INT_TH_H) + iio_push_event(iio, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + if (regval & VEML6031X00_INT_TH_L) + iio_push_event(iio, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + } + + trigger_poll = data->trig_en && (regval & VEML6031X00_INT_DRDY); + } + + /* + * iio_trigger_poll_nested() must be called with irq_lock released: + * it runs trig_handler() synchronously in this thread, which calls + * reenable() on completion, and that callback also takes irq_lock. + * iio_pollfunc_store_time() is not called for our own trigger, so the + * timestamp is stored here. + */ + if (trigger_poll) { + iio->pollfunc->timestamp = timestamp; + iio_trigger_poll_nested(data->trig); + } + + pm_runtime_put(dev); + + return IRQ_HANDLED; +} + +static int veml6031x00_buffer_preenable(struct iio_dev *iio) +{ + struct veml6031x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret, it_usec; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = veml6031x00_get_it(data, &it_usec); + if (ret < 0) { + pm_runtime_put_autosuspend(dev); + return ret; + } + + /* + * Wait one integration period + 10% margin so the first triggered + * read does not race with the sensor completing its first conversion + * after power-on. + */ + fsleep(it_usec + (it_usec / 10)); + + return 0; +} + +static int veml6031x00_buffer_postdisable(struct iio_dev *iio) +{ + struct veml6031x00_data *data = iio_priv(iio); + + pm_runtime_put_autosuspend(regmap_get_device(data->regmap)); + + return 0; +} + +static int __veml6031x00_set_trigger_state(struct veml6031x00_data *data, + bool state) + __must_hold(&data->irq_lock) +{ + int ret; + + lockdep_assert_held(&data->irq_lock); + + ret = veml6031x00_set_interrupt(data, state); + if (ret) + return ret; + + /* The AF bit must be updated before updating AF_TRIG */ + ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0, + VEML6031X00_CONF0_AF, state); + if (ret) + goto disable_trigger; + + ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0, + VEML6031X00_CONF0_AF_TRIG, state); + if (ret) { + regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0, + VEML6031X00_CONF0_AF, false); + goto disable_trigger; + } + + data->trig_en = state; + + return 0; + +disable_trigger: + data->trig_en = false; + if (state) + veml6031x00_set_interrupt(data, false); + return ret; +} + +static int veml6031x00_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *iio = iio_trigger_get_drvdata(trig); + struct veml6031x00_data *data = iio_priv(iio); + + guard(mutex)(&data->irq_lock); + + if (state == data->trig_en) + return 0; + + return __veml6031x00_set_trigger_state(data, state); +} + +static const struct iio_buffer_setup_ops veml6031x00_buffer_setup_ops = { + .preenable = veml6031x00_buffer_preenable, + .postdisable = veml6031x00_buffer_postdisable, +}; + +static void veml6031x00_trigger_reenable(struct iio_trigger *trig) +{ + struct iio_dev *iio = iio_trigger_get_drvdata(trig); + struct veml6031x00_data *data = iio_priv(iio); + int ret; + + guard(mutex)(&data->irq_lock); + + if (!data->trig_en) + return; + + ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0, + VEML6031X00_CONF0_AF_TRIG, true); + if (ret) + dev_err(regmap_get_device(data->regmap), + "Failed to reenable trigger: %d\n", ret); +} + +static const struct iio_trigger_ops veml6031x00_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, + .set_trigger_state = veml6031x00_set_trigger_state, + .reenable = veml6031x00_trigger_reenable, +}; + +static irqreturn_t veml6031x00_trig_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct veml6031x00_data *data = iio_priv(iio); + struct regmap *map = data->regmap; + IIO_DECLARE_BUFFER_WITH_TS(__le16, scan, 2) = { }; + unsigned int i; + int ch, ret; + + if (test_bit(VEML6031X00_SCAN_ALS, iio->active_scan_mask) && + test_bit(VEML6031X00_SCAN_IR, iio->active_scan_mask)) { + ret = regmap_bulk_read(map, VEML6031X00_REG_ALS_L, + scan, 2 * sizeof(*scan)); + if (ret) + goto done; + } else { + i = 0; + iio_for_each_active_channel(iio, ch) { + ret = regmap_bulk_read(map, iio->channels[ch].address, + &scan[i++], sizeof(*scan)); + if (ret) + goto done; + } + } + + iio_push_to_buffers_with_ts(iio, scan, sizeof(scan), pf->timestamp); + +done: + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + +static int veml6031x00_validate_part_id(struct veml6031x00_data *data) +{ + struct regmap *map = data->regmap; + struct device *dev = regmap_get_device(map); + unsigned int part_id; + int ret; + __le16 regval; + + ret = regmap_bulk_read(map, VEML6031X00_REG_ID_L, ®val, sizeof(regval)); + if (ret) + return dev_err_probe(dev, ret, "Failed to read ID\n"); + + part_id = le16_to_cpu(regval); + if (part_id != data->chip->part_id) + dev_info(dev, "Unknown or unexpected ID %04x\n", part_id); + + return 0; +} + +static int veml6031x00_hw_init(struct veml6031x00_data *data) +{ + struct regmap *map = data->regmap; + struct device *dev = regmap_get_device(map); + unsigned int val; + __le16 regval; + int ret; + + ret = regmap_assign_bits(data->regmap, VEML6031X00_REG_CONF0, + VEML6031X00_CONF0_AF | VEML6031X00_CONF0_AF_TRIG, 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to disable trigger\n"); + + ret = regmap_set_bits(data->regmap, VEML6031X00_REG_CONF1, + VEML6031X00_CONF1_ALS_CAL); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable internal calibration\n"); + + regval = 0; + ret = regmap_bulk_write(map, VEML6031X00_REG_WL_L, ®val, sizeof(regval)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set low threshold\n"); + + regval = cpu_to_le16(U16_MAX); + ret = regmap_bulk_write(map, VEML6031X00_REG_WH_L, ®val, sizeof(regval)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set high threshold\n"); + + ret = regmap_field_write(data->rf.int_en, 0); + if (ret) + return ret; + + ret = regmap_read(map, VEML6031X00_REG_INT, &val); + if (ret) + return dev_err_probe(dev, ret, "Failed to clear interrupts\n"); + + return 0; +} + +static int veml6031x00_setup_trigger(struct iio_dev *iio) +{ + struct veml6031x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + + data->trig = devm_iio_trigger_alloc(dev, "%s-drdy%d", + iio->name, iio_device_id(iio)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &veml6031x00_trigger_ops; + iio_trigger_set_drvdata(data->trig, iio); + + return devm_iio_trigger_register(dev, data->trig); +} + +static int veml6031x00_init_iiodev(struct i2c_client *i2c, struct iio_dev *iio) +{ + struct veml6031x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + iio->name = data->chip->name; + iio->modes = INDIO_DIRECT_MODE; + + if (!i2c->irq) { + iio->channels = veml6031x00_channels; + iio->num_channels = ARRAY_SIZE(veml6031x00_channels); + iio->info = &veml6031x00_info_no_irq; + + return 0; + } + + iio->channels = veml6031x00_channels_irq; + iio->num_channels = ARRAY_SIZE(veml6031x00_channels_irq); + iio->info = &veml6031x00_info; + + ret = veml6031x00_setup_trigger(iio); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, veml6031x00_disable_event_action, data); +} + +static int veml6031x00_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct veml6031x00_data *data; + struct iio_dev *iio; + int ret; + + iio = devm_iio_device_alloc(dev, sizeof(*data)); + if (!iio) + return -ENOMEM; + + data = iio_priv(iio); + i2c_set_clientdata(i2c, iio); + + data->chip = i2c_get_match_data(i2c); + if (!data->chip) + return dev_err_probe(dev, -ENODATA, "Failed to get chip data\n"); + + ret = devm_mutex_init(dev, &data->scale_lock); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &data->irq_lock); + if (ret) + return ret; + + data->regmap = devm_regmap_init_i2c(i2c, &veml6031x00_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "Failed to set regmap\n"); + + ret = veml6031x00_regfield_init(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to init regfield\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + /* The device starts in power down mode by default */ + ret = veml6031x00_set_power(data, true); + if (ret) + return dev_err_probe(dev, ret, "Failed to power on the device\n"); + + ret = devm_add_action_or_reset(dev, veml6031x00_als_shutdown_action, data); + if (ret) + return dev_err_probe(dev, ret, "Failed to add shutdown action\n"); + + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + ret = veml6031x00_validate_part_id(data); + if (ret) + return ret; + + ret = veml6031x00_hw_init(data); + if (ret) + return ret; + + /* Max resolution = 6.9632 lx/cnt for gain = 0.125 and IT = 3.125ms */ + ret = devm_iio_init_iio_gts(dev, 6, 963200000, + veml6031x00_gain_sel, + ARRAY_SIZE(veml6031x00_gain_sel), + veml6031x00_it_sel, + ARRAY_SIZE(veml6031x00_it_sel), + &data->gts); + if (ret) + return dev_err_probe(dev, ret, "Failed to init IIO GTS\n"); + + ret = veml6031x00_init_iiodev(i2c, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize IIO device\n"); + + ret = devm_iio_triggered_buffer_setup(dev, iio, + iio_pollfunc_store_time, + veml6031x00_trig_handler, + &veml6031x00_buffer_setup_ops); + if (ret) + return dev_err_probe(dev, ret, "Failed to register triggered buffer\n"); + + if (i2c->irq) { + ret = devm_request_threaded_irq(dev, i2c->irq, NULL, veml6031x00_irq, + IRQF_ONESHOT, iio->name, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to request IRQ\n"); + } + + ret = devm_iio_device_register(dev, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to register IIO device\n"); + + return 0; +} + +static int veml6031x00_runtime_suspend(struct device *dev) +{ + struct veml6031x00_data *data = iio_priv(dev_get_drvdata(dev)); + + return veml6031x00_set_power(data, false); +} + +static int veml6031x00_runtime_resume(struct device *dev) +{ + struct veml6031x00_data *data = iio_priv(dev_get_drvdata(dev)); + + return veml6031x00_set_power(data, true); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(veml6031x00_pm_ops, + veml6031x00_runtime_suspend, + veml6031x00_runtime_resume, + NULL); + +static const struct veml6031x00_chip veml6031x00_chip = { + .name = "veml6031x00", + .part_id = 0x0001, +}; + +static const struct veml6031x00_chip veml6031x01_chip = { + .name = "veml6031x01", + .part_id = 0x0001, +}; + +static const struct veml6031x00_chip veml60311x00_chip = { + .name = "veml60311x00", + .part_id = 0x1001, +}; + +static const struct veml6031x00_chip veml60311x01_chip = { + .name = "veml60311x01", + .part_id = 0x1001, +}; + +static const struct of_device_id veml6031x00_of_match[] = { + { + .compatible = "vishay,veml6031x00", + .data = &veml6031x00_chip, + }, + { + .compatible = "vishay,veml6031x01", + .data = &veml6031x01_chip, + }, + { + .compatible = "vishay,veml60311x00", + .data = &veml60311x00_chip, + }, + { + .compatible = "vishay,veml60311x01", + .data = &veml60311x01_chip, + }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6031x00_of_match); + +static const struct i2c_device_id veml6031x00_id[] = { + { + .name = "veml6031x00", + .driver_data = (kernel_ulong_t)&veml6031x00_chip, + }, + { + .name = "veml6031x01", + .driver_data = (kernel_ulong_t)&veml6031x01_chip, + }, + { + .name = "veml60311x00", + .driver_data = (kernel_ulong_t)&veml60311x00_chip, + }, + { + .name = "veml60311x01", + .driver_data = (kernel_ulong_t)&veml60311x01_chip, + }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6031x00_id); + +static struct i2c_driver veml6031x00_driver = { + .driver = { + .name = "veml6031x00", + .of_match_table = veml6031x00_of_match, + .pm = pm_ptr(&veml6031x00_pm_ops), + }, + .probe = veml6031x00_probe, + .id_table = veml6031x00_id, +}; +module_i2c_driver(veml6031x00_driver); + +MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gmail.com>"); +MODULE_DESCRIPTION("VEML6031X00 Ambient Light Sensor"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index 8b0c07f82602..cc5be1fa91ea 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -44,8 +44,7 @@ #define AK8975_REG_INFO 0x01 #define AK8975_REG_ST1 0x02 -#define AK8975_REG_ST1_DRDY_SHIFT 0 -#define AK8975_REG_ST1_DRDY_MASK (1 << AK8975_REG_ST1_DRDY_SHIFT) +#define AK8975_REG_ST1_DRDY_MASK BIT(0) #define AK8975_REG_HXL 0x03 #define AK8975_REG_HXH 0x04 @@ -54,15 +53,12 @@ #define AK8975_REG_HZL 0x07 #define AK8975_REG_HZH 0x08 #define AK8975_REG_ST2 0x09 -#define AK8975_REG_ST2_DERR_SHIFT 2 -#define AK8975_REG_ST2_DERR_MASK (1 << AK8975_REG_ST2_DERR_SHIFT) +#define AK8975_REG_ST2_DERR_MASK BIT(2) -#define AK8975_REG_ST2_HOFL_SHIFT 3 -#define AK8975_REG_ST2_HOFL_MASK (1 << AK8975_REG_ST2_HOFL_SHIFT) +#define AK8975_REG_ST2_HOFL_MASK BIT(3) #define AK8975_REG_CNTL 0x0A -#define AK8975_REG_CNTL_MODE_SHIFT 0 -#define AK8975_REG_CNTL_MODE_MASK (0xF << AK8975_REG_CNTL_MODE_SHIFT) +#define AK8975_REG_CNTL_MODE_MASK GENMASK(3, 0) #define AK8975_REG_CNTL_MODE_POWER_DOWN 0x00 #define AK8975_REG_CNTL_MODE_ONCE 0x01 #define AK8975_REG_CNTL_MODE_SELF_TEST 0x08 @@ -94,8 +90,7 @@ #define AK09912_REG_ST1 0x10 -#define AK09912_REG_ST1_DRDY_SHIFT 0 -#define AK09912_REG_ST1_DRDY_MASK (1 << AK09912_REG_ST1_DRDY_SHIFT) +#define AK09912_REG_ST1_DRDY_MASK BIT(0) #define AK09912_REG_HXL 0x11 #define AK09912_REG_HXH 0x12 @@ -106,8 +101,7 @@ #define AK09912_REG_TMPS 0x17 #define AK09912_REG_ST2 0x18 -#define AK09912_REG_ST2_HOFL_SHIFT 3 -#define AK09912_REG_ST2_HOFL_MASK (1 << AK09912_REG_ST2_HOFL_SHIFT) +#define AK09912_REG_ST2_HOFL_MASK BIT(3) #define AK09912_REG_CNTL1 0x30 @@ -116,8 +110,7 @@ #define AK09912_REG_CNTL_MODE_ONCE 0x01 #define AK09912_REG_CNTL_MODE_SELF_TEST 0x10 #define AK09912_REG_CNTL_MODE_FUSE_ROM 0x1F -#define AK09912_REG_CNTL2_MODE_SHIFT 0 -#define AK09912_REG_CNTL2_MODE_MASK (0x1F << AK09912_REG_CNTL2_MODE_SHIFT) +#define AK09912_REG_CNTL2_MODE_MASK GENMASK(4, 0) #define AK09912_REG_CNTL3 0x32 @@ -237,6 +230,13 @@ enum ak_ctrl_mode { MODE_END, }; +enum ak_chan_index { + AK8975_CHAN_X, + AK8975_CHAN_Y, + AK8975_CHAN_Z, + AK8975_CHAN_TS, +}; + struct ak_def { enum asahi_compass_chipset type; long (*raw_to_gauss)(u16 data); @@ -418,6 +418,7 @@ struct ak8975_data { wait_queue_head_t data_ready_queue; unsigned long flags; u8 cntl_cache; + bool powered_on; struct iio_mount_matrix orientation; struct regulator *vdd; struct regulator *vid; @@ -430,20 +431,19 @@ struct ak8975_data { }; /* Enable attached power regulator if any. */ -static int ak8975_power_on(const struct ak8975_data *data) +static int ak8975_power_on(struct ak8975_data *data) { + struct device *dev = &data->client->dev; int ret; ret = regulator_enable(data->vdd); if (ret) { - dev_warn(&data->client->dev, - "Failed to enable specified Vdd supply\n"); + dev_warn(dev, "Failed to enable specified Vdd supply\n"); return ret; } ret = regulator_enable(data->vid); if (ret) { - dev_warn(&data->client->dev, - "Failed to enable specified Vid supply\n"); + dev_warn(dev, "Failed to enable specified Vid supply\n"); regulator_disable(data->vdd); return ret; } @@ -457,16 +457,23 @@ static int ak8975_power_on(const struct ak8975_data *data) */ fsleep(500); + data->powered_on = true; + return 0; } /* Disable attached power regulator if any. */ -static void ak8975_power_off(const struct ak8975_data *data) +static void ak8975_power_off(struct ak8975_data *data) { + if (!data->powered_on) + return; + gpiod_set_value_cansleep(data->reset_gpiod, 1); regulator_disable(data->vid); regulator_disable(data->vdd); + + data->powered_on = false; } /* @@ -494,14 +501,10 @@ static int ak8975_who_i_am(const struct ak8975_data *data, AK09912_REG_WIA1, sizeof(wia_val), wia_val); - if (ret < 0) { - dev_err(&client->dev, "Error reading WIA\n"); - return ret; - } - if (ret != sizeof(wia_val)) { - dev_err(&client->dev, "Error reading WIA\n"); - return -EIO; - } + if (ret < 0) + return dev_err_probe(&client->dev, ret, "Error reading WIA\n"); + if (ret != sizeof(wia_val)) + return dev_err_probe(&client->dev, -EIO, "Error reading WIA\n"); if (wia_val[0] != AK8975_DEVICE_ID) return -ENODEV; @@ -577,6 +580,7 @@ static irqreturn_t ak8975_irq_handler(int irq, void *data) static int ak8975_setup_irq(struct ak8975_data *data) { struct i2c_client *client = data->client; + struct device *dev = &client->dev; int irq; int ret; @@ -587,9 +591,8 @@ static int ak8975_setup_irq(struct ak8975_data *data) else irq = gpiod_to_irq(data->eoc_gpiod); - ret = devm_request_irq(&client->dev, irq, ak8975_irq_handler, - IRQF_TRIGGER_RISING, - dev_name(&client->dev), data); + ret = devm_request_irq(dev, irq, ak8975_irq_handler, IRQF_TRIGGER_RISING, + dev_name(dev), data); if (ret) return ret; @@ -605,43 +608,33 @@ static int ak8975_setup_irq(struct ak8975_data *data) static int ak8975_setup(struct ak8975_data *data) { struct i2c_client *client = data->client; + struct device *dev = &client->dev; int ret; /* Write the fused rom access mode. */ ret = ak8975_set_mode(data, FUSE_ROM); - if (ret < 0) { - dev_err(&client->dev, "Error in setting fuse access mode\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Error in setting fuse access mode\n"); /* Get asa data and store in the device data. */ ret = i2c_smbus_read_i2c_block_data_or_emulated(client, data->def->ctrl_regs[ASA_BASE], sizeof(data->asa), data->asa); - if (ret < 0) { - dev_err(&client->dev, "Not able to read asa data\n"); - return ret; - } - if (ret != sizeof(data->asa)) { - dev_err(&client->dev, "Error reading asa data\n"); - return -EIO; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Not able to read asa data\n"); + if (ret != sizeof(data->asa)) + return dev_err_probe(dev, -EIO, "Error reading asa data\n"); /* After reading fuse ROM data set power-down mode */ ret = ak8975_set_mode(data, POWER_DOWN); - if (ret < 0) { - dev_err(&client->dev, "Error in setting power-down mode\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Error in setting power-down mode\n"); if (data->eoc_gpiod || client->irq > 0) { ret = ak8975_setup_irq(data); - if (ret < 0) { - dev_err(&client->dev, - "Error setting data ready interrupt\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Error setting data ready interrupt\n"); } data->raw_to_gauss[0] = data->def->raw_to_gauss(data->asa[0]); @@ -745,10 +738,11 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) struct ak8975_data *data = iio_priv(indio_dev); const struct i2c_client *client = data->client; const struct ak_def *def = data->def; + struct device *dev = &data->client->dev; __le16 rval; int ret; - pm_runtime_get_sync(&data->client->dev); + pm_runtime_get_sync(dev); mutex_lock(&data->lock); @@ -770,20 +764,20 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) /* Read out ST2 for release lock on measurement data. */ ret = i2c_smbus_read_byte_data(client, data->def->ctrl_regs[ST2]); if (ret < 0) { - dev_err(&client->dev, "Error in reading ST2\n"); + dev_err(dev, "Error in reading ST2\n"); goto exit; } if (ret & (data->def->ctrl_masks[ST2_DERR] | data->def->ctrl_masks[ST2_HOFL])) { - dev_err(&client->dev, "ST2 status error 0x%x\n", ret); + dev_err(dev, "ST2 status error 0x%x\n", ret); ret = -EINVAL; goto exit; } mutex_unlock(&data->lock); - pm_runtime_put_autosuspend(&data->client->dev); + pm_runtime_put_autosuspend(dev); /* Swap bytes and convert to valid range. */ *val = clamp_t(s16, le16_to_cpu(rval), -def->range, def->range); @@ -792,8 +786,8 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) exit: mutex_unlock(&data->lock); - pm_runtime_put_autosuspend(&data->client->dev); - dev_err(&client->dev, "Error in reading axis\n"); + pm_runtime_put_autosuspend(dev); + dev_err(dev, "Error in reading axis\n"); return ret; } @@ -848,11 +842,16 @@ static const struct iio_chan_spec_ext_info ak8975_ext_info[] = { } static const struct iio_chan_spec ak8975_channels[] = { - AK8975_CHANNEL(X, 0), AK8975_CHANNEL(Y, 1), AK8975_CHANNEL(Z, 2), - IIO_CHAN_SOFT_TIMESTAMP(3), + AK8975_CHANNEL(X, AK8975_CHAN_X), + AK8975_CHANNEL(Y, AK8975_CHAN_Y), + AK8975_CHANNEL(Z, AK8975_CHAN_Z), + IIO_CHAN_SOFT_TIMESTAMP(AK8975_CHAN_TS), }; -static const unsigned long ak8975_scan_masks[] = { 0x7, 0 }; +static const unsigned long ak8975_scan_masks[] = { + BIT(AK8975_CHAN_X) | BIT(AK8975_CHAN_Y) | BIT(AK8975_CHAN_Z), + 0 +}; static const struct iio_info ak8975_info = { .read_raw = &ak8975_read_raw, @@ -934,9 +933,23 @@ static const struct iio_buffer_setup_ops ak8975_buffer_setup_ops = { .preenable = ak8975_buffer_preenable, .postdisable = ak8975_buffer_postdisable, }; + +static void devm_ak8975_power_off(void *data) +{ + struct ak8975_data *ak = data; + + if (!ak->powered_on) + return; + + /* Soft-stop the chip before hard-stopping the regulators */ + ak8975_set_mode(data, POWER_DOWN); + ak8975_power_off(data); +} + static int ak8975_probe(struct i2c_client *client) { const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct device *dev = &client->dev; struct ak8975_data *data; struct iio_dev *indio_dev; struct gpio_desc *eoc_gpiod; @@ -949,7 +962,7 @@ static int ak8975_probe(struct i2c_client *client) * We may not have a GPIO based IRQ to scan, that is fine, we will * poll if so. */ - eoc_gpiod = devm_gpiod_get_optional(&client->dev, NULL, GPIOD_IN); + eoc_gpiod = devm_gpiod_get_optional(dev, NULL, GPIOD_IN); if (IS_ERR(eoc_gpiod)) return PTR_ERR(eoc_gpiod); gpiod_set_consumer_name(eoc_gpiod, "ak_8975"); @@ -959,13 +972,12 @@ static int ak8975_probe(struct i2c_client *client) * deassert reset on ak8975_power_on() and assert reset on * ak8975_power_off(). */ - reset_gpiod = devm_gpiod_get_optional(&client->dev, - "reset", GPIOD_OUT_HIGH); + reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(reset_gpiod)) return PTR_ERR(reset_gpiod); /* Register with IIO */ - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (indio_dev == NULL) return -ENOMEM; @@ -977,7 +989,7 @@ static int ak8975_probe(struct i2c_client *client) data->reset_gpiod = reset_gpiod; data->eoc_irq = 0; - ret = iio_read_mount_matrix(&client->dev, &data->orientation); + ret = iio_read_mount_matrix(dev, &data->orientation); if (ret) return ret; @@ -987,16 +999,16 @@ static int ak8975_probe(struct i2c_client *client) return -ENODEV; /* If enumerated via firmware node, fix the ABI */ - if (dev_fwnode(&client->dev)) - name = dev_name(&client->dev); + if (dev_fwnode(dev)) + name = dev_name(dev); else name = id->name; /* Fetch the regulators */ - data->vdd = devm_regulator_get(&client->dev, "vdd"); + data->vdd = devm_regulator_get(dev, "vdd"); if (IS_ERR(data->vdd)) return PTR_ERR(data->vdd); - data->vid = devm_regulator_get(&client->dev, "vid"); + data->vid = devm_regulator_get(dev, "vid"); if (IS_ERR(data->vid)) return PTR_ERR(data->vid); @@ -1004,21 +1016,25 @@ static int ak8975_probe(struct i2c_client *client) if (ret) return ret; + ret = devm_add_action_or_reset(dev, devm_ak8975_power_off, data); + if (ret) + return ret; + ret = ak8975_who_i_am(data, data->def->type); - if (ret) { - dev_err(&client->dev, "Unexpected device\n"); - goto power_off; - } - dev_dbg(&client->dev, "Asahi compass chip %s\n", name); + if (ret) + return dev_err_probe(dev, ret, "Unexpected device\n"); + + dev_dbg(dev, "Asahi compass chip %s\n", name); /* Perform some basic start-of-day setup of the device. */ ret = ak8975_setup(data); - if (ret) { - dev_err(&client->dev, "%s initialization fails\n", name); - goto power_off; - } + if (ret) + return dev_err_probe(dev, ret, "%s initialization fails\n", name); + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; - mutex_init(&data->lock); indio_dev->channels = ak8975_channels; indio_dev->num_channels = ARRAY_SIZE(ak8975_channels); indio_dev->info = &ak8975_info; @@ -1026,52 +1042,29 @@ static int ak8975_probe(struct i2c_client *client) indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->name = name; - ret = iio_triggered_buffer_setup(indio_dev, NULL, ak8975_handle_trigger, - &ak8975_buffer_setup_ops); - if (ret) { - dev_err(&client->dev, "triggered buffer setup failed\n"); - goto power_off; - } + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + ak8975_handle_trigger, + &ak8975_buffer_setup_ops); + if (ret) + return dev_err_probe(dev, ret, "triggered buffer setup failed\n"); - ret = iio_device_register(indio_dev); - if (ret) { - dev_err(&client->dev, "device register failed\n"); - goto cleanup_buffer; - } + pm_runtime_set_active(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "device register failed\n"); - /* Enable runtime PM */ - pm_runtime_get_noresume(&client->dev); - pm_runtime_set_active(&client->dev); - pm_runtime_enable(&client->dev); /* * The device comes online in 500us, so add two orders of magnitude * of delay before autosuspending: 50 ms. */ - pm_runtime_set_autosuspend_delay(&client->dev, 50); - pm_runtime_use_autosuspend(&client->dev); - pm_runtime_put(&client->dev); + pm_runtime_set_autosuspend_delay(dev, 50); + pm_runtime_use_autosuspend(dev); return 0; - -cleanup_buffer: - iio_triggered_buffer_cleanup(indio_dev); -power_off: - ak8975_power_off(data); - return ret; -} - -static void ak8975_remove(struct i2c_client *client) -{ - struct iio_dev *indio_dev = i2c_get_clientdata(client); - struct ak8975_data *data = iio_priv(indio_dev); - - pm_runtime_get_sync(&client->dev); - pm_runtime_put_noidle(&client->dev); - pm_runtime_disable(&client->dev); - iio_device_unregister(indio_dev); - iio_triggered_buffer_cleanup(indio_dev); - ak8975_set_mode(data, POWER_DOWN); - ak8975_power_off(data); } static int ak8975_runtime_suspend(struct device *dev) @@ -1084,7 +1077,7 @@ static int ak8975_runtime_suspend(struct device *dev) /* Set the device in power down if it wasn't already */ ret = ak8975_set_mode(data, POWER_DOWN); if (ret < 0) { - dev_err(&client->dev, "Error in setting power-down mode\n"); + dev_err(dev, "Error in setting power-down mode\n"); return ret; } /* Next cut the regulators */ @@ -1108,7 +1101,7 @@ static int ak8975_runtime_resume(struct device *dev) */ ret = ak8975_set_mode(data, POWER_DOWN); if (ret < 0) { - dev_err(&client->dev, "Error in setting power-down mode\n"); + dev_err(dev, "Error in setting power-down mode\n"); return ret; } @@ -1165,7 +1158,6 @@ static struct i2c_driver ak8975_driver = { .acpi_match_table = ak_acpi_match, }, .probe = ak8975_probe, - .remove = ak8975_remove, .id_table = ak8975_id, }; module_i2c_driver(ak8975_driver); diff --git a/drivers/iio/position/iqs624-pos.c b/drivers/iio/position/iqs624-pos.c index 8239239c6ee2..d2aa2df71457 100644 --- a/drivers/iio/position/iqs624-pos.c +++ b/drivers/iio/position/iqs624-pos.c @@ -96,19 +96,6 @@ static int iqs624_pos_notifier(struct notifier_block *notifier, return ret; } -static void iqs624_pos_notifier_unregister(void *context) -{ - struct iqs624_pos_private *iqs624_pos = context; - struct iio_dev *indio_dev = iqs624_pos->indio_dev; - int ret; - - ret = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh, - &iqs624_pos->notifier); - if (ret) - dev_err(indio_dev->dev.parent, - "Failed to unregister notifier: %d\n", ret); -} - static int iqs624_pos_angle_get(struct iqs62x_core *iqs62x, unsigned int *val) { int ret; @@ -255,19 +242,14 @@ static int iqs624_pos_probe(struct platform_device *pdev) mutex_init(&iqs624_pos->lock); iqs624_pos->notifier.notifier_call = iqs624_pos_notifier; - ret = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh, - &iqs624_pos->notifier); + ret = devm_blocking_notifier_chain_register(&pdev->dev, + &iqs624_pos->iqs62x->nh, + &iqs624_pos->notifier); if (ret) { dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); return ret; } - ret = devm_add_action_or_reset(&pdev->dev, - iqs624_pos_notifier_unregister, - iqs624_pos); - if (ret) - return ret; - return devm_iio_device_register(&pdev->dev, indio_dev); } diff --git a/drivers/iio/potentiometer/x9250.c b/drivers/iio/potentiometer/x9250.c index 974e4447fc2c..675b78677baa 100644 --- a/drivers/iio/potentiometer/x9250.c +++ b/drivers/iio/potentiometer/x9250.c @@ -155,10 +155,15 @@ static const char *const x9250_regulator_names[] = { static int x9250_probe(struct spi_device *spi) { + const struct x9250_cfg *cfg; struct iio_dev *indio_dev; struct x9250 *x9250; int ret; + cfg = spi_get_device_match_data(spi); + if (!cfg) + return -ENODATA; + ret = devm_regulator_bulk_get_enable(&spi->dev, ARRAY_SIZE(x9250_regulator_names), x9250_regulator_names); if (ret) @@ -176,7 +181,7 @@ static int x9250_probe(struct spi_device *spi) x9250 = iio_priv(indio_dev); x9250->spi = spi; - x9250->cfg = spi_get_device_match_data(spi); + x9250->cfg = cfg; x9250->wp_gpio = devm_gpiod_get_optional(&spi->dev, "wp", GPIOD_OUT_LOW); if (IS_ERR(x9250->wp_gpio)) return dev_err_probe(&spi->dev, PTR_ERR(x9250->wp_gpio), diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig index 838a8340c4c0..cef8b90b9ae7 100644 --- a/drivers/iio/pressure/Kconfig +++ b/drivers/iio/pressure/Kconfig @@ -112,6 +112,8 @@ config DPS310 tristate "Infineon DPS310 pressure and temperature sensor" depends on I2C select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER help Support for the Infineon DPS310 digital barometric pressure sensor. It can be accessed over I2C bus. diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile index bc0d11a20acc..4b1a05f7a0bd 100644 --- a/drivers/iio/pressure/Makefile +++ b/drivers/iio/pressure/Makefile @@ -3,6 +3,8 @@ # Makefile for industrial I/O pressure drivers # +CONTEXT_ANALYSIS_dps310.o := y + # When adding new entries keep the list in alphabetical order obj-$(CONFIG_ABP060MG) += abp060mg.o obj-$(CONFIG_ABP2030PA) += abp2030pa.o diff --git a/drivers/iio/pressure/bmp280-spi.c b/drivers/iio/pressure/bmp280-spi.c index a02a621b8015..95d9635f4711 100644 --- a/drivers/iio/pressure/bmp280-spi.c +++ b/drivers/iio/pressure/bmp280-spi.c @@ -83,6 +83,8 @@ static int bmp280_spi_probe(struct spi_device *spi) struct regmap *regmap; chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return -ENODATA; if (chip_info->spi_read_extra_byte) bmp_regmap_bus = &bmp380_regmap_bus; diff --git a/drivers/iio/pressure/cros_ec_baro.c b/drivers/iio/pressure/cros_ec_baro.c index 6a567b6075d9..e4bc4df48d01 100644 --- a/drivers/iio/pressure/cros_ec_baro.c +++ b/drivers/iio/pressure/cros_ec_baro.c @@ -192,7 +192,7 @@ MODULE_DEVICE_TABLE(platform, cros_ec_baro_ids); static struct platform_driver cros_ec_baro_platform_driver = { .driver = { .name = "cros-ec-baro", - .pm = &cros_ec_sensors_pm_ops, + .pm = pm_sleep_ptr(&cros_ec_sensors_pm_ops), }, .probe = cros_ec_baro_probe, .id_table = cros_ec_baro_ids, diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c index 45bdb8c7670f..85df58ac1809 100644 --- a/drivers/iio/pressure/dps310.c +++ b/drivers/iio/pressure/dps310.c @@ -14,14 +14,19 @@ * - Optionally support the FIFO */ +#include <linux/cleanup.h> #include <linux/i2c.h> #include <linux/limits.h> #include <linux/math64.h> #include <linux/module.h> #include <linux/regmap.h> +#include <linux/unaligned.h> +#include <linux/iio/buffer.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> #define DPS310_DEV_NAME "dps310" @@ -50,9 +55,9 @@ #define DPS310_CFG_REG 0x09 #define DPS310_INT_HL BIT(7) #define DPS310_TMP_SHIFT_EN BIT(3) -#define DPS310_PRS_SHIFT_EN BIT(4) -#define DPS310_FIFO_EN BIT(5) -#define DPS310_SPI_EN BIT(6) +#define DPS310_PRS_SHIFT_EN BIT(2) +#define DPS310_FIFO_EN BIT(1) +#define DPS310_SPI_EN BIT(0) #define DPS310_RESET 0x0c #define DPS310_RESET_MAGIC 0x09 #define DPS310_COEF_BASE 0x10 @@ -92,19 +97,51 @@ struct dps310_data { bool timeout_recovery_failed; }; +enum dps310_scan_index { + DPS310_SCAN_TEMP, + DPS310_SCAN_PRESSURE, + DPS310_SCAN_TIMESTAMP, +}; + static const struct iio_chan_spec dps310_channels[] = { { .type = IIO_TEMP, .info_mask_separate = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | BIT(IIO_CHAN_INFO_SAMP_FREQ) | BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = DPS310_SCAN_TEMP, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, }, { .type = IIO_PRESSURE, + /* + * _raw here is already compensated (section 4.9.1, which needs + * a temperature too) and in Pa; _scale converts to the kPa the + * ABI wants. _processed predates buffers and has to stay. + * + * Do not copy this into other drivers. A _raw that is not the + * raw register value is only tolerable because the alternative + * is losing resolution or breaking existing _processed users. + */ .info_mask_separate = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = DPS310_SCAN_PRESSURE, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, }, + IIO_CHAN_SOFT_TIMESTAMP(DPS310_SCAN_TIMESTAMP), }; /* To be called after checking the COEF_RDY bit in MEAS_CFG */ @@ -286,8 +323,8 @@ static int dps310_get_temp_precision(struct dps310_data *data, int *val) return 0; } -/* Called with lock held */ static int dps310_set_pres_precision(struct dps310_data *data, int val) + __must_hold(&data->lock) { int rc; u8 shift_en; @@ -305,8 +342,8 @@ static int dps310_set_pres_precision(struct dps310_data *data, int val) DPS310_PRS_PRC_BITS, ilog2(val)); } -/* Called with lock held */ static int dps310_set_temp_precision(struct dps310_data *data, int val) + __must_hold(&data->lock) { int rc; u8 shift_en; @@ -324,8 +361,8 @@ static int dps310_set_temp_precision(struct dps310_data *data, int val) DPS310_TMP_PRC_BITS, ilog2(val)); } -/* Called with lock held */ static int dps310_set_pres_samp_freq(struct dps310_data *data, int freq) + __must_hold(&data->lock) { u8 val; @@ -338,8 +375,8 @@ static int dps310_set_pres_samp_freq(struct dps310_data *data, int freq) DPS310_PRS_RATE_BITS, val); } -/* Called with lock held */ static int dps310_set_temp_samp_freq(struct dps310_data *data, int freq) + __must_hold(&data->lock) { u8 val; @@ -438,6 +475,7 @@ static int dps310_ready_status(struct dps310_data *data, int ready_bit, int time } static int dps310_ready(struct dps310_data *data, int ready_bit, int timeout) + __must_hold(&data->lock) { int rc; @@ -463,82 +501,83 @@ static int dps310_ready(struct dps310_data *data, int ready_bit, int timeout) return 0; } -static int dps310_read_pres_raw(struct dps310_data *data) +static int dps310_read_pres_raw_locked(struct dps310_data *data) + __must_hold(&data->lock) { int rc; int rate; int timeout; - s32 raw; u8 val[3]; - if (mutex_lock_interruptible(&data->lock)) - return -EINTR; - rc = dps310_get_pres_samp_freq(data, &rate); if (rc) - goto done; + return rc; timeout = DPS310_POLL_TIMEOUT_US(rate); /* Poll for sensor readiness; base the timeout upon the sample rate. */ rc = dps310_ready(data, DPS310_PRS_RDY, timeout); if (rc) - goto done; + return rc; rc = regmap_bulk_read(data->regmap, DPS310_PRS_BASE, val, sizeof(val)); if (rc < 0) - goto done; + return rc; - raw = (val[0] << 16) | (val[1] << 8) | val[2]; - data->pressure_raw = sign_extend32(raw, 23); + data->pressure_raw = sign_extend32(get_unaligned_be24(val), 23); -done: - mutex_unlock(&data->lock); - return rc; + return 0; } -/* Called with lock held */ static int dps310_read_temp_ready(struct dps310_data *data) + __must_hold(&data->lock) { int rc; u8 val[3]; - s32 raw; rc = regmap_bulk_read(data->regmap, DPS310_TMP_BASE, val, sizeof(val)); if (rc < 0) return rc; - raw = (val[0] << 16) | (val[1] << 8) | val[2]; - data->temp_raw = sign_extend32(raw, 23); + data->temp_raw = sign_extend32(get_unaligned_be24(val), 23); return 0; } -static int dps310_read_temp_raw(struct dps310_data *data) +static int dps310_read_temp_raw_locked(struct dps310_data *data) + __must_hold(&data->lock) { int rc; int rate; int timeout; - if (mutex_lock_interruptible(&data->lock)) - return -EINTR; - rc = dps310_get_temp_samp_freq(data, &rate); if (rc) - goto done; + return rc; timeout = DPS310_POLL_TIMEOUT_US(rate); /* Poll for sensor readiness; base the timeout upon the sample rate. */ rc = dps310_ready(data, DPS310_TMP_RDY, timeout); if (rc) - goto done; + return rc; - rc = dps310_read_temp_ready(data); + return dps310_read_temp_ready(data); +} -done: - mutex_unlock(&data->lock); - return rc; +/* Best effort: on error the previous temperature stands */ +static void dps310_refresh_temp_locked(struct dps310_data *data) + __must_hold(&data->lock) +{ + int rc; + int t_ready; + + rc = regmap_read(data->regmap, DPS310_MEAS_CFG, &t_ready); + if (rc) + return; + + if (t_ready & DPS310_TMP_RDY) + dps310_read_temp_ready(data); } static bool dps310_is_writeable_reg(struct device *dev, unsigned int reg) @@ -580,59 +619,52 @@ static int dps310_write_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, int val, int val2, long mask) { - int rc; struct dps310_data *data = iio_priv(iio); - if (mutex_lock_interruptible(&data->lock)) + /* Reconfiguring mid-capture would change the values being captured */ + IIO_DEV_ACQUIRE_DIRECT_MODE(iio, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + ACQUIRE(mutex_intr, lock)(&data->lock); + if (ACQUIRE_ERR(mutex_intr, &lock)) return -EINTR; switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: switch (chan->type) { case IIO_PRESSURE: - rc = dps310_set_pres_samp_freq(data, val); - break; + return dps310_set_pres_samp_freq(data, val); case IIO_TEMP: - rc = dps310_set_temp_samp_freq(data, val); - break; + return dps310_set_temp_samp_freq(data, val); default: - rc = -EINVAL; - break; + return -EINVAL; } - break; case IIO_CHAN_INFO_OVERSAMPLING_RATIO: switch (chan->type) { case IIO_PRESSURE: - rc = dps310_set_pres_precision(data, val); - break; + return dps310_set_pres_precision(data, val); case IIO_TEMP: - rc = dps310_set_temp_precision(data, val); - break; + return dps310_set_temp_precision(data, val); default: - rc = -EINVAL; - break; + return -EINVAL; } - break; default: - rc = -EINVAL; - break; + return -EINVAL; } - - mutex_unlock(&data->lock); - return rc; } static int dps310_calculate_pressure(struct dps310_data *data, int *val) + __must_hold(&data->lock) { int i; int rc; - int t_ready; int kpi; int kti; s64 rem = 0ULL; @@ -656,15 +688,6 @@ static int dps310_calculate_pressure(struct dps310_data *data, int *val) kp = (s64)kpi; kt = (s64)kti; - /* Refresh temp if it's ready, otherwise just use the latest value */ - if (mutex_trylock(&data->lock)) { - rc = regmap_read(data->regmap, DPS310_MEAS_CFG, &t_ready); - if (rc >= 0 && t_ready & DPS310_TMP_RDY) - dps310_read_temp_ready(data); - - mutex_unlock(&data->lock); - } - p = (s64)data->pressure_raw; t = (s64)data->temp_raw; @@ -710,6 +733,23 @@ static int dps310_calculate_pressure(struct dps310_data *data, int *val) return 0; } +static int dps310_read_pressure_value(struct dps310_data *data, int *val) +{ + int rc; + + ACQUIRE(mutex_intr, lock)(&data->lock); + if (ACQUIRE_ERR(mutex_intr, &lock)) + return -EINTR; + + rc = dps310_read_pres_raw_locked(data); + if (rc) + return rc; + + dps310_refresh_temp_locked(data); + + return dps310_calculate_pressure(data, val); +} + static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, long mask) { @@ -723,18 +763,27 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, return IIO_VAL_INT; - case IIO_CHAN_INFO_PROCESSED: - rc = dps310_read_pres_raw(data); + case IIO_CHAN_INFO_RAW: + rc = dps310_read_pressure_value(data, val); if (rc) return rc; - rc = dps310_calculate_pressure(data, val); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_PROCESSED: + rc = dps310_read_pressure_value(data, val); if (rc) return rc; *val2 = 1000; /* Convert Pa to KPa per IIO ABI */ return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_SCALE: + /* The raw value is in Pa, the ABI wants kPa */ + *val = 1; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: rc = dps310_get_pres_precision(data, val); if (rc) @@ -747,6 +796,7 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, } static int dps310_calculate_temp(struct dps310_data *data, int *val) + __must_hold(&data->lock) { s64 c0; s64 t; @@ -768,6 +818,21 @@ static int dps310_calculate_temp(struct dps310_data *data, int *val) return 0; } +static int dps310_read_temp_value(struct dps310_data *data, int *val) +{ + int rc; + + ACQUIRE(mutex_intr, lock)(&data->lock); + if (ACQUIRE_ERR(mutex_intr, &lock)) + return -EINTR; + + rc = dps310_read_temp_raw_locked(data); + if (rc) + return rc; + + return dps310_calculate_temp(data, val); +} + static int dps310_read_temp(struct dps310_data *data, int *val, int *val2, long mask) { @@ -782,11 +847,7 @@ static int dps310_read_temp(struct dps310_data *data, int *val, int *val2, return IIO_VAL_INT; case IIO_CHAN_INFO_PROCESSED: - rc = dps310_read_temp_raw(data); - if (rc) - return rc; - - rc = dps310_calculate_temp(data, val); + rc = dps310_read_temp_value(data, val); if (rc) return rc; @@ -804,12 +865,10 @@ static int dps310_read_temp(struct dps310_data *data, int *val, int *val2, } } -static int dps310_read_raw(struct iio_dev *iio, - struct iio_chan_spec const *chan, - int *val, int *val2, long mask) +static int dps310_read_channel(struct dps310_data *data, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) { - struct dps310_data *data = iio_priv(iio); - switch (chan->type) { case IIO_PRESSURE: return dps310_read_pressure(data, val, val2, mask); @@ -822,6 +881,87 @@ static int dps310_read_raw(struct iio_dev *iio, } } +static int dps310_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dps310_data *data = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: { + /* This consumes the measurement the capture path reads */ + IIO_DEV_ACQUIRE_DIRECT_MODE(iio, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + return dps310_read_channel(data, chan, val, val2, mask); + } + default: + return dps310_read_channel(data, chan, val, val2, mask); + } +} + +static int dps310_fill_channels(struct dps310_data *data, + const unsigned long *scan_mask, + s32 channels[2]) + __must_hold(&data->lock) +{ + unsigned int i; + int rc; + + /* Compensation needs a temperature, so it is sampled either way */ + rc = dps310_read_temp_raw_locked(data); + if (rc) + return rc; + + i = 0; + if (test_bit(DPS310_SCAN_TEMP, scan_mask)) { + /* Millidegrees Celsius */ + rc = dps310_calculate_temp(data, &channels[i++]); + if (rc) + return rc; + } + + if (test_bit(DPS310_SCAN_PRESSURE, scan_mask)) { + rc = dps310_read_pres_raw_locked(data); + if (rc) + return rc; + + /* Pascals, see the channel definition */ + rc = dps310_calculate_pressure(data, &channels[i++]); + if (rc) + return rc; + } + + return 0; +} + +static irqreturn_t dps310_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct dps310_data *data = iio_priv(iio); + struct { + s32 channels[2]; + aligned_s64 timestamp; + } scan = { }; + int rc; + + mutex_lock(&data->lock); + rc = dps310_fill_channels(data, iio->active_scan_mask, scan.channels); + mutex_unlock(&data->lock); + if (rc) + goto err; + + iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan), iio_get_time_ns(iio)); + +err: + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + static void dps310_reset(void *action_data) { struct dps310_data *data = action_data; @@ -845,11 +985,12 @@ static const struct iio_info dps310_info = { static int dps310_probe(struct i2c_client *client) { + struct device *dev = &client->dev; struct dps310_data *data; struct iio_dev *iio; int rc; - iio = devm_iio_device_alloc(&client->dev, sizeof(*data)); + iio = devm_iio_device_alloc(dev, sizeof(*data)); if (!iio) return -ENOMEM; @@ -868,7 +1009,7 @@ static int dps310_probe(struct i2c_client *client) return PTR_ERR(data->regmap); /* Register to run the device reset when the device is removed */ - rc = devm_add_action_or_reset(&client->dev, dps310_reset, data); + rc = devm_add_action_or_reset(dev, dps310_reset, data); if (rc) return rc; @@ -876,7 +1017,17 @@ static int dps310_probe(struct i2c_client *client) if (rc) return rc; - rc = devm_iio_device_register(&client->dev, iio); + /* + * The device measures continuously in background mode, so a capture is + * just a read of the latest results. The trigger is not aligned with + * the measurements, so the timestamp is taken in the handler. + */ + rc = devm_iio_triggered_buffer_setup(dev, iio, NULL, + dps310_trigger_handler, NULL); + if (rc) + return rc; + + rc = devm_iio_device_register(dev, iio); if (rc) return rc; diff --git a/drivers/iio/pressure/rohm-bm1390.c b/drivers/iio/pressure/rohm-bm1390.c index f9deb48e6a41..7a6a16cffb66 100644 --- a/drivers/iio/pressure/rohm-bm1390.c +++ b/drivers/iio/pressure/rohm-bm1390.c @@ -267,7 +267,7 @@ static int bm1390_read_data(struct bm1390_data *data, guard(mutex)(&data->mutex); /* * We use 'continuous mode' even for raw read because according to the - * data-sheet an one-shot mode can't be used with IIR filter. + * data-sheet a one-shot mode can't be used with IIR filter. */ ret = bm1390_meas_set(data, BM1390_MEAS_MODE_CONTINUOUS); if (ret) @@ -479,6 +479,7 @@ static const struct iio_info bm1390_info = { static int bm1390_chip_init(struct bm1390_data *data) { + u8 regval; int ret; ret = regmap_write_bits(data->regmap, BM1390_REG_POWER, @@ -512,8 +513,9 @@ static int bm1390_chip_init(struct bm1390_data *data) * Default to use IIR filter in "middle" mode. Also the AVE_NUM must * be fixed when IIR is in use. */ + regval = FIELD_PREP(BM1390_MASK_AVE_NUM, BM1390_IIR_AVE_NUM); ret = regmap_update_bits(data->regmap, BM1390_REG_MODE_CTRL, - BM1390_MASK_AVE_NUM, BM1390_IIR_AVE_NUM); + BM1390_MASK_AVE_NUM, regval); if (ret) return ret; diff --git a/drivers/iio/proximity/aw96103.c b/drivers/iio/proximity/aw96103.c index 8352d51e5128..e3ab55170b6c 100644 --- a/drivers/iio/proximity/aw96103.c +++ b/drivers/iio/proximity/aw96103.c @@ -687,7 +687,7 @@ static irqreturn_t aw96103_irq(int irq, void *data) iio_get_time_ns(indio_dev)); break; default: - return IRQ_HANDLED; + continue; } aw96103->channels_arr[i].old_irq_status = curr_status; } diff --git a/drivers/iio/temperature/iqs620at-temp.c b/drivers/iio/temperature/iqs620at-temp.c index e2f878d57af7..3a3b1f256585 100644 --- a/drivers/iio/temperature/iqs620at-temp.c +++ b/drivers/iio/temperature/iqs620at-temp.c @@ -5,13 +5,18 @@ * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com> */ +#include <linux/array_size.h> +#include <linux/bits.h> #include <linux/device.h> -#include <linux/iio/iio.h> -#include <linux/kernel.h> #include <linux/mfd/iqs62x.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/regmap.h> +#include <linux/types.h> + +#include <asm/byteorder.h> + +#include <linux/iio/iio.h> #define IQS620_TEMP_UI_OUT 0x1A diff --git a/drivers/iio/temperature/ltc2983.c b/drivers/iio/temperature/ltc2983.c index 6efb5252a773..6655453c9abf 100644 --- a/drivers/iio/temperature/ltc2983.c +++ b/drivers/iio/temperature/ltc2983.c @@ -5,23 +5,34 @@ * * Copyright 2019 Analog Devices Inc. */ + +#include <linux/array_size.h> #include <linux/bitfield.h> +#include <linux/bitops.h> #include <linux/completion.h> +#include <linux/container_of.h> +#include <linux/delay.h> #include <linux/device.h> #include <linux/err.h> -#include <linux/errno.h> -#include <linux/kernel.h> -#include <linux/iio/iio.h> +#include <linux/gpio/consumer.h> #include <linux/interrupt.h> #include <linux/list.h> +#include <linux/math64.h> #include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> #include <linux/property.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> +#include <linux/slab.h> #include <linux/spi/spi.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/unaligned.h> #include <asm/byteorder.h> -#include <linux/unaligned.h> + +#include <linux/iio/iio.h> /* register map */ #define LTC2983_STATUS_REG 0x0000 diff --git a/drivers/iio/temperature/mlx90632.c b/drivers/iio/temperature/mlx90632.c index 2fde48f337e2..74ae36f74e8b 100644 --- a/drivers/iio/temperature/mlx90632.c +++ b/drivers/iio/temperature/mlx90632.c @@ -6,21 +6,25 @@ * * Driver for the Melexis MLX90632 I2C 16-bit IR thermopile sensor */ + +#include <linux/array_size.h> #include <linux/bitfield.h> +#include <linux/bits.h> #include <linux/delay.h> +#include <linux/dev_printk.h> #include <linux/device.h> #include <linux/err.h> -#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/iopoll.h> #include <linux/jiffies.h> -#include <linux/kernel.h> #include <linux/limits.h> -#include <linux/module.h> #include <linux/math64.h> +#include <linux/module.h> +#include <linux/mutex.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> +#include <linux/types.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> diff --git a/drivers/iio/temperature/mlx90635.c b/drivers/iio/temperature/mlx90635.c index 0f31879a4f64..48c760c980f3 100644 --- a/drivers/iio/temperature/mlx90635.c +++ b/drivers/iio/temperature/mlx90635.c @@ -6,21 +6,24 @@ * * Driver for the Melexis MLX90635 I2C 16-bit IR thermopile sensor */ + +#include <linux/array_size.h> #include <linux/bitfield.h> +#include <linux/bits.h> #include <linux/delay.h> +#include <linux/dev_printk.h> #include <linux/device.h> #include <linux/err.h> -#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/iopoll.h> #include <linux/jiffies.h> -#include <linux/kernel.h> -#include <linux/limits.h> -#include <linux/module.h> #include <linux/math64.h> +#include <linux/module.h> +#include <linux/mutex.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> +#include <linux/types.h> #include <linux/iio/iio.h> diff --git a/drivers/iio/temperature/tmp117.c b/drivers/iio/temperature/tmp117.c index 74cb8d62bef3..2d0e79416434 100644 --- a/drivers/iio/temperature/tmp117.c +++ b/drivers/iio/temperature/tmp117.c @@ -10,16 +10,17 @@ */ #include <linux/array_size.h> +#include <linux/bitops.h> #include <linux/delay.h> +#include <linux/dev_printk.h> #include <linux/err.h> #include <linux/i2c.h> -#include <linux/module.h> -#include <linux/bitops.h> -#include <linux/types.h> -#include <linux/kernel.h> #include <linux/limits.h> +#include <linux/minmax.h> +#include <linux/module.h> #include <linux/property.h> #include <linux/regulator/consumer.h> +#include <linux/types.h> #include <linux/iio/iio.h> @@ -97,15 +98,20 @@ static int tmp117_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec { struct tmp117_data *data = iio_priv(indio_dev); s16 off; + int ret; switch (mask) { case IIO_CHAN_INFO_CALIBBIAS: off = clamp_t(int, val, S16_MIN, S16_MAX); if (off == data->calibbias) return 0; + + ret = i2c_smbus_write_word_swapped(data->client, TMP117_REG_TEMP_OFFSET, off); + if (ret) + return ret; + data->calibbias = off; - return i2c_smbus_write_word_swapped(data->client, - TMP117_REG_TEMP_OFFSET, off); + return 0; default: return -EINVAL; diff --git a/drivers/iio/temperature/tsys01.c b/drivers/iio/temperature/tsys01.c index 28b3ce022ce7..b19b4a3ef038 100644 --- a/drivers/iio/temperature/tsys01.c +++ b/drivers/iio/temperature/tsys01.c @@ -8,14 +8,19 @@ * http://www.meas-spec.com/downloads/TSYS01_Digital_Temperature_Sensor.pdf */ -#include <linux/iio/iio.h> -#include <linux/iio/sysfs.h> +#include <linux/array_size.h> +#include <linux/bits.h> #include <linux/device.h> -#include <linux/mutex.h> +#include <linux/i2c.h> +#include <linux/math64.h> #include <linux/module.h> -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/stat.h> +#include <linux/mutex.h> +#include <linux/sprintf.h> +#include <linux/types.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + #include "../common/ms_sensors/ms_sensors_i2c.h" /* TSYS01 Commands */ diff --git a/drivers/iio/temperature/tsys02d.c b/drivers/iio/temperature/tsys02d.c index 3ef72347456e..2c67faebd90c 100644 --- a/drivers/iio/temperature/tsys02d.c +++ b/drivers/iio/temperature/tsys02d.c @@ -10,11 +10,17 @@ * http://www.meas-spec.com/downloads/Digital_Sensor_TSYS02D.pdf */ -#include <linux/init.h> +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/dev_printk.h> #include <linux/device.h> -#include <linux/kernel.h> -#include <linux/stat.h> +#include <linux/i2c.h> #include <linux/module.h> +#include <linux/mutex.h> +#include <linux/stat.h> +#include <linux/sysfs.h> +#include <linux/types.h> + #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig index 4fc17dd0dcd7..04930b4fdd5d 100644 --- a/drivers/iio/test/Kconfig +++ b/drivers/iio/test/Kconfig @@ -4,6 +4,19 @@ # # Keep in alphabetical order +config IIO_CHANNEL_PREFIX_KUNIT_TEST + tristate "Test IIO channel prefix" if !KUNIT_ALL_TESTS + depends on KUNIT && IIO + default KUNIT_ALL_TESTS + help + Build unit tests for __iio_chan_prefix_emit(), the helper that + builds IIO sysfs attribute name prefixes from an iio_chan_spec. + + For more information on KUnit and unit tests in general, please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + config IIO_GTS_KUNIT_TEST tristate "Test IIO gain-time-scale helpers" if !KUNIT_ALL_TESTS depends on KUNIT diff --git a/drivers/iio/test/Makefile b/drivers/iio/test/Makefile index 0c846bc21acd..ba08afa7f3f2 100644 --- a/drivers/iio/test/Makefile +++ b/drivers/iio/test/Makefile @@ -4,6 +4,7 @@ # # Keep in alphabetical order +obj-$(CONFIG_IIO_CHANNEL_PREFIX_KUNIT_TEST) += iio-test-channel-prefix.o obj-$(CONFIG_IIO_RESCALE_KUNIT_TEST) += iio-test-rescale.o obj-$(CONFIG_IIO_FORMAT_KUNIT_TEST) += iio-test-format.o obj-$(CONFIG_IIO_GTS_KUNIT_TEST) += iio-test-gts.o diff --git a/drivers/iio/test/iio-test-channel-prefix.c b/drivers/iio/test/iio-test-channel-prefix.c new file mode 100644 index 000000000000..44c838fcd8d6 --- /dev/null +++ b/drivers/iio/test/iio-test-channel-prefix.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Unit tests for IIO channel prefix generation. + */ + +#include <kunit/test.h> +#include <kunit/visibility.h> + +#include <linux/iio/iio.h> +#include <linux/limits.h> +#include <linux/string.h> + +#include "../iio_core.h" + +#define PREFIX_BUF_SIZE (NAME_MAX + 1) + +#define EXPECT_PREFIX(_test, _buf, _ret, _expected) do { \ + struct kunit *__test = (_test); \ + const char *__expected = (_expected); \ + \ + KUNIT_EXPECT_EQ(__test, (_ret), (ssize_t)strlen(__expected)); \ + KUNIT_EXPECT_STREQ(__test, (_buf), __expected); \ + } while (0) + +static char *iio_test_prefix_alloc(struct kunit *test) +{ + char *buf = kunit_kzalloc(test, PREFIX_BUF_SIZE, GFP_KERNEL); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + return buf; +} + +static void iio_test_prefix_shared_by_all(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + }; + const struct iio_chan_spec chan_noisy = { + .type = IIO_ACCEL, + .output = 1, + .indexed = 1, + .modified = 1, + .channel = 7, + .channel2 = IIO_MOD_Z, + .extend_name = "supply", + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SHARED_BY_ALL, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, ""); + + ret = __iio_chan_prefix_emit(NULL, &chan_noisy, IIO_SHARED_BY_ALL, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, ""); +} + +static void iio_test_prefix_shared_by_dir(struct kunit *test) +{ + const struct iio_chan_spec chan_in = { + .type = IIO_VOLTAGE, + .output = 0, + }; + const struct iio_chan_spec chan_out = { + .type = IIO_VOLTAGE, + .output = 1, + }; + const struct iio_chan_spec chan_in_noisy = { + .type = IIO_ACCEL, + .indexed = 1, + .modified = 1, + .channel = 5, + .channel2 = IIO_MOD_Y, + .extend_name = "supply", + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan_in, IIO_SHARED_BY_DIR, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in"); + + ret = __iio_chan_prefix_emit(NULL, &chan_out, IIO_SHARED_BY_DIR, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "out"); + + ret = __iio_chan_prefix_emit(NULL, &chan_in_noisy, IIO_SHARED_BY_DIR, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in"); +} + +static void iio_test_prefix_shared_by_type(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + }; + const struct iio_chan_spec chan_diff = { + .type = IIO_VOLTAGE, + .differential = 1, + }; + const struct iio_chan_spec chan_noisy = { + .type = IIO_VOLTAGE, + .indexed = 1, + .modified = 1, + .channel = 4, + .channel2 = IIO_MOD_X, + .extend_name = "supply", + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SHARED_BY_TYPE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_voltage"); + + ret = __iio_chan_prefix_emit(NULL, &chan_diff, IIO_SHARED_BY_TYPE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_voltage-voltage"); + + ret = __iio_chan_prefix_emit(NULL, &chan_noisy, IIO_SHARED_BY_TYPE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_voltage"); +} + +static void iio_test_prefix_separate_simple(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_TEMP, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_temp"); +} + +static void iio_test_prefix_separate_indexed(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 3, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_voltage3"); +} + +static void iio_test_prefix_separate_indexed_diff(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + .indexed = 1, + .differential = 1, + .channel = 0, + .channel2 = 1, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_voltage0-voltage1"); +} + +static void iio_test_prefix_separate_modified(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_accel_x"); +} + +static void iio_test_prefix_separate_indexed_modified(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_ACCEL, + .indexed = 1, + .modified = 1, + .channel = 2, + .channel2 = IIO_MOD_Y, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_accel2_y"); +} + +static void iio_test_prefix_separate_extend_name(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 2, + .extend_name = "supply", + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "in_voltage2_supply"); +} + +static void iio_test_prefix_output_separate(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + .output = 1, + .indexed = 1, + .channel = 0, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + EXPECT_PREFIX(test, buf, ret, "out_voltage0"); +} + +static void iio_test_prefix_diff_unindexed_fails(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + .differential = 1, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +static void iio_test_prefix_diff_modified_fails(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + .indexed = 1, + .differential = 1, + .modified = 1, + .channel = 0, + .channel2 = 1, + }; + char *buf = iio_test_prefix_alloc(test); + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SEPARATE, + buf, PREFIX_BUF_SIZE); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +static void iio_test_prefix_overflow(struct kunit *test) +{ + const struct iio_chan_spec chan = { + .type = IIO_VOLTAGE, + }; + char small[4]; + ssize_t ret; + + ret = __iio_chan_prefix_emit(NULL, &chan, IIO_SHARED_BY_TYPE, + small, sizeof(small)); + KUNIT_EXPECT_EQ(test, ret, -EOVERFLOW); +} + +static struct kunit_case iio_chan_prefix_test_cases[] = { + KUNIT_CASE(iio_test_prefix_shared_by_all), + KUNIT_CASE(iio_test_prefix_shared_by_dir), + KUNIT_CASE(iio_test_prefix_shared_by_type), + KUNIT_CASE(iio_test_prefix_separate_simple), + KUNIT_CASE(iio_test_prefix_separate_indexed), + KUNIT_CASE(iio_test_prefix_separate_indexed_diff), + KUNIT_CASE(iio_test_prefix_separate_modified), + KUNIT_CASE(iio_test_prefix_separate_indexed_modified), + KUNIT_CASE(iio_test_prefix_separate_extend_name), + KUNIT_CASE(iio_test_prefix_output_separate), + KUNIT_CASE(iio_test_prefix_diff_unindexed_fails), + KUNIT_CASE(iio_test_prefix_diff_modified_fails), + KUNIT_CASE(iio_test_prefix_overflow), + { } +}; + +static struct kunit_suite iio_channel_prefix_test_suite = { + .name = "iio-channel-prefix", + .test_cases = iio_chan_prefix_test_cases, +}; + +kunit_test_suite(iio_channel_prefix_test_suite); + +MODULE_AUTHOR("Rodrigo Alencar <rodrigo.alencar@analog.com>"); +MODULE_DESCRIPTION("Test IIO channel prefix generation"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); diff --git a/drivers/iio/test/iio-test-rescale.c b/drivers/iio/test/iio-test-rescale.c index bcd4a607be19..de4a09f432c0 100644 --- a/drivers/iio/test/iio-test-rescale.c +++ b/drivers/iio/test/iio-test-rescale.c @@ -587,7 +587,7 @@ KUNIT_ARRAY_PARAM(iio_rescale_offset, offset_cases, case_to_desc); * @str: The string to parse * @nano: The number as an integer * - * Returns 0 on success, or a negative error code if the string cound not be + * Returns 0 on success, or a negative error code if the string could not be * parsed. */ static int iio_str_to_nano(const char *str, s64 *nano) @@ -617,7 +617,7 @@ static int iio_str_to_nano(const char *str, s64 *nano) * @real_str: The real value as a string * @exp_str: The expected value as a string * - * Returns a negative error code if the strings cound not be parsed, or the + * Returns a negative error code if the strings could not be parsed, or the * relative error in parts-per-million. */ static int iio_test_relative_error_ppm(const char *real_str, const char *exp_str) diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c index 4aa991a54101..802f07e806d5 100644 --- a/drivers/interconnect/core.c +++ b/drivers/interconnect/core.c @@ -423,6 +423,17 @@ static void devm_icc_release(struct device *dev, void *res) icc_put(*(struct icc_path **)res); } +/** + * devm_of_icc_get() - get a path handle from a DT node based on name + * @dev: device pointer for the consumer device + * @name: interconnect path name + * + * This function is the resource managed version of of_icc_get(). The path + * is released with icc_put() automatically when @dev is unbound. + * + * Return: icc_path pointer on success or ERR_PTR() on error. NULL is returned + * when the API is disabled or the "interconnects" DT property is missing. + */ struct icc_path *devm_of_icc_get(struct device *dev, const char *name) { struct icc_path **ptr, *path; @@ -786,12 +797,33 @@ static int __icc_enable(struct icc_path *path, bool enable) path->reqs[0].peak_bw); } +/** + * icc_enable() - enable a path + * @path: interconnect path + * + * Mark all requests on the path as enabled and reapply the bandwidth that + * was last set with icc_set_bw(). + * Passing a NULL @path is a no-op and returns 0. + * + * Return: 0 on success, or an appropriate error code otherwise. + */ int icc_enable(struct icc_path *path) { return __icc_enable(path, true); } EXPORT_SYMBOL_GPL(icc_enable); +/** + * icc_disable() - disable a path + * @path: interconnect path + * + * Mark all requests on the path as disabled, so that they no longer count + * towards the aggregated bandwidth, and reapply the constraints. The + * requested bandwidth is kept and restored by icc_enable(). + * Passing a NULL @path is a no-op and returns 0. + * + * Return: 0 on success, or an appropriate error code otherwise. + */ int icc_disable(struct icc_path *path) { return __icc_enable(path, false); diff --git a/drivers/interconnect/mediatek/Makefile b/drivers/interconnect/mediatek/Makefile index 6bd656668f5d..64170ab163ff 100644 --- a/drivers/interconnect/mediatek/Makefile +++ b/drivers/interconnect/mediatek/Makefile @@ -3,4 +3,4 @@ obj-$(CONFIG_INTERCONNECT_MTK_DVFSRC_EMI) += icc-emi.o obj-$(CONFIG_INTERCONNECT_MTK_MT8183) += mt8183.o obj-$(CONFIG_INTERCONNECT_MTK_MT8195) += mt8195.o -obj-$(CONFIG_INTERCONNECT_MTK_MT8195) += mt8196.o +obj-$(CONFIG_INTERCONNECT_MTK_MT8196) += mt8196.o diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig index b3187acf90b8..7f3a977e8f10 100644 --- a/drivers/interconnect/qcom/Kconfig +++ b/drivers/interconnect/qcom/Kconfig @@ -2,6 +2,7 @@ config INTERCONNECT_QCOM tristate "Qualcomm Network-on-Chip interconnect drivers" depends on ARCH_QCOM || COMPILE_TEST + default ARCH_QCOM help Support for Qualcomm's Network-on-Chip interconnect hardware. @@ -14,6 +15,7 @@ config INTERCONNECT_QCOM_ELIZA depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on Eliza-based platforms. @@ -24,6 +26,7 @@ config INTERCONNECT_QCOM_GLYMUR depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on glymur-based platforms. @@ -31,8 +34,10 @@ config INTERCONNECT_QCOM_GLYMUR config INTERCONNECT_QCOM_HAWI tristate "Qualcomm HAWI interconnect driver" depends on INTERCONNECT_QCOM_RPMH_POSSIBLE + depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on hawi-based platforms. @@ -43,13 +48,28 @@ config INTERCONNECT_QCOM_KAANAPALI depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on kaanapali-based platforms. +config INTERCONNECT_QCOM_KUNO + tristate "Qualcomm Kuno interconnect driver" + depends on INTERCONNECT_QCOM_RPMH_POSSIBLE + depends on ARM || COMPILE_TEST + select INTERCONNECT_QCOM_RPMH + select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM + help + This is a driver for the Qualcomm Network-on-Chip on kuno-based + platforms. The interconnect provider collects and aggregates the + consumer bandwidth requests to satisfy constraints placed on the + Network-on-Chip performance states. + config INTERCONNECT_QCOM_MAILI tristate "Qualcomm MAILI interconnect driver" depends on INTERCONNECT_QCOM_RPMH_POSSIBLE + depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER default ARCH_QCOM @@ -72,6 +92,7 @@ config INTERCONNECT_QCOM_MSM8916 depends on INTERCONNECT_QCOM depends on QCOM_SMD_RPM select INTERCONNECT_QCOM_SMD_RPM + default ARCH_QCOM if ARM64 help This is a driver for the Qualcomm Network-on-Chip on msm8916-based platforms. @@ -100,6 +121,7 @@ config INTERCONNECT_QCOM_MSM8953 depends on QCOM_SMD_RPM depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_SMD_RPM + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on msm8953-based platforms. @@ -110,6 +132,7 @@ config INTERCONNECT_QCOM_MSM8974 depends on QCOM_SMD_RPM depends on ARM || COMPILE_TEST select INTERCONNECT_QCOM_SMD_RPM + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on msm8974-based platforms. @@ -130,6 +153,7 @@ config INTERCONNECT_QCOM_MSM8996 depends on QCOM_SMD_RPM depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_SMD_RPM + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on msm8996-based platforms. @@ -137,8 +161,10 @@ config INTERCONNECT_QCOM_MSM8996 config INTERCONNECT_QCOM_NORD tristate "Qualcomm Nord interconnect driver" depends on INTERCONNECT_QCOM_RPMH_POSSIBLE + depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on Nord-based platforms. The topology consists of several NoCs controlled by @@ -149,6 +175,7 @@ config INTERCONNECT_QCOM_OSM_L3 tristate "Qualcomm OSM L3 interconnect driver" depends on INTERCONNECT_QCOM || COMPILE_TEST depends on ARM64 || COMPILE_TEST + default ARCH_QCOM help Say y here to support the Operating State Manager (OSM) interconnect driver which controls the scaling of L3 caches on Qualcomm SoCs. @@ -159,6 +186,7 @@ config INTERCONNECT_QCOM_QCM2290 depends on QCOM_SMD_RPM depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_SMD_RPM + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on qcm2290-based platforms. @@ -169,6 +197,7 @@ config INTERCONNECT_QCOM_QCS404 depends on QCOM_SMD_RPM depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_SMD_RPM + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on qcs404-based platforms. @@ -179,6 +208,7 @@ config INTERCONNECT_QCOM_QCS615 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on qcs615-based platforms. @@ -189,6 +219,7 @@ config INTERCONNECT_QCOM_QCS8300 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Technologies, Inc. Network-on-Chip on QCS8300-based platforms. The interconnect provider collects and @@ -201,6 +232,7 @@ config INTERCONNECT_QCOM_QDU1000 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on QDU1000-based and QRU1000-based platforms. @@ -225,6 +257,7 @@ config INTERCONNECT_QCOM_SA8775P depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sa8775p-based platforms. @@ -235,6 +268,7 @@ config INTERCONNECT_QCOM_SAR2130P depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on SAR2130P-based platforms. @@ -245,6 +279,7 @@ config INTERCONNECT_QCOM_SC7180 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sc7180-based platforms. @@ -255,6 +290,7 @@ config INTERCONNECT_QCOM_SC7280 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sc7280-based platforms. @@ -265,6 +301,7 @@ config INTERCONNECT_QCOM_SC8180X depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sc8180x-based platforms. @@ -275,6 +312,7 @@ config INTERCONNECT_QCOM_SC8280XP depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on SC8280XP-based platforms. @@ -285,6 +323,7 @@ config INTERCONNECT_QCOM_SDM660 depends on ARM64 || COMPILE_TEST depends on QCOM_SMD_RPM select INTERCONNECT_QCOM_SMD_RPM + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sdm660-based platforms. @@ -295,6 +334,7 @@ config INTERCONNECT_QCOM_SDM670 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sdm670-based platforms. @@ -305,6 +345,7 @@ config INTERCONNECT_QCOM_SDM845 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sdm845-based platforms. @@ -315,6 +356,7 @@ config INTERCONNECT_QCOM_SDX55 depends on ARM || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sdx55-based platforms. @@ -335,6 +377,7 @@ config INTERCONNECT_QCOM_SDX75 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sdx75-based platforms. @@ -343,6 +386,8 @@ config INTERCONNECT_QCOM_SHIKRA tristate "Qualcomm SHIKRA interconnect driver" depends on INTERCONNECT_QCOM depends on QCOM_SMD_RPM + depends on ARM64 || COMPILE_TEST + default ARCH_QCOM select INTERCONNECT_QCOM_SMD_RPM help This is a driver for the Qualcomm Network-on-Chip on shikra-based @@ -354,6 +399,7 @@ config INTERCONNECT_QCOM_SM6115 depends on QCOM_SMD_RPM depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_SMD_RPM + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sm6115-based platforms. @@ -364,6 +410,7 @@ config INTERCONNECT_QCOM_SM6350 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sm6350-based platforms. @@ -374,6 +421,7 @@ config INTERCONNECT_QCOM_SM7150 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sm7150-based platforms. @@ -384,6 +432,7 @@ config INTERCONNECT_QCOM_MILOS depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on Milos-based platforms. @@ -394,6 +443,7 @@ config INTERCONNECT_QCOM_SM8150 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sm8150-based platforms. @@ -404,6 +454,7 @@ config INTERCONNECT_QCOM_SM8250 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on sm8250-based platforms. @@ -414,6 +465,7 @@ config INTERCONNECT_QCOM_SM8350 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on SM8350-based platforms. @@ -424,6 +476,7 @@ config INTERCONNECT_QCOM_SM8450 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on SM8450-based platforms. @@ -434,6 +487,7 @@ config INTERCONNECT_QCOM_SM8550 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on SM8550-based platforms. @@ -444,6 +498,7 @@ config INTERCONNECT_QCOM_SM8650 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on SM8650-based platforms. @@ -454,6 +509,7 @@ config INTERCONNECT_QCOM_SM8750 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on SM8750-based platforms. @@ -464,6 +520,7 @@ config INTERCONNECT_QCOM_X1E80100 depends on ARM64 || COMPILE_TEST select INTERCONNECT_QCOM_RPMH select INTERCONNECT_QCOM_BCM_VOTER + default ARCH_QCOM help This is a driver for the Qualcomm Network-on-Chip on X1E80100-based platforms. diff --git a/drivers/interconnect/qcom/Makefile b/drivers/interconnect/qcom/Makefile index 5f139a370bbe..a71478aaf591 100644 --- a/drivers/interconnect/qcom/Makefile +++ b/drivers/interconnect/qcom/Makefile @@ -8,6 +8,7 @@ qnoc-eliza-objs := eliza.o qnoc-glymur-objs := glymur.o qnoc-hawi-objs := hawi.o qnoc-kaanapali-objs := kaanapali.o +qnoc-kuno-objs := kuno.o qnoc-maili-objs := maili.o qnoc-milos-objs := milos.o qnoc-msm8909-objs := msm8909.o @@ -57,6 +58,7 @@ obj-$(CONFIG_INTERCONNECT_QCOM_ELIZA) += qnoc-eliza.o obj-$(CONFIG_INTERCONNECT_QCOM_GLYMUR) += qnoc-glymur.o obj-$(CONFIG_INTERCONNECT_QCOM_HAWI) += qnoc-hawi.o obj-$(CONFIG_INTERCONNECT_QCOM_KAANAPALI) += qnoc-kaanapali.o +obj-$(CONFIG_INTERCONNECT_QCOM_KUNO) += qnoc-kuno.o obj-$(CONFIG_INTERCONNECT_QCOM_MAILI) += qnoc-maili.o obj-$(CONFIG_INTERCONNECT_QCOM_MILOS) += qnoc-milos.o obj-$(CONFIG_INTERCONNECT_QCOM_MSM8909) += qnoc-msm8909.o diff --git a/drivers/interconnect/qcom/bcm-voter.c b/drivers/interconnect/qcom/bcm-voter.c index a2d437a05a11..0880a287b0c0 100644 --- a/drivers/interconnect/qcom/bcm-voter.c +++ b/drivers/interconnect/qcom/bcm-voter.c @@ -3,7 +3,6 @@ * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */ -#include <asm/div64.h> #include <linux/interconnect-provider.h> #include <linux/list_sort.h> #include <linux/module.h> @@ -14,6 +13,7 @@ #include <soc/qcom/tcs.h> #include "bcm-voter.h" +#include "icc-common.h" #include "icc-rpmh.h" static LIST_HEAD(bcm_voters); @@ -47,17 +47,6 @@ static int cmp_vcd(void *priv, const struct list_head *a, const struct list_head return bcm_a->aux_data.vcd - bcm_b->aux_data.vcd; } -static u64 bcm_div(u64 num, u32 base) -{ - /* Ensure that small votes aren't lost. */ - if (num && num < base) - return 1; - - do_div(num, base); - - return num; -} - /* BCMs with enable_mask use one-hot-encoding for on/off signaling */ static void bcm_aggregate_mask(struct qcom_icc_bcm *bcm) { @@ -99,20 +88,20 @@ static void bcm_aggregate(struct qcom_icc_bcm *bcm) for (bucket = 0; bucket < QCOM_ICC_NUM_BUCKETS; bucket++) { for (i = 0; i < bcm->num_nodes; i++) { node = bcm->nodes[i]; - temp = bcm_div(node->sum_avg[bucket] * bcm->aux_data.width, - node->buswidth * node->channels); + temp = qcom_bw_div(node->sum_avg[bucket] * le16_to_cpu(bcm->aux_data.width), + node->buswidth * node->channels); agg_avg[bucket] = max(agg_avg[bucket], temp); - temp = bcm_div(node->max_peak[bucket] * bcm->aux_data.width, - node->buswidth); + temp = qcom_bw_div(node->max_peak[bucket] * le16_to_cpu(bcm->aux_data.width), + node->buswidth); agg_peak[bucket] = max(agg_peak[bucket], temp); } temp = agg_avg[bucket] * bcm->vote_scale; - bcm->vote_x[bucket] = bcm_div(temp, bcm->aux_data.unit); + bcm->vote_x[bucket] = qcom_bw_div(temp, le32_to_cpu(bcm->aux_data.unit)); temp = agg_peak[bucket] * bcm->vote_scale; - bcm->vote_y[bucket] = bcm_div(temp, bcm->aux_data.unit); + bcm->vote_y[bucket] = qcom_bw_div(temp, le32_to_cpu(bcm->aux_data.unit)); } if (bcm->keepalive && bcm->vote_x[QCOM_ICC_BUCKET_AMC] == 0 && @@ -262,6 +251,40 @@ void qcom_icc_bcm_voter_add(struct bcm_voter *voter, struct qcom_icc_bcm *bcm) EXPORT_SYMBOL_GPL(qcom_icc_bcm_voter_add); /** + * qcom_icc_bcm_get_bw - get current bcm vote + * @voter: voter used to query bcm + * @bcm: bcm to get current vote from + */ +void qcom_icc_bcm_get_bw(struct bcm_voter *voter, + struct qcom_icc_bcm *bcm) +{ + struct tcs_cmd cmd = { .addr = bcm->addr }; + int ret, i; + u64 x, y; + + mutex_lock(&voter->lock); + + ret = rpmh_read(voter->dev, &cmd); + if (ret) { + pr_err("Error sending AMC RPMH requests (%d)\n", ret); + goto out; + } + + x = FIELD_GET(BCM_TCS_CMD_VOTE_X_MASK, cmd.data); + y = FIELD_GET(BCM_TCS_CMD_VOTE_Y_MASK, cmd.data); + + /* For boot-up, fill the AMC vote in all buckets */ + for (i = 0; i < QCOM_ICC_NUM_BUCKETS; i++) { + bcm->vote_x[i] = x; + bcm->vote_y[i] = y; + } + +out: + mutex_unlock(&voter->lock); +} +EXPORT_SYMBOL_GPL(qcom_icc_bcm_get_bw); + +/** * qcom_icc_bcm_voter_commit - generates and commits tcs cmds based on bcms * @voter: voter that needs flushing * diff --git a/drivers/interconnect/qcom/bcm-voter.h b/drivers/interconnect/qcom/bcm-voter.h index b4d36e349f3c..fc75d457dcc7 100644 --- a/drivers/interconnect/qcom/bcm-voter.h +++ b/drivers/interconnect/qcom/bcm-voter.h @@ -13,6 +13,7 @@ #include "icc-rpmh.h" struct bcm_voter *of_bcm_voter_get(struct device *dev, const char *name); +void qcom_icc_bcm_get_bw(struct bcm_voter *voter, struct qcom_icc_bcm *bcm); void qcom_icc_bcm_voter_add(struct bcm_voter *voter, struct qcom_icc_bcm *bcm); int qcom_icc_bcm_voter_commit(struct bcm_voter *voter); diff --git a/drivers/interconnect/qcom/eliza.c b/drivers/interconnect/qcom/eliza.c index 891e4e6e8ba8..ad03adef79ba 100644 --- a/drivers/interconnect/qcom/eliza.c +++ b/drivers/interconnect/qcom/eliza.c @@ -451,7 +451,7 @@ static struct qcom_icc_node qns_gem_noc_cnoc = { .link_nodes = { &qnm_gemnoc_cnoc }, }; -static struct qcom_icc_qosbox alm_gpu_tcu_qos = { +static const struct qcom_icc_qosbox alm_gpu_tcu_qos = { .num_ports = 1, .port_offsets = { 0x155000 }, .prio = 1, @@ -468,7 +468,7 @@ static struct qcom_icc_node alm_gpu_tcu = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox alm_sys_tcu_qos = { +static const struct qcom_icc_qosbox alm_sys_tcu_qos = { .num_ports = 1, .port_offsets = { 0x157000 }, .prio = 6, @@ -494,7 +494,7 @@ static struct qcom_icc_node chm_apps = { &qns_pcie }, }; -static struct qcom_icc_qosbox qnm_gpu_qos = { +static const struct qcom_icc_qosbox qnm_gpu_qos = { .num_ports = 2, .port_offsets = { 0x31000, 0xb1000 }, .prio = 0, @@ -511,7 +511,7 @@ static struct qcom_icc_node qnm_gpu = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_lpass_gemnoc_qos = { +static const struct qcom_icc_qosbox qnm_lpass_gemnoc_qos = { .num_ports = 1, .port_offsets = { 0x159000 }, .prio = 0, @@ -538,7 +538,7 @@ static struct qcom_icc_node qnm_mdsp = { &qns_pcie }, }; -static struct qcom_icc_qosbox qnm_mnoc_hf_qos = { +static const struct qcom_icc_qosbox qnm_mnoc_hf_qos = { .num_ports = 2, .port_offsets = { 0x33000, 0xb3000 }, .prio = 0, @@ -555,7 +555,7 @@ static struct qcom_icc_node qnm_mnoc_hf = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_mnoc_sf_qos = { +static const struct qcom_icc_qosbox qnm_mnoc_sf_qos = { .num_ports = 2, .port_offsets = { 0x35000, 0xb5000 }, .prio = 0, @@ -572,7 +572,7 @@ static struct qcom_icc_node qnm_mnoc_sf = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_nsp_gemnoc_qos = { +static const struct qcom_icc_qosbox qnm_nsp_gemnoc_qos = { .num_ports = 2, .port_offsets = { 0x37000, 0xb7000 }, .prio = 0, @@ -590,7 +590,7 @@ static struct qcom_icc_node qnm_nsp_gemnoc = { &qns_pcie }, }; -static struct qcom_icc_qosbox qnm_pcie_qos = { +static const struct qcom_icc_qosbox qnm_pcie_qos = { .num_ports = 1, .port_offsets = { 0x15b000 }, .prio = 2, @@ -688,7 +688,7 @@ static struct qcom_icc_node qnm_lpiaon_noc = { .link_nodes = { &qns_lpass_ag_noc_gemnoc }, }; -static struct qcom_icc_qosbox qnm_camnoc_nrt_icp_sf_qos = { +static const struct qcom_icc_qosbox qnm_camnoc_nrt_icp_sf_qos = { .num_ports = 1, .port_offsets = { 0x25000 }, .prio = 4, diff --git a/drivers/interconnect/qcom/icc-common.h b/drivers/interconnect/qcom/icc-common.h index 21c39b163948..af4b5a6f30b7 100644 --- a/drivers/interconnect/qcom/icc-common.h +++ b/drivers/interconnect/qcom/icc-common.h @@ -7,6 +7,15 @@ #define __DRIVERS_INTERCONNECT_QCOM_ICC_COMMON_H__ #include <linux/interconnect-provider.h> +#include <linux/math64.h> + +static inline u64 qcom_bw_div(u64 num, u32 base) +{ + if (num && num < base) + return 1; + + return div_u64(num, base); +} struct icc_node_data *qcom_icc_xlate_extended(const struct of_phandle_args *spec, void *data); diff --git a/drivers/interconnect/qcom/icc-rpm.c b/drivers/interconnect/qcom/icc-rpm.c index ca4c8a944755..6f325789a96e 100644 --- a/drivers/interconnect/qcom/icc-rpm.c +++ b/drivers/interconnect/qcom/icc-rpm.c @@ -59,14 +59,31 @@ static int qcom_icc_set_qnoc_qos(struct icc_node *src) int rc; rc = regmap_update_bits(qp->regmap, + qp->qos_offset + QNOC_QOS_MCTL_LOWn_ADDR(qos->qos_port), + QNOC_QOS_MCTL_DFLT_PRIO_MASK, + qos->areq_prio << QNOC_QOS_MCTL_DFLT_PRIO_SHIFT); + if (rc) + return rc; + + rc = regmap_update_bits(qp->regmap, qp->qos_offset + QNOC_QOS_MCTL_LOWn_ADDR(qos->qos_port), - QNOC_QOS_MCTL_DFLT_PRIO_MASK, - qos->areq_prio << QNOC_QOS_MCTL_DFLT_PRIO_SHIFT); + QNOC_QOS_MCTL_URGFWD_EN_MASK, + !!qos->urg_fwd_en << QNOC_QOS_MCTL_URGFWD_EN_SHIFT); + if (rc) + return rc; + + if (!qos->aux_qos_port) + return 0; + + rc = regmap_update_bits(qp->regmap, + qp->qos_offset + QNOC_QOS_MCTL_LOWn_ADDR(qos->aux_qos_port), + QNOC_QOS_MCTL_DFLT_PRIO_MASK, + qos->areq_prio << QNOC_QOS_MCTL_DFLT_PRIO_SHIFT); if (rc) return rc; return regmap_update_bits(qp->regmap, - qp->qos_offset + QNOC_QOS_MCTL_LOWn_ADDR(qos->qos_port), + qp->qos_offset + QNOC_QOS_MCTL_LOWn_ADDR(qos->aux_qos_port), QNOC_QOS_MCTL_URGFWD_EN_MASK, !!qos->urg_fwd_en << QNOC_QOS_MCTL_URGFWD_EN_SHIFT); } @@ -75,8 +92,9 @@ static int qcom_icc_bimc_set_qos_health(struct qcom_icc_provider *qp, struct qcom_icc_qos *qos, int regnum) { - u32 val; u32 mask; + u32 val; + int ret; val = qos->prio_level; mask = M_BKE_HEALTH_CFG_PRIOLVL_MASK; @@ -90,8 +108,17 @@ static int qcom_icc_bimc_set_qos_health(struct qcom_icc_provider *qp, mask |= M_BKE_HEALTH_CFG_LIMITCMDS_MASK; } + ret = regmap_update_bits(qp->regmap, + qp->qos_offset + M_BKE_HEALTH_CFG_ADDR(regnum, qos->qos_port), + mask, val); + if (ret) + return ret; + + if (!qos->aux_qos_port) + return 0; + return regmap_update_bits(qp->regmap, - qp->qos_offset + M_BKE_HEALTH_CFG_ADDR(regnum, qos->qos_port), + qp->qos_offset + M_BKE_HEALTH_CFG_ADDR(regnum, qos->aux_qos_port), mask, val); } @@ -102,7 +129,7 @@ static int qcom_icc_set_bimc_qos(struct icc_node *src) struct icc_provider *provider; u32 mode = NOC_QOS_MODE_BYPASS; u32 val = 0; - int i, rc = 0; + int i, rc; qn = src->data; provider = src->provider; @@ -116,8 +143,7 @@ static int qcom_icc_set_bimc_qos(struct icc_node *src) */ if (mode != NOC_QOS_MODE_BYPASS) { for (i = 3; i >= 0; i--) { - rc = qcom_icc_bimc_set_qos_health(qp, - &qn->qos, i); + rc = qcom_icc_bimc_set_qos_health(qp, &qn->qos, i); if (rc) return rc; } @@ -126,8 +152,17 @@ static int qcom_icc_set_bimc_qos(struct icc_node *src) val = 1; } + rc = regmap_update_bits(qp->regmap, + qp->qos_offset + M_BKE_EN_ADDR(qn->qos.qos_port), + M_BKE_EN_EN_BMASK, val); + if (rc) + return rc; + + if (!qn->qos.aux_qos_port) + return 0; + return regmap_update_bits(qp->regmap, - qp->qos_offset + M_BKE_EN_ADDR(qn->qos.qos_port), + qp->qos_offset + M_BKE_EN_ADDR(qn->qos.aux_qos_port), M_BKE_EN_EN_BMASK, val); } @@ -145,8 +180,24 @@ static int qcom_icc_noc_set_qos_priority(struct qcom_icc_provider *qp, if (rc) return rc; + rc = regmap_update_bits(qp->regmap, + qp->qos_offset + NOC_QOS_PRIORITYn_ADDR(qos->qos_port), + NOC_QOS_PRIORITY_P0_MASK, qos->prio_level); + if (rc) + return rc; + + if (!qos->aux_qos_port) + return 0; + + val = qos->areq_prio << NOC_QOS_PRIORITY_P1_SHIFT; + rc = regmap_update_bits(qp->regmap, + qp->qos_offset + NOC_QOS_PRIORITYn_ADDR(qos->aux_qos_port), + NOC_QOS_PRIORITY_P1_MASK, val); + if (rc) + return rc; + return regmap_update_bits(qp->regmap, - qp->qos_offset + NOC_QOS_PRIORITYn_ADDR(qos->qos_port), + qp->qos_offset + NOC_QOS_PRIORITYn_ADDR(qos->aux_qos_port), NOC_QOS_PRIORITY_P0_MASK, qos->prio_level); } @@ -182,8 +233,17 @@ static int qcom_icc_set_noc_qos(struct icc_node *src) /* How did we get here? */ } + rc = regmap_update_bits(qp->regmap, + qp->qos_offset + NOC_QOS_MODEn_ADDR(qn->qos.qos_port), + NOC_QOS_MODEn_MASK, mode); + if (rc) + return rc; + + if (!qn->qos.aux_qos_port) + return 0; + return regmap_update_bits(qp->regmap, - qp->qos_offset + NOC_QOS_MODEn_ADDR(qn->qos.qos_port), + qp->qos_offset + NOC_QOS_MODEn_ADDR(qn->qos.aux_qos_port), NOC_QOS_MODEn_MASK, mode); } @@ -298,25 +358,25 @@ static u64 qcom_icc_calc_rate(struct qcom_icc_provider *qp, struct qcom_icc_node u64 agg_avg_rate, agg_peak_rate, agg_rate; if (qn->channels) - agg_avg_rate = div_u64(qn->sum_avg[ctx], qn->channels); + agg_avg_rate = qcom_bw_div(qn->sum_avg[ctx], qn->channels); else agg_avg_rate = qn->sum_avg[ctx]; if (qn->ab_coeff) { agg_avg_rate = agg_avg_rate * qn->ab_coeff; - agg_avg_rate = div_u64(agg_avg_rate, 100); + agg_avg_rate = qcom_bw_div(agg_avg_rate, 100); } if (qn->ib_coeff) { agg_peak_rate = qn->max_peak[ctx] * 100; - agg_peak_rate = div_u64(agg_peak_rate, qn->ib_coeff); + agg_peak_rate = qcom_bw_div(agg_peak_rate, qn->ib_coeff); } else { agg_peak_rate = qn->max_peak[ctx]; } agg_rate = max_t(u64, agg_avg_rate, agg_peak_rate); - return div_u64(agg_rate, qn->buswidth); + return qcom_bw_div(agg_rate, qn->buswidth); } /** @@ -402,7 +462,7 @@ static int qcom_icc_set(struct icc_node *src, struct icc_node *dst) if (ret) return ret; - /* Cache the rate after we've successfully commited it to RPM */ + /* Cache the rate after we've successfully committed it to RPM */ qp->bus_clk_rate[QCOM_SMD_RPM_ACTIVE_STATE] = active_rate; } @@ -412,7 +472,7 @@ static int qcom_icc_set(struct icc_node *src, struct icc_node *dst) if (ret) return ret; - /* Cache the rate after we've successfully commited it to RPM */ + /* Cache the rate after we've successfully committed it to RPM */ qp->bus_clk_rate[QCOM_SMD_RPM_SLEEP_STATE] = sleep_rate; } @@ -583,8 +643,15 @@ regmap_done: node->data = qnodes[i]; icc_node_add(node, provider); - for (j = 0; j < qnodes[i]->num_links; j++) - icc_link_create(node, qnodes[i]->links[j]); + for (j = 0; j < qnodes[i]->num_links; j++) { + ret = icc_link_create(node, qnodes[i]->links[j]); + if (ret) { + icc_nodes_remove(provider); + clk_bulk_disable_unprepare(qp->num_intf_clks, + qp->intf_clks); + goto err_disable_unprepare_clk; + } + } /* Set QoS registers (we only need to do it once, generally) */ if (qnodes[i]->qos.ap_owned && diff --git a/drivers/interconnect/qcom/icc-rpm.h b/drivers/interconnect/qcom/icc-rpm.h index cdd5f444b152..d2c5f45eeedf 100644 --- a/drivers/interconnect/qcom/icc-rpm.h +++ b/drivers/interconnect/qcom/icc-rpm.h @@ -30,7 +30,7 @@ enum qcom_icc_type { * @resource_type: RPM resource type of the clock resource * @clock_id: index of the clock resource of a specific resource type * @branch: whether the resource represents a branch clock -*/ + */ struct rpm_clk_resource { u32 resource_type; u32 clock_id; @@ -77,6 +77,7 @@ struct qcom_icc_provider { * @ap_owned: indicates if the node is owned by the AP or by the RPM * @qos_mode: default qos mode for this node * @qos_port: qos port number for finding qos registers of this node + * @aux_qos_port: optional QoS port in addition to @qos_port. Zero means "none". * @urg_fwd_en: enable urgent forwarding */ struct qcom_icc_qos { @@ -86,6 +87,7 @@ struct qcom_icc_qos { bool ap_owned; int qos_mode; int qos_port; + u16 aux_qos_port; bool urg_fwd_en; }; @@ -167,7 +169,7 @@ int qnoc_probe(struct platform_device *pdev); void qnoc_remove(struct platform_device *pdev); bool qcom_icc_rpm_smd_available(void); -int qcom_icc_rpm_smd_send(int ctx, int rsc_type, int id, u32 val); +int qcom_icc_rpm_smd_send(int ctx, int rsc_type, int id, u64 val); int qcom_icc_rpm_set_bus_rate(const struct rpm_clk_resource *clk, int ctx, u32 rate); #endif diff --git a/drivers/interconnect/qcom/icc-rpmh.c b/drivers/interconnect/qcom/icc-rpmh.c index 3b445acefece..4a2a746a4e59 100644 --- a/drivers/interconnect/qcom/icc-rpmh.c +++ b/drivers/interconnect/qcom/icc-rpmh.c @@ -136,6 +136,61 @@ int qcom_icc_set(struct icc_node *src, struct icc_node *dst) } EXPORT_SYMBOL_GPL(qcom_icc_set); +static int qcom_icc_get_bw(struct icc_node *node, u32 *avg, u32 *peak) +{ + struct qcom_icc_node *qn = node->data; + u32 avg_max = 0; + u32 peak_max = 0; + u64 x, y; + int i; + + if (!qn->num_bcms) { + *avg = INT_MAX; + *peak = INT_MAX; + + return 0; + } + + for (i = 0; i < qn->num_bcms; ++i) { + struct qcom_icc_bcm *bcm = qn->bcms[i]; + + /* Use AMC vote for boot-up */ + x = bcm->vote_x[QCOM_ICC_BUCKET_AMC]; + y = bcm->vote_y[QCOM_ICC_BUCKET_AMC]; + + /* Consider enable mask and convert to INT_MAX */ + if (bcm->enable_mask) { + if (x & bcm->enable_mask) + avg_max = INT_MAX; + if (y & bcm->enable_mask) + peak_max = INT_MAX; + } else { + if (x) { + x *= le32_to_cpu(bcm->aux_data.unit); + do_div(x, bcm->vote_scale); + x *= qn->buswidth * qn->channels; + do_div(x, le16_to_cpu(bcm->aux_data.width)); + + avg_max = max(avg_max, x); + } + + if (y) { + y *= le32_to_cpu(bcm->aux_data.unit); + do_div(y, bcm->vote_scale); + y *= qn->buswidth; + do_div(y, le16_to_cpu(bcm->aux_data.width)); + + peak_max = max(peak_max, y); + } + } + } + + *avg = avg_max; + *peak = peak_max; + + return 0; +} + /** * qcom_icc_bcm_init - populates bcm aux data and connect qnodes * @bcm: bcm to be initialized @@ -173,10 +228,7 @@ int qcom_icc_bcm_init(struct qcom_icc_bcm *bcm, struct device *dev) return -EINVAL; } - bcm->aux_data.unit = le32_to_cpu(data->unit); - bcm->aux_data.width = le16_to_cpu(data->width); - bcm->aux_data.vcd = data->vcd; - bcm->aux_data.reserved = data->reserved; + bcm->aux_data = *data; INIT_LIST_HEAD(&bcm->list); INIT_LIST_HEAD(&bcm->ws_list); @@ -255,6 +307,13 @@ int qcom_icc_rpmh_probe(struct platform_device *pdev) provider = &qp->provider; provider->dev = dev; provider->set = qcom_icc_set; + /* + * Programming the QoS registers below requires the NoC to be clocked, + * which during probe is only guaranteed by the INT_MAX initial votes + * the interconnect core applies when get_bw() is absent. + */ + if (!desc->config) + provider->get_bw = qcom_icc_get_bw; provider->pre_aggregate = qcom_icc_pre_aggregate; provider->aggregate = qcom_icc_aggregate; provider->xlate_extended = qcom_icc_xlate_extended; @@ -272,8 +331,10 @@ int qcom_icc_rpmh_probe(struct platform_device *pdev) if (IS_ERR(qp->voter)) return PTR_ERR(qp->voter); - for (i = 0; i < qp->num_bcms; i++) + for (i = 0; i < qp->num_bcms; i++) { qcom_icc_bcm_init(qp->bcms[i], dev); + qcom_icc_bcm_get_bw(qp->voter, qp->bcms[i]); + } for (i = 0; i < num_nodes; i++) { qn = qnodes[i]; diff --git a/drivers/interconnect/qcom/kuno.c b/drivers/interconnect/qcom/kuno.c new file mode 100644 index 000000000000..546622cf8cea --- /dev/null +++ b/drivers/interconnect/qcom/kuno.c @@ -0,0 +1,988 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/device.h> +#include <linux/interconnect.h> +#include <linux/interconnect-provider.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <dt-bindings/interconnect/qcom,kuno.h> + +#include "bcm-voter.h" +#include "icc-rpmh.h" + +static struct qcom_icc_node qup0_core_slave = { + .name = "qup0_core_slave", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_ahb2_phy = { + .name = "qhs_ahb2_phy", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_aoss = { + .name = "qhs_aoss", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_apss = { + .name = "qhs_apss", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_audio = { + .name = "qhs_audio", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_clk_ctl = { + .name = "qhs_clk_ctl", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_cpr_cx = { + .name = "qhs_cpr_cx", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_cpr_mxa = { + .name = "qhs_cpr_mxa", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_cpr_mxc = { + .name = "qhs_cpr_mxc", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_crypto_cfg = { + .name = "qhs_crypto_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_emac0_cfg = { + .name = "qhs_emac0_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_imem_cfg = { + .name = "qhs_imem_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_ipa = { + .name = "qhs_ipa", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_ipc_router = { + .name = "qhs_ipc_router", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_mss_cfg = { + .name = "qhs_mss_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_pcie0_cfg = { + .name = "qhs_pcie0_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_pcie_rscc = { + .name = "qhs_pcie_rscc", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_pdm = { + .name = "qhs_pdm", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_pmu_wrapper_cfg = { + .name = "qhs_pmu_wrapper_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_prng = { + .name = "qhs_prng", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_qdss_cfg = { + .name = "qhs_qdss_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_qpic = { + .name = "qhs_qpic", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_qup0 = { + .name = "qhs_qup0", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_sdc4 = { + .name = "qhs_sdc4", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_spmi_vgi_coex = { + .name = "qhs_spmi_vgi_coex", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_tcsr = { + .name = "qhs_tcsr", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_tlmm = { + .name = "qhs_tlmm", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_tme_cfg = { + .name = "qhs_tme_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_usb3 = { + .name = "qhs_usb3", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_vsense_ctrl_cfg = { + .name = "qhs_vsense_ctrl_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qss_anoc_throttle_cfg = { + .name = "qss_anoc_throttle_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qxs_boot_imem = { + .name = "qxs_boot_imem", + .channels = 1, + .buswidth = 8, + .num_links = 0, +}; + +static struct qcom_icc_node qxs_imem = { + .name = "qxs_imem", + .channels = 1, + .buswidth = 8, + .num_links = 0, +}; + +static struct qcom_icc_node xs_qdss_stm = { + .name = "xs_qdss_stm", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node xs_sys_tcu_cfg = { + .name = "xs_sys_tcu_cfg", + .channels = 1, + .buswidth = 8, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_lagg = { + .name = "qhs_lagg", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node qhs_mccc_master = { + .name = "qhs_mccc_master", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node ebi = { + .name = "ebi", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node srvc_memnoc = { + .name = "srvc_memnoc", + .channels = 1, + .buswidth = 4, + .num_links = 0, +}; + +static struct qcom_icc_node xs_pcie_0 = { + .name = "xs_pcie_0", + .channels = 1, + .buswidth = 8, + .num_links = 0, +}; + +static struct qcom_icc_node qup0_core_master = { + .name = "qup0_core_master", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = { &qup0_core_slave }, +}; + +static struct qcom_icc_node qnm_cnoc = { + .name = "qnm_cnoc", + .channels = 1, + .buswidth = 4, + .num_links = 2, + .link_nodes = { &qhs_lagg, &qhs_mccc_master }, +}; + +static struct qcom_icc_node llcc_mc = { + .name = "llcc_mc", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = { &ebi }, +}; + +static struct qcom_icc_node qnm_memnoc_cfg = { + .name = "qnm_memnoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = { &srvc_memnoc }, +}; + +static struct qcom_icc_node qnm_memnoc_pcie = { + .name = "qnm_memnoc_pcie", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &xs_pcie_0 }, +}; + +static struct qcom_icc_node xm_ipa2pcie = { + .name = "xm_ipa2pcie", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &xs_pcie_0 }, +}; + +static struct qcom_icc_node qns_ddrss_cfg = { + .name = "qns_ddrss_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = { &qnm_cnoc }, +}; + +static struct qcom_icc_node qns_llcc = { + .name = "qns_llcc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = { &llcc_mc }, +}; + +static struct qcom_icc_node qns_pcie = { + .name = "qns_pcie", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qnm_memnoc_pcie }, +}; + +static struct qcom_icc_node qhm_pcie_rscc = { + .name = "qhm_pcie_rscc", + .channels = 1, + .buswidth = 4, + .num_links = 34, + .link_nodes = { &qhs_ahb2_phy, &qhs_aoss, + &qhs_apss, &qhs_audio, + &qhs_clk_ctl, &qhs_cpr_cx, + &qhs_cpr_mxa, &qhs_cpr_mxc, + &qhs_crypto_cfg, &qhs_emac0_cfg, + &qhs_imem_cfg, &qhs_ipa, + &qhs_ipc_router, &qhs_mss_cfg, + &qhs_pcie0_cfg, &qhs_pdm, + &qhs_pmu_wrapper_cfg, &qhs_prng, + &qhs_qdss_cfg, &qhs_qpic, + &qhs_qup0, &qhs_sdc4, + &qhs_spmi_vgi_coex, &qhs_tcsr, + &qhs_tlmm, &qhs_tme_cfg, + &qhs_usb3, &qhs_vsense_ctrl_cfg, + &qns_ddrss_cfg, &qss_anoc_throttle_cfg, + &qxs_boot_imem, &qxs_imem, + &xs_qdss_stm, &xs_sys_tcu_cfg }, +}; + +static struct qcom_icc_node qnm_memnoc_cnoc = { + .name = "qnm_memnoc_cnoc", + .channels = 1, + .buswidth = 8, + .num_links = 35, + .link_nodes = { &qhs_ahb2_phy, &qhs_aoss, + &qhs_apss, &qhs_audio, + &qhs_clk_ctl, &qhs_cpr_cx, + &qhs_cpr_mxa, &qhs_cpr_mxc, + &qhs_crypto_cfg, &qhs_emac0_cfg, + &qhs_imem_cfg, &qhs_ipa, + &qhs_ipc_router, &qhs_mss_cfg, + &qhs_pcie0_cfg, &qhs_pcie_rscc, + &qhs_pdm, &qhs_pmu_wrapper_cfg, + &qhs_prng, &qhs_qdss_cfg, + &qhs_qpic, &qhs_qup0, + &qhs_sdc4, &qhs_spmi_vgi_coex, + &qhs_tcsr, &qhs_tlmm, + &qhs_tme_cfg, &qhs_usb3, + &qhs_vsense_ctrl_cfg, &qns_ddrss_cfg, + &qss_anoc_throttle_cfg, &qxs_boot_imem, + &qxs_imem, &xs_qdss_stm, + &xs_sys_tcu_cfg }, +}; + +static struct qcom_icc_node qns_memnoc_cnoc = { + .name = "qns_memnoc_cnoc", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qnm_memnoc_cnoc }, +}; + +static struct qcom_icc_node alm_sys_tcu = { + .name = "alm_sys_tcu", + .channels = 1, + .buswidth = 8, + .num_links = 2, + .link_nodes = { &qns_llcc, &qns_memnoc_cnoc }, +}; + +static struct qcom_icc_node qnm_mdsp = { + .name = "qnm_mdsp", + .channels = 1, + .buswidth = 8, + .num_links = 3, + .link_nodes = { &qns_llcc, &qns_memnoc_cnoc, + &qns_pcie }, +}; + +static struct qcom_icc_node qnm_pcie = { + .name = "qnm_pcie", + .channels = 1, + .buswidth = 8, + .num_links = 2, + .link_nodes = { &qns_llcc, &qns_memnoc_cnoc }, +}; + +static struct qcom_icc_node qnm_snoc_sf = { + .name = "qnm_snoc_sf", + .channels = 1, + .buswidth = 8, + .num_links = 2, + .link_nodes = { &qns_llcc, &qns_memnoc_cnoc }, +}; + +static struct qcom_icc_node xm_apps0 = { + .name = "xm_apps0", + .channels = 1, + .buswidth = 16, + .num_links = 3, + .link_nodes = { &qns_llcc, &qns_memnoc_cnoc, + &qns_pcie }, +}; + +static struct qcom_icc_node qns_pcie_memnoc = { + .name = "qns_pcie_memnoc", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qnm_pcie }, +}; + +static struct qcom_icc_node qns_memnoc_sf = { + .name = "qns_memnoc_sf", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qnm_snoc_sf }, +}; + +static struct qcom_icc_node xm_pcie3_0 = { + .name = "xm_pcie3_0", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qns_pcie_memnoc }, +}; + +static struct qcom_icc_node qhm_cpucp = { + .name = "qhm_cpucp", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = { &qns_memnoc_sf }, +}; + +static struct qcom_icc_node qnm_aggre_noc = { + .name = "qnm_aggre_noc", + .channels = 1, + .buswidth = 8, + .num_links = 2, + .link_nodes = { &qns_memnoc_sf, &xs_pcie_0 }, +}; + +static struct qcom_icc_node qnm_cnoc_datapath = { + .name = "qnm_cnoc_datapath", + .channels = 1, + .buswidth = 4, + .num_links = 2, + .link_nodes = { &qns_memnoc_sf, &xs_pcie_0 }, +}; + +static struct qcom_icc_node qxm_mss_nav_ce = { + .name = "qxm_mss_nav_ce", + .channels = 1, + .buswidth = 8, + .num_links = 2, + .link_nodes = { &qns_memnoc_sf, &xs_pcie_0 }, +}; + +static struct qcom_icc_node qxm_tme = { + .name = "qxm_tme", + .channels = 1, + .buswidth = 8, + .num_links = 2, + .link_nodes = { &qns_memnoc_sf, &xs_pcie_0 }, +}; + +static struct qcom_icc_node qns_a1noc = { + .name = "qns_a1noc", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qnm_aggre_noc }, +}; + +static struct qcom_icc_node qns_snoc_datapath = { + .name = "qns_snoc_datapath", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = { &qnm_cnoc_datapath }, +}; + +static struct qcom_icc_node qhm_audio = { + .name = "qhm_audio", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = { &qns_a1noc }, +}; + +static struct qcom_icc_node qhm_qdss_bam = { + .name = "qhm_qdss_bam", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = { &qns_a1noc }, +}; + +static struct qcom_icc_node qhm_qpic = { + .name = "qhm_qpic", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = { &qns_a1noc }, +}; + +static struct qcom_icc_node qhm_qup0 = { + .name = "qhm_qup0", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = { &qns_a1noc }, +}; + +static struct qcom_icc_node qxm_crypto = { + .name = "qxm_crypto", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qns_a1noc }, +}; + +static struct qcom_icc_node qxm_ipa = { + .name = "qxm_ipa", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qns_a1noc }, +}; + +static struct qcom_icc_node xm_emac_0 = { + .name = "xm_emac_0", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qns_a1noc }, +}; + +static struct qcom_icc_node xm_qdss_etr0 = { + .name = "xm_qdss_etr0", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qns_a1noc }, +}; + +static struct qcom_icc_node xm_qdss_etr1 = { + .name = "xm_qdss_etr1", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qns_a1noc }, +}; + +static struct qcom_icc_node xm_sdc4 = { + .name = "xm_sdc4", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qns_a1noc }, +}; + +static struct qcom_icc_node xm_usb3 = { + .name = "xm_usb3", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = { &qns_a1noc }, +}; + +static struct qcom_icc_node xm_qdss_dap = { + .name = "xm_qdss_dap", + .channels = 1, + .buswidth = 8, + .num_links = 36, + .link_nodes = { &qhs_ahb2_phy, &qhs_aoss, + &qhs_apss, &qhs_audio, + &qhs_clk_ctl, &qhs_cpr_cx, + &qhs_cpr_mxa, &qhs_cpr_mxc, + &qhs_crypto_cfg, &qhs_emac0_cfg, + &qhs_imem_cfg, &qhs_ipa, + &qhs_ipc_router, &qhs_mss_cfg, + &qhs_pcie0_cfg, &qhs_pcie_rscc, + &qhs_pdm, &qhs_pmu_wrapper_cfg, + &qhs_prng, &qhs_qdss_cfg, + &qhs_qpic, &qhs_qup0, + &qhs_sdc4, &qhs_spmi_vgi_coex, + &qhs_tcsr, &qhs_tlmm, + &qhs_tme_cfg, &qhs_usb3, + &qhs_vsense_ctrl_cfg, &qns_ddrss_cfg, + &qns_snoc_datapath, &qss_anoc_throttle_cfg, + &qxs_boot_imem, &qxs_imem, + &xs_qdss_stm, &xs_sys_tcu_cfg }, +}; + +static struct qcom_icc_bcm bcm_acv = { + .name = "ACV", + .enable_mask = 0x8, + .num_nodes = 1, + .nodes = { &ebi }, +}; + +static struct qcom_icc_bcm bcm_ce0 = { + .name = "CE0", + .num_nodes = 1, + .nodes = { &qxm_crypto }, +}; + +static struct qcom_icc_bcm bcm_cn0 = { + .name = "CN0", + .keepalive = true, + .enable_mask = 0x1, + .num_nodes = 39, + .nodes = { &qhm_pcie_rscc, &qnm_memnoc_cnoc, + &xm_qdss_dap, &qhs_ahb2_phy, + &qhs_aoss, &qhs_apss, + &qhs_audio, &qhs_clk_ctl, + &qhs_cpr_cx, &qhs_cpr_mxa, + &qhs_cpr_mxc, &qhs_crypto_cfg, + &qhs_emac0_cfg, &qhs_imem_cfg, + &qhs_ipa, &qhs_ipc_router, + &qhs_mss_cfg, &qhs_pcie0_cfg, + &qhs_pcie_rscc, &qhs_pdm, + &qhs_pmu_wrapper_cfg, &qhs_prng, + &qhs_qdss_cfg, &qhs_qpic, + &qhs_qup0, &qhs_sdc4, + &qhs_spmi_vgi_coex, &qhs_tcsr, + &qhs_tlmm, &qhs_tme_cfg, + &qhs_usb3, &qhs_vsense_ctrl_cfg, + &qns_ddrss_cfg, &qns_snoc_datapath, + &qss_anoc_throttle_cfg, &qxs_boot_imem, + &qxs_imem, &xs_qdss_stm, + &xs_sys_tcu_cfg }, +}; + +static struct qcom_icc_bcm bcm_mc0 = { + .name = "MC0", + .keepalive = true, + .num_nodes = 1, + .nodes = { &ebi }, +}; + +static struct qcom_icc_bcm bcm_qup0 = { + .name = "QUP0", + .vote_scale = 1, + .num_nodes = 1, + .nodes = { &qup0_core_slave }, +}; + +static struct qcom_icc_bcm bcm_sh0 = { + .name = "SH0", + .keepalive = true, + .num_nodes = 1, + .nodes = { &qns_llcc }, +}; + +static struct qcom_icc_bcm bcm_sh1 = { + .name = "SH1", + .enable_mask = 0x1, + .num_nodes = 2, + .nodes = { &qns_memnoc_cnoc, &qns_pcie }, +}; + +static struct qcom_icc_bcm bcm_sn0 = { + .name = "SN0", + .keepalive = true, + .num_nodes = 1, + .nodes = { &qns_memnoc_sf }, +}; + +static struct qcom_icc_bcm bcm_sn1 = { + .name = "SN1", + .enable_mask = 0x1, + .num_nodes = 1, + .nodes = { &xm_pcie3_0 }, +}; + +static struct qcom_icc_bcm bcm_sn2 = { + .name = "SN2", + .num_nodes = 2, + .nodes = { &qns_a1noc, &qnm_aggre_noc }, +}; + +static struct qcom_icc_bcm bcm_sn4 = { + .name = "SN4", + .num_nodes = 1, + .nodes = { &qxm_mss_nav_ce }, +}; + +static struct qcom_icc_bcm bcm_sn7 = { + .name = "SN7", + .num_nodes = 1, + .nodes = { &xs_pcie_0 }, +}; + +static struct qcom_icc_bcm * const aggre_noc_bcms[] = { + &bcm_ce0, +}; + +static struct qcom_icc_node * const aggre_noc_nodes[] = { + [MASTER_AUDIO] = &qhm_audio, + [MASTER_QDSS_BAM] = &qhm_qdss_bam, + [MASTER_QPIC] = &qhm_qpic, + [MASTER_QUP_0] = &qhm_qup0, + [MASTER_CRYPTO] = &qxm_crypto, + [MASTER_IPA] = &qxm_ipa, + [MASTER_EMAC] = &xm_emac_0, + [MASTER_QDSS_ETR] = &xm_qdss_etr0, + [MASTER_QDSS_ETR_1] = &xm_qdss_etr1, + [MASTER_SDCC_4] = &xm_sdc4, + [MASTER_USB3_0] = &xm_usb3, + [SLAVE_A1NOC_CFG] = &qns_a1noc, +}; + +static const struct qcom_icc_desc kuno_aggre_noc = { + .nodes = aggre_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre_noc_nodes), + .bcms = aggre_noc_bcms, + .num_bcms = ARRAY_SIZE(aggre_noc_bcms), +}; + +static struct qcom_icc_bcm * const clk_virt_bcms[] = { + &bcm_qup0, +}; + +static struct qcom_icc_node * const clk_virt_nodes[] = { + [MASTER_QUP_CORE_0] = &qup0_core_master, + [SLAVE_QUP_CORE_0] = &qup0_core_slave, +}; + +static const struct qcom_icc_desc kuno_clk_virt = { + .nodes = clk_virt_nodes, + .num_nodes = ARRAY_SIZE(clk_virt_nodes), + .bcms = clk_virt_bcms, + .num_bcms = ARRAY_SIZE(clk_virt_bcms), +}; + +static struct qcom_icc_bcm * const cnoc_main_bcms[] = { + &bcm_cn0, +}; + +static struct qcom_icc_node * const cnoc_main_nodes[] = { + [MASTER_PCIE_RSCC] = &qhm_pcie_rscc, + [MASTER_GEM_NOC_CNOC] = &qnm_memnoc_cnoc, + [MASTER_QDSS_DAP] = &xm_qdss_dap, + [SLAVE_AHB2PHY] = &qhs_ahb2_phy, + [SLAVE_AOSS] = &qhs_aoss, + [SLAVE_APPSS] = &qhs_apss, + [SLAVE_AUDIO] = &qhs_audio, + [SLAVE_CLK_CTL] = &qhs_clk_ctl, + [SLAVE_RBCPR_CX_CFG] = &qhs_cpr_cx, + [SLAVE_RBCPR_MXA_CFG] = &qhs_cpr_mxa, + [SLAVE_RBCPR_MXC_CFG] = &qhs_cpr_mxc, + [SLAVE_CRYPTO_0_CFG] = &qhs_crypto_cfg, + [SLAVE_EMAC_CFG] = &qhs_emac0_cfg, + [SLAVE_IMEM_CFG] = &qhs_imem_cfg, + [SLAVE_IPA_CFG] = &qhs_ipa, + [SLAVE_IPC_ROUTER_CFG] = &qhs_ipc_router, + [SLAVE_CNOC_MSS] = &qhs_mss_cfg, + [SLAVE_PCIE_0_CFG] = &qhs_pcie0_cfg, + [SLAVE_PCIE_RSC_CFG] = &qhs_pcie_rscc, + [SLAVE_PDM] = &qhs_pdm, + [SLAVE_PMU_WRAPPER_CFG] = &qhs_pmu_wrapper_cfg, + [SLAVE_PRNG] = &qhs_prng, + [SLAVE_QDSS_CFG] = &qhs_qdss_cfg, + [SLAVE_QPIC] = &qhs_qpic, + [SLAVE_QUP_0] = &qhs_qup0, + [SLAVE_SDCC_4] = &qhs_sdc4, + [SLAVE_SPMI_VGI_COEX] = &qhs_spmi_vgi_coex, + [SLAVE_TCSR] = &qhs_tcsr, + [SLAVE_TLMM] = &qhs_tlmm, + [SLAVE_TME_CFG] = &qhs_tme_cfg, + [SLAVE_USB3] = &qhs_usb3, + [SLAVE_VSENSE_CTRL_CFG] = &qhs_vsense_ctrl_cfg, + [SLAVE_DDRSS_CFG] = &qns_ddrss_cfg, + [SLAVE_SNOC_CNOC] = &qns_snoc_datapath, + [SLAVE_ANOC_THROTTLE_CFG] = &qss_anoc_throttle_cfg, + [SLAVE_BOOT_IMEM] = &qxs_boot_imem, + [SLAVE_IMEM] = &qxs_imem, + [SLAVE_QDSS_STM] = &xs_qdss_stm, + [SLAVE_TCU] = &xs_sys_tcu_cfg, +}; + +static const struct qcom_icc_desc kuno_cnoc_main = { + .nodes = cnoc_main_nodes, + .num_nodes = ARRAY_SIZE(cnoc_main_nodes), + .bcms = cnoc_main_bcms, + .num_bcms = ARRAY_SIZE(cnoc_main_bcms), +}; + +static struct qcom_icc_node * const dc_noc_nodes[] = { + [MASTER_CNOC_DC_NOC] = &qnm_cnoc, + [SLAVE_LAGG_CFG] = &qhs_lagg, + [SLAVE_MCCC_MASTER] = &qhs_mccc_master, +}; + +static const struct qcom_icc_desc kuno_dc_noc = { + .nodes = dc_noc_nodes, + .num_nodes = ARRAY_SIZE(dc_noc_nodes), +}; + +static struct qcom_icc_bcm * const mc_virt_bcms[] = { + &bcm_acv, + &bcm_mc0, +}; + +static struct qcom_icc_node * const mc_virt_nodes[] = { + [MASTER_LLCC] = &llcc_mc, + [SLAVE_EBI1] = &ebi, +}; + +static const struct qcom_icc_desc kuno_mc_virt = { + .nodes = mc_virt_nodes, + .num_nodes = ARRAY_SIZE(mc_virt_nodes), + .bcms = mc_virt_bcms, + .num_bcms = ARRAY_SIZE(mc_virt_bcms), +}; + +static struct qcom_icc_bcm * const mem_noc_bcms[] = { + &bcm_sh0, + &bcm_sh1, +}; + +static struct qcom_icc_node * const mem_noc_nodes[] = { + [MASTER_SYS_TCU] = &alm_sys_tcu, + [MASTER_MSS_PROC] = &qnm_mdsp, + [MASTER_GEM_NOC_CFG] = &qnm_memnoc_cfg, + [MASTER_ANOC_PCIE_GEM_NOC] = &qnm_pcie, + [MASTER_SNOC_SF_MEM_NOC] = &qnm_snoc_sf, + [MASTER_APPSS_PROC] = &xm_apps0, + [SLAVE_LLCC] = &qns_llcc, + [SLAVE_GEM_NOC_CNOC] = &qns_memnoc_cnoc, + [SLAVE_MEM_NOC_PCIE_SNOC] = &qns_pcie, + [SLAVE_SERVICE_GEM_NOC] = &srvc_memnoc, +}; + +static const struct qcom_icc_desc kuno_mem_noc = { + .nodes = mem_noc_nodes, + .num_nodes = ARRAY_SIZE(mem_noc_nodes), + .bcms = mem_noc_bcms, + .num_bcms = ARRAY_SIZE(mem_noc_bcms), +}; + +static struct qcom_icc_bcm * const pcie_anoc_bcms[] = { + &bcm_sn1, +}; + +static struct qcom_icc_node * const pcie_anoc_nodes[] = { + [MASTER_PCIE_0] = &xm_pcie3_0, + [SLAVE_ANOC_PCIE_GEM_NOC] = &qns_pcie_memnoc, +}; + +static const struct qcom_icc_desc kuno_pcie_anoc = { + .nodes = pcie_anoc_nodes, + .num_nodes = ARRAY_SIZE(pcie_anoc_nodes), + .bcms = pcie_anoc_bcms, + .num_bcms = ARRAY_SIZE(pcie_anoc_bcms), +}; + +static struct qcom_icc_bcm * const system_noc_bcms[] = { + &bcm_sn0, + &bcm_sn2, + &bcm_sn4, + &bcm_sn7, +}; + +static struct qcom_icc_node * const system_noc_nodes[] = { + [MASTER_CPUCP] = &qhm_cpucp, + [MASTER_ANOC_SNOC] = &qnm_aggre_noc, + [MASTER_CNOC_SNOC] = &qnm_cnoc_datapath, + [MASTER_GEM_NOC_PCIE_SNOC] = &qnm_memnoc_pcie, + [MASTER_MSS_NAV] = &qxm_mss_nav_ce, + [MASTER_TME] = &qxm_tme, + [MASTER_IPA_PCIE] = &xm_ipa2pcie, + [SLAVE_SNOC_MEM_NOC_SF] = &qns_memnoc_sf, + [SLAVE_PCIE_0] = &xs_pcie_0, +}; + +static const struct qcom_icc_desc kuno_system_noc = { + .nodes = system_noc_nodes, + .num_nodes = ARRAY_SIZE(system_noc_nodes), + .bcms = system_noc_bcms, + .num_bcms = ARRAY_SIZE(system_noc_bcms), +}; + +static const struct of_device_id qnoc_of_match[] = { + { .compatible = "qcom,kuno-aggre-noc", .data = &kuno_aggre_noc }, + { .compatible = "qcom,kuno-clk-virt", .data = &kuno_clk_virt }, + { .compatible = "qcom,kuno-cnoc-main", .data = &kuno_cnoc_main }, + { .compatible = "qcom,kuno-dc-noc", .data = &kuno_dc_noc }, + { .compatible = "qcom,kuno-mc-virt", .data = &kuno_mc_virt }, + { .compatible = "qcom,kuno-mem-noc", .data = &kuno_mem_noc }, + { .compatible = "qcom,kuno-pcie-anoc", .data = &kuno_pcie_anoc }, + { .compatible = "qcom,kuno-system-noc", .data = &kuno_system_noc }, + { } +}; +MODULE_DEVICE_TABLE(of, qnoc_of_match); + +static struct platform_driver qnoc_driver = { + .probe = qcom_icc_rpmh_probe, + .remove = qcom_icc_rpmh_remove, + .driver = { + .name = "qnoc-kuno", + .of_match_table = qnoc_of_match, + .sync_state = icc_sync_state, + }, +}; + +static int __init qnoc_driver_init(void) +{ + return platform_driver_register(&qnoc_driver); +} +core_initcall(qnoc_driver_init); + +static void __exit qnoc_driver_exit(void) +{ + platform_driver_unregister(&qnoc_driver); +} +module_exit(qnoc_driver_exit); + +MODULE_DESCRIPTION("Kuno NoC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/interconnect/qcom/milos.c b/drivers/interconnect/qcom/milos.c index d010b106728a..8b5b5ee53e3b 100644 --- a/drivers/interconnect/qcom/milos.c +++ b/drivers/interconnect/qcom/milos.c @@ -137,7 +137,7 @@ static struct qcom_icc_node srvc_pcie_aggre_noc; static struct qcom_icc_node qns_gemnoc_gc; static struct qcom_icc_node qns_gemnoc_sf; -static struct qcom_icc_qosbox qhm_qup1_qos = { +static const struct qcom_icc_qosbox qhm_qup1_qos = { .num_ports = 1, .port_offsets = { 0xc000 }, .prio = 2, @@ -154,7 +154,7 @@ static struct qcom_icc_node qhm_qup1 = { .link_nodes = { &qns_a1noc_snoc }, }; -static struct qcom_icc_qosbox xm_ufs_mem_qos = { +static const struct qcom_icc_qosbox xm_ufs_mem_qos = { .num_ports = 1, .port_offsets = { 0xf200 }, .prio = 2, @@ -171,7 +171,7 @@ static struct qcom_icc_node xm_ufs_mem = { .link_nodes = { &qns_a1noc_snoc }, }; -static struct qcom_icc_qosbox xm_usb3_0_qos = { +static const struct qcom_icc_qosbox xm_usb3_0_qos = { .num_ports = 1, .port_offsets = { 0x10000 }, .prio = 2, @@ -188,7 +188,7 @@ static struct qcom_icc_node xm_usb3_0 = { .link_nodes = { &qns_a1noc_snoc }, }; -static struct qcom_icc_qosbox qhm_qdss_bam_qos = { +static const struct qcom_icc_qosbox qhm_qdss_bam_qos = { .num_ports = 1, .port_offsets = { 0x14000 }, .prio = 2, @@ -205,7 +205,7 @@ static struct qcom_icc_node qhm_qdss_bam = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox qhm_qspi_qos = { +static const struct qcom_icc_qosbox qhm_qspi_qos = { .num_ports = 1, .port_offsets = { 0x12000 }, .prio = 2, @@ -222,7 +222,7 @@ static struct qcom_icc_node qhm_qspi = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox qhm_qup0_qos = { +static const struct qcom_icc_qosbox qhm_qup0_qos = { .num_ports = 1, .port_offsets = { 0x13000 }, .prio = 2, @@ -239,7 +239,7 @@ static struct qcom_icc_node qhm_qup0 = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox qxm_crypto_qos = { +static const struct qcom_icc_qosbox qxm_crypto_qos = { .num_ports = 1, .port_offsets = { 0x15000 }, .prio = 2, @@ -256,7 +256,7 @@ static struct qcom_icc_node qxm_crypto = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox qxm_ipa_qos = { +static const struct qcom_icc_qosbox qxm_ipa_qos = { .num_ports = 1, .port_offsets = { 0x16000 }, .prio = 2, @@ -273,7 +273,7 @@ static struct qcom_icc_node qxm_ipa = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox xm_qdss_etr_0_qos = { +static const struct qcom_icc_qosbox xm_qdss_etr_0_qos = { .num_ports = 1, .port_offsets = { 0x17000 }, .prio = 2, @@ -290,7 +290,7 @@ static struct qcom_icc_node xm_qdss_etr_0 = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox xm_qdss_etr_1_qos = { +static const struct qcom_icc_qosbox xm_qdss_etr_1_qos = { .num_ports = 1, .port_offsets = { 0x18000 }, .prio = 2, @@ -307,7 +307,7 @@ static struct qcom_icc_node xm_qdss_etr_1 = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox xm_sdc1_qos = { +static const struct qcom_icc_qosbox xm_sdc1_qos = { .num_ports = 1, .port_offsets = { 0x1a000 }, .prio = 2, @@ -324,7 +324,7 @@ static struct qcom_icc_node xm_sdc1 = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox xm_sdc2_qos = { +static const struct qcom_icc_qosbox xm_sdc2_qos = { .num_ports = 1, .port_offsets = { 0x19000 }, .prio = 2, @@ -404,7 +404,7 @@ static struct qcom_icc_node qnm_gemnoc_pcie = { .link_nodes = { &xs_pcie_0, &xs_pcie_1 }, }; -static struct qcom_icc_qosbox alm_gpu_tcu_qos = { +static const struct qcom_icc_qosbox alm_gpu_tcu_qos = { .num_ports = 1, .port_offsets = { 0xf1000 }, .prio = 1, @@ -421,7 +421,7 @@ static struct qcom_icc_node alm_gpu_tcu = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox alm_sys_tcu_qos = { +static const struct qcom_icc_qosbox alm_sys_tcu_qos = { .num_ports = 1, .port_offsets = { 0xf3000 }, .prio = 6, @@ -447,7 +447,7 @@ static struct qcom_icc_node chm_apps = { &qns_pcie }, }; -static struct qcom_icc_qosbox qnm_gpu_qos = { +static const struct qcom_icc_qosbox qnm_gpu_qos = { .num_ports = 2, .port_offsets = { 0x31000, 0x71000 }, .prio = 0, @@ -464,7 +464,7 @@ static struct qcom_icc_node qnm_gpu = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_lpass_gemnoc_qos = { +static const struct qcom_icc_qosbox qnm_lpass_gemnoc_qos = { .num_ports = 1, .port_offsets = { 0xf5000 }, .prio = 0, @@ -491,7 +491,7 @@ static struct qcom_icc_node qnm_mdsp = { &qns_pcie }, }; -static struct qcom_icc_qosbox qnm_mnoc_hf_qos = { +static const struct qcom_icc_qosbox qnm_mnoc_hf_qos = { .num_ports = 2, .port_offsets = { 0x33000, 0x73000 }, .prio = 0, @@ -508,7 +508,7 @@ static struct qcom_icc_node qnm_mnoc_hf = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_mnoc_sf_qos = { +static const struct qcom_icc_qosbox qnm_mnoc_sf_qos = { .num_ports = 2, .port_offsets = { 0x35000, 0x75000 }, .prio = 0, @@ -525,7 +525,7 @@ static struct qcom_icc_node qnm_mnoc_sf = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_nsp_gemnoc_qos = { +static const struct qcom_icc_qosbox qnm_nsp_gemnoc_qos = { .num_ports = 2, .port_offsets = { 0x37000, 0x77000 }, .prio = 0, @@ -543,7 +543,7 @@ static struct qcom_icc_node qnm_nsp_gemnoc = { &qns_pcie }, }; -static struct qcom_icc_qosbox qnm_pcie_qos = { +static const struct qcom_icc_qosbox qnm_pcie_qos = { .num_ports = 1, .port_offsets = { 0xf7000 }, .prio = 2, @@ -560,7 +560,7 @@ static struct qcom_icc_node qnm_pcie = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_snoc_gc_qos = { +static const struct qcom_icc_qosbox qnm_snoc_gc_qos = { .num_ports = 1, .port_offsets = { 0xf9000 }, .prio = 0, @@ -577,7 +577,7 @@ static struct qcom_icc_node qnm_snoc_gc = { .link_nodes = { &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_snoc_sf_qos = { +static const struct qcom_icc_qosbox qnm_snoc_sf_qos = { .num_ports = 1, .port_offsets = { 0xfb000 }, .prio = 0, @@ -620,7 +620,7 @@ static struct qcom_icc_node llcc_mc = { .link_nodes = { &ebi }, }; -static struct qcom_icc_qosbox qnm_camnoc_hf_qos = { +static const struct qcom_icc_qosbox qnm_camnoc_hf_qos = { .num_ports = 2, .port_offsets = { 0xa8000, 0xa9000 }, .prio = 0, @@ -637,7 +637,7 @@ static struct qcom_icc_node qnm_camnoc_hf = { .link_nodes = { &qns_mem_noc_hf }, }; -static struct qcom_icc_qosbox qnm_camnoc_icp_qos = { +static const struct qcom_icc_qosbox qnm_camnoc_icp_qos = { .num_ports = 1, .port_offsets = { 0x2a000 }, .prio = 5, @@ -654,7 +654,7 @@ static struct qcom_icc_node qnm_camnoc_icp = { .link_nodes = { &qns_mem_noc_sf }, }; -static struct qcom_icc_qosbox qnm_camnoc_sf_qos = { +static const struct qcom_icc_qosbox qnm_camnoc_sf_qos = { .num_ports = 2, .port_offsets = { 0x2b000, 0x2c000 }, .prio = 0, @@ -671,7 +671,7 @@ static struct qcom_icc_node qnm_camnoc_sf = { .link_nodes = { &qns_mem_noc_sf }, }; -static struct qcom_icc_qosbox qnm_mdp_qos = { +static const struct qcom_icc_qosbox qnm_mdp_qos = { .num_ports = 1, .port_offsets = { 0xad000 }, .prio = 0, @@ -688,7 +688,7 @@ static struct qcom_icc_node qnm_mdp = { .link_nodes = { &qns_mem_noc_hf }, }; -static struct qcom_icc_qosbox qnm_video_qos = { +static const struct qcom_icc_qosbox qnm_video_qos = { .num_ports = 1, .port_offsets = { 0x30000 }, .prio = 0, @@ -737,7 +737,7 @@ static struct qcom_icc_node qsm_pcie_anoc_cfg = { .link_nodes = { &srvc_pcie_aggre_noc }, }; -static struct qcom_icc_qosbox xm_pcie3_0_qos = { +static const struct qcom_icc_qosbox xm_pcie3_0_qos = { .num_ports = 1, .port_offsets = { 0xb000 }, .prio = 2, @@ -754,7 +754,7 @@ static struct qcom_icc_node xm_pcie3_0 = { .link_nodes = { &qns_pcie_mem_noc }, }; -static struct qcom_icc_qosbox xm_pcie3_1_qos = { +static const struct qcom_icc_qosbox xm_pcie3_1_qos = { .num_ports = 1, .port_offsets = { 0xc000 }, .prio = 2, @@ -787,7 +787,7 @@ static struct qcom_icc_node qnm_aggre2_noc = { .link_nodes = { &qns_gemnoc_sf }, }; -static struct qcom_icc_qosbox qnm_apss_noc_qos = { +static const struct qcom_icc_qosbox qnm_apss_noc_qos = { .num_ports = 1, .port_offsets = { 0x1c000 }, .prio = 2, @@ -804,7 +804,7 @@ static struct qcom_icc_node qnm_apss_noc = { .link_nodes = { &qns_gemnoc_sf }, }; -static struct qcom_icc_qosbox qnm_cnoc_data_qos = { +static const struct qcom_icc_qosbox qnm_cnoc_data_qos = { .num_ports = 1, .port_offsets = { 0x1d000 }, .prio = 2, @@ -821,7 +821,7 @@ static struct qcom_icc_node qnm_cnoc_data = { .link_nodes = { &qns_gemnoc_sf }, }; -static struct qcom_icc_qosbox qxm_pimem_qos = { +static const struct qcom_icc_qosbox qxm_pimem_qos = { .num_ports = 1, .port_offsets = { 0x1e000 }, .prio = 2, @@ -838,7 +838,7 @@ static struct qcom_icc_node qxm_pimem = { .link_nodes = { &qns_gemnoc_gc }, }; -static struct qcom_icc_qosbox xm_gic_qos = { +static const struct qcom_icc_qosbox xm_gic_qos = { .num_ports = 1, .port_offsets = { 0x1f000 }, .prio = 2, diff --git a/drivers/interconnect/qcom/msm8976.c b/drivers/interconnect/qcom/msm8976.c index c219dcfd43b6..11100264ef69 100644 --- a/drivers/interconnect/qcom/msm8976.c +++ b/drivers/interconnect/qcom/msm8976.c @@ -454,7 +454,8 @@ static struct qcom_icc_node mas_oxili = { .qos.qos_mode = NOC_QOS_MODE_BYPASS, .qos.areq_prio = 0, .qos.prio_level = 0, - .qos.qos_port = 16, /* [16, 17] */ + .qos.qos_port = 16, + .qos.aux_qos_port = 17, .num_links = ARRAY_SIZE(mas_oxili_links), .links = mas_oxili_links, }; diff --git a/drivers/interconnect/qcom/msm8996.c b/drivers/interconnect/qcom/msm8996.c index 882f9d2b6d4e..7fc808cc5975 100644 --- a/drivers/interconnect/qcom/msm8996.c +++ b/drivers/interconnect/qcom/msm8996.c @@ -501,6 +501,7 @@ static struct qcom_icc_node mas_venus = { .qos.areq_prio = 0, .qos.prio_level = 0, .qos.qos_port = 3, + .qos.aux_qos_port = 4, .num_links = ARRAY_SIZE(mas_mnoc_bimc_common_links), .links = mas_mnoc_bimc_common_links }; diff --git a/drivers/interconnect/qcom/sm6350.c b/drivers/interconnect/qcom/sm6350.c index c098c608b836..8680d242ccfa 100644 --- a/drivers/interconnect/qcom/sm6350.c +++ b/drivers/interconnect/qcom/sm6350.c @@ -149,7 +149,7 @@ static struct qcom_icc_node qhm_a1noc_cfg = { .link_nodes = { &srvc_aggre1_noc }, }; -static struct qcom_icc_qosbox qhm_qup_0_qos = { +static const struct qcom_icc_qosbox qhm_qup_0_qos = { .num_ports = 1, .port_offsets = { 0xa000 }, .prio = 2, @@ -165,7 +165,7 @@ static struct qcom_icc_node qhm_qup_0 = { .link_nodes = { &qns_a1noc_snoc }, }; -static struct qcom_icc_qosbox xm_emmc_qos = { +static const struct qcom_icc_qosbox xm_emmc_qos = { .num_ports = 1, .port_offsets = { 0x7000 }, .prio = 2, @@ -181,7 +181,7 @@ static struct qcom_icc_node xm_emmc = { .link_nodes = { &qns_a1noc_snoc }, }; -static struct qcom_icc_qosbox xm_ufs_mem_qos = { +static const struct qcom_icc_qosbox xm_ufs_mem_qos = { .num_ports = 1, .port_offsets = { 0x8000 }, .prio = 4, @@ -205,7 +205,7 @@ static struct qcom_icc_node qhm_a2noc_cfg = { .link_nodes = { &srvc_aggre2_noc }, }; -static struct qcom_icc_qosbox qhm_qdss_bam_qos = { +static const struct qcom_icc_qosbox qhm_qdss_bam_qos = { .num_ports = 1, .port_offsets = { 0xb000 }, .prio = 2, @@ -221,7 +221,7 @@ static struct qcom_icc_node qhm_qdss_bam = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox qhm_qup_1_qos = { +static const struct qcom_icc_qosbox qhm_qup_1_qos = { .num_ports = 1, .port_offsets = { 0x9000 }, .prio = 2, @@ -236,7 +236,7 @@ static struct qcom_icc_node qhm_qup_1 = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox qxm_crypto_qos = { +static const struct qcom_icc_qosbox qxm_crypto_qos = { .num_ports = 1, .port_offsets = { 0x6000 }, .prio = 2, @@ -252,7 +252,7 @@ static struct qcom_icc_node qxm_crypto = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox qxm_ipa_qos = { +static const struct qcom_icc_qosbox qxm_ipa_qos = { .num_ports = 1, .port_offsets = { 0x7000 }, .prio = 2, @@ -268,7 +268,7 @@ static struct qcom_icc_node qxm_ipa = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox xm_qdss_etr_qos = { +static const struct qcom_icc_qosbox xm_qdss_etr_qos = { .num_ports = 1, .port_offsets = { 0xc000 }, .prio = 2, @@ -284,7 +284,7 @@ static struct qcom_icc_node xm_qdss_etr = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox xm_sdc2_qos = { +static const struct qcom_icc_qosbox xm_sdc2_qos = { .num_ports = 1, .port_offsets = { 0x18000 }, .prio = 2, @@ -300,7 +300,7 @@ static struct qcom_icc_node xm_sdc2 = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox xm_usb3_0_qos = { +static const struct qcom_icc_qosbox xm_usb3_0_qos = { .num_ports = 1, .port_offsets = { 0xd000 }, .prio = 2, @@ -356,7 +356,7 @@ static struct qcom_icc_node qup1_core_master = { .link_nodes = { &qup1_core_slave }, }; -static struct qcom_icc_qosbox qnm_npu_qos = { +static const struct qcom_icc_qosbox qnm_npu_qos = { .num_ports = 2, .port_offsets = { 0xf000, 0x11000 }, .prio = 0, @@ -372,7 +372,7 @@ static struct qcom_icc_node qnm_npu = { .link_nodes = { &qns_cdsp_gemnoc }, }; -static struct qcom_icc_qosbox qxm_npu_dsp_qos = { +static const struct qcom_icc_qosbox qxm_npu_dsp_qos = { .num_ports = 1, .port_offsets = { 0x13000 }, .prio = 0, @@ -495,7 +495,7 @@ static struct qcom_icc_node qhm_cnoc_dc_noc = { &qhs_gemnoc }, }; -static struct qcom_icc_qosbox acm_apps_qos = { +static const struct qcom_icc_qosbox acm_apps_qos = { .num_ports = 2, .port_offsets = { 0x2f100, 0x2f000 }, .prio = 0, @@ -512,7 +512,7 @@ static struct qcom_icc_node acm_apps = { &qns_gem_noc_snoc }, }; -static struct qcom_icc_qosbox acm_sys_tcu_qos = { +static const struct qcom_icc_qosbox acm_sys_tcu_qos = { .num_ports = 1, .port_offsets = { 0x35000 }, .prio = 6, @@ -539,7 +539,7 @@ static struct qcom_icc_node qhm_gemnoc_cfg = { &qhs_mdsp_ms_mpu_cfg }, }; -static struct qcom_icc_qosbox qnm_cmpnoc_qos = { +static const struct qcom_icc_qosbox qnm_cmpnoc_qos = { .num_ports = 1, .port_offsets = { 0x2e000 }, .prio = 0, @@ -556,7 +556,7 @@ static struct qcom_icc_node qnm_cmpnoc = { &qns_gem_noc_snoc }, }; -static struct qcom_icc_qosbox qnm_mnoc_hf_qos = { +static const struct qcom_icc_qosbox qnm_mnoc_hf_qos = { .num_ports = 1, .port_offsets = { 0x30000 }, .prio = 0, @@ -573,7 +573,7 @@ static struct qcom_icc_node qnm_mnoc_hf = { &qns_gem_noc_snoc }, }; -static struct qcom_icc_qosbox qnm_mnoc_sf_qos = { +static const struct qcom_icc_qosbox qnm_mnoc_sf_qos = { .num_ports = 1, .port_offsets = { 0x34000 }, .prio = 0, @@ -590,7 +590,7 @@ static struct qcom_icc_node qnm_mnoc_sf = { &qns_gem_noc_snoc }, }; -static struct qcom_icc_qosbox qnm_snoc_gc_qos = { +static const struct qcom_icc_qosbox qnm_snoc_gc_qos = { .num_ports = 1, .port_offsets = { 0x32000 }, .prio = 0, @@ -606,7 +606,7 @@ static struct qcom_icc_node qnm_snoc_gc = { .link_nodes = { &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_snoc_sf_qos = { +static const struct qcom_icc_qosbox qnm_snoc_sf_qos = { .num_ports = 1, .port_offsets = { 0x31000 }, .prio = 0, @@ -622,7 +622,7 @@ static struct qcom_icc_node qnm_snoc_sf = { .link_nodes = { &qns_llcc }, }; -static struct qcom_icc_qosbox qxm_gpu_qos = { +static const struct qcom_icc_qosbox qxm_gpu_qos = { .num_ports = 2, .port_offsets = { 0x33000, 0x33080 }, .prio = 0, @@ -655,7 +655,7 @@ static struct qcom_icc_node qhm_mnoc_cfg = { .link_nodes = { &srvc_mnoc }, }; -static struct qcom_icc_qosbox qnm_video0_qos = { +static const struct qcom_icc_qosbox qnm_video0_qos = { .num_ports = 1, .port_offsets = { 0xf000 }, .prio = 2, @@ -671,7 +671,7 @@ static struct qcom_icc_node qnm_video0 = { .link_nodes = { &qns_mem_noc_sf }, }; -static struct qcom_icc_qosbox qnm_video_cvp_qos = { +static const struct qcom_icc_qosbox qnm_video_cvp_qos = { .num_ports = 1, .port_offsets = { 0xe000 }, .prio = 5, @@ -687,7 +687,7 @@ static struct qcom_icc_node qnm_video_cvp = { .link_nodes = { &qns_mem_noc_sf }, }; -static struct qcom_icc_qosbox qxm_camnoc_hf_qos = { +static const struct qcom_icc_qosbox qxm_camnoc_hf_qos = { .num_ports = 2, .port_offsets = { 0xa000, 0xb000 }, .prio = 3, @@ -703,7 +703,7 @@ static struct qcom_icc_node qxm_camnoc_hf = { .link_nodes = { &qns_mem_noc_hf }, }; -static struct qcom_icc_qosbox qxm_camnoc_icp_qos = { +static const struct qcom_icc_qosbox qxm_camnoc_icp_qos = { .num_ports = 1, .port_offsets = { 0xd000 }, .prio = 5, @@ -719,7 +719,7 @@ static struct qcom_icc_node qxm_camnoc_icp = { .link_nodes = { &qns_mem_noc_sf }, }; -static struct qcom_icc_qosbox qxm_camnoc_sf_qos = { +static const struct qcom_icc_qosbox qxm_camnoc_sf_qos = { .num_ports = 1, .port_offsets = { 0x9000 }, .prio = 3, @@ -735,7 +735,7 @@ static struct qcom_icc_node qxm_camnoc_sf = { .link_nodes = { &qns_mem_noc_sf }, }; -static struct qcom_icc_qosbox qxm_mdp0_qos = { +static const struct qcom_icc_qosbox qxm_mdp0_qos = { .num_ports = 1, .port_offsets = { 0xc000 }, .prio = 3, @@ -822,7 +822,7 @@ static struct qcom_icc_node qnm_gemnoc = { &xs_qdss_stm }, }; -static struct qcom_icc_qosbox qxm_pimem_qos = { +static const struct qcom_icc_qosbox qxm_pimem_qos = { .num_ports = 1, .port_offsets = { 0xd000 }, .prio = 2, @@ -839,7 +839,7 @@ static struct qcom_icc_node qxm_pimem = { &qxs_imem }, }; -static struct qcom_icc_qosbox xm_gic_qos = { +static const struct qcom_icc_qosbox xm_gic_qos = { .num_ports = 1, .port_offsets = { 0xb000 }, .prio = 3, diff --git a/drivers/interconnect/qcom/sm8650.c b/drivers/interconnect/qcom/sm8650.c index cf3ae734d4c3..1967475b9132 100644 --- a/drivers/interconnect/qcom/sm8650.c +++ b/drivers/interconnect/qcom/sm8650.c @@ -154,7 +154,7 @@ static const struct regmap_config icc_regmap_config = { .fast_io = true, }; -static struct qcom_icc_qosbox qhm_qspi_qos = { +static const struct qcom_icc_qosbox qhm_qspi_qos = { .num_ports = 1, .port_offsets = { 0xc000 }, .prio = 2, @@ -171,7 +171,7 @@ static struct qcom_icc_node qhm_qspi = { .link_nodes = { &qns_a1noc_snoc }, }; -static struct qcom_icc_qosbox qhm_qup1_qos = { +static const struct qcom_icc_qosbox qhm_qup1_qos = { .num_ports = 1, .port_offsets = { 0xd000 }, .prio = 2, @@ -196,7 +196,7 @@ static struct qcom_icc_node qxm_qup02 = { .link_nodes = { &qns_a1noc_snoc }, }; -static struct qcom_icc_qosbox xm_sdc4_qos = { +static const struct qcom_icc_qosbox xm_sdc4_qos = { .num_ports = 1, .port_offsets = { 0xe000 }, .prio = 2, @@ -213,7 +213,7 @@ static struct qcom_icc_node xm_sdc4 = { .link_nodes = { &qns_a1noc_snoc }, }; -static struct qcom_icc_qosbox xm_ufs_mem_qos = { +static const struct qcom_icc_qosbox xm_ufs_mem_qos = { .num_ports = 1, .port_offsets = { 0xf000 }, .prio = 2, @@ -230,7 +230,7 @@ static struct qcom_icc_node xm_ufs_mem = { .link_nodes = { &qns_a1noc_snoc }, }; -static struct qcom_icc_qosbox xm_usb3_0_qos = { +static const struct qcom_icc_qosbox xm_usb3_0_qos = { .num_ports = 1, .port_offsets = { 0x10000 }, .prio = 2, @@ -247,7 +247,7 @@ static struct qcom_icc_node xm_usb3_0 = { .link_nodes = { &qns_a1noc_snoc }, }; -static struct qcom_icc_qosbox qhm_qdss_bam_qos = { +static const struct qcom_icc_qosbox qhm_qdss_bam_qos = { .num_ports = 1, .port_offsets = { 0x12000 }, .prio = 2, @@ -264,7 +264,7 @@ static struct qcom_icc_node qhm_qdss_bam = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox qhm_qup2_qos = { +static const struct qcom_icc_qosbox qhm_qup2_qos = { .num_ports = 1, .port_offsets = { 0x13000 }, .prio = 2, @@ -281,7 +281,7 @@ static struct qcom_icc_node qhm_qup2 = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox qxm_crypto_qos = { +static const struct qcom_icc_qosbox qxm_crypto_qos = { .num_ports = 1, .port_offsets = { 0x15000 }, .prio = 2, @@ -298,7 +298,7 @@ static struct qcom_icc_node qxm_crypto = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox qxm_ipa_qos = { +static const struct qcom_icc_qosbox qxm_ipa_qos = { .num_ports = 1, .port_offsets = { 0x16000 }, .prio = 2, @@ -323,7 +323,7 @@ static struct qcom_icc_node qxm_sp = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox xm_qdss_etr_0_qos = { +static const struct qcom_icc_qosbox xm_qdss_etr_0_qos = { .num_ports = 1, .port_offsets = { 0x17000 }, .prio = 2, @@ -340,7 +340,7 @@ static struct qcom_icc_node xm_qdss_etr_0 = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox xm_qdss_etr_1_qos = { +static const struct qcom_icc_qosbox xm_qdss_etr_1_qos = { .num_ports = 1, .port_offsets = { 0x18000 }, .prio = 2, @@ -357,7 +357,7 @@ static struct qcom_icc_node xm_qdss_etr_1 = { .link_nodes = { &qns_a2noc_snoc }, }; -static struct qcom_icc_qosbox xm_sdc2_qos = { +static const struct qcom_icc_qosbox xm_sdc2_qos = { .num_ports = 1, .port_offsets = { 0x19000 }, .prio = 2, @@ -448,7 +448,7 @@ static struct qcom_icc_node qnm_gemnoc_pcie = { .link_nodes = { &xs_pcie_0, &xs_pcie_1 }, }; -static struct qcom_icc_qosbox alm_gpu_tcu_qos = { +static const struct qcom_icc_qosbox alm_gpu_tcu_qos = { .num_ports = 1, .port_offsets = { 0xbf000 }, .prio = 1, @@ -465,7 +465,7 @@ static struct qcom_icc_node alm_gpu_tcu = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox alm_sys_tcu_qos = { +static const struct qcom_icc_qosbox alm_sys_tcu_qos = { .num_ports = 1, .port_offsets = { 0xc1000 }, .prio = 6, @@ -482,7 +482,7 @@ static struct qcom_icc_node alm_sys_tcu = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox alm_ubwc_p_tcu_qos = { +static const struct qcom_icc_qosbox alm_ubwc_p_tcu_qos = { .num_ports = 1, .port_offsets = { 0xc5000 }, .prio = 1, @@ -508,7 +508,7 @@ static struct qcom_icc_node chm_apps = { &qns_pcie }, }; -static struct qcom_icc_qosbox qnm_gpu_qos = { +static const struct qcom_icc_qosbox qnm_gpu_qos = { .num_ports = 2, .port_offsets = { 0x31000, 0x71000 }, .prio = 0, @@ -525,7 +525,7 @@ static struct qcom_icc_node qnm_gpu = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_lpass_gemnoc_qos = { +static const struct qcom_icc_qosbox qnm_lpass_gemnoc_qos = { .num_ports = 1, .port_offsets = { 0xb5000 }, .prio = 0, @@ -552,7 +552,7 @@ static struct qcom_icc_node qnm_mdsp = { &qns_pcie }, }; -static struct qcom_icc_qosbox qnm_mnoc_hf_qos = { +static const struct qcom_icc_qosbox qnm_mnoc_hf_qos = { .num_ports = 2, .port_offsets = { 0x33000, 0x73000 }, .prio = 0, @@ -569,7 +569,7 @@ static struct qcom_icc_node qnm_mnoc_hf = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_mnoc_sf_qos = { +static const struct qcom_icc_qosbox qnm_mnoc_sf_qos = { .num_ports = 2, .port_offsets = { 0x35000, 0x75000 }, .prio = 0, @@ -586,7 +586,7 @@ static struct qcom_icc_node qnm_mnoc_sf = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_nsp_gemnoc_qos = { +static const struct qcom_icc_qosbox qnm_nsp_gemnoc_qos = { .num_ports = 2, .port_offsets = { 0x37000, 0x77000 }, .prio = 0, @@ -604,7 +604,7 @@ static struct qcom_icc_node qnm_nsp_gemnoc = { &qns_pcie }, }; -static struct qcom_icc_qosbox qnm_pcie_qos = { +static const struct qcom_icc_qosbox qnm_pcie_qos = { .num_ports = 1, .port_offsets = { 0xb7000 }, .prio = 2, @@ -621,7 +621,7 @@ static struct qcom_icc_node qnm_pcie = { .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; -static struct qcom_icc_qosbox qnm_snoc_sf_qos = { +static const struct qcom_icc_qosbox qnm_snoc_sf_qos = { .num_ports = 1, .port_offsets = { 0xbb000 }, .prio = 0, @@ -639,7 +639,7 @@ static struct qcom_icc_node qnm_snoc_sf = { &qns_pcie }, }; -static struct qcom_icc_qosbox qnm_ubwc_p_qos = { +static const struct qcom_icc_qosbox qnm_ubwc_p_qos = { .num_ports = 1, .port_offsets = { 0xc3000 }, .prio = 1, @@ -656,7 +656,7 @@ static struct qcom_icc_node qnm_ubwc_p = { .link_nodes = { &qns_llcc }, }; -static struct qcom_icc_qosbox xm_gic_qos = { +static const struct qcom_icc_qosbox xm_gic_qos = { .num_ports = 1, .port_offsets = { 0xb9000 }, .prio = 4, @@ -705,7 +705,7 @@ static struct qcom_icc_node llcc_mc = { .link_nodes = { &ebi }, }; -static struct qcom_icc_qosbox qnm_camnoc_hf_qos = { +static const struct qcom_icc_qosbox qnm_camnoc_hf_qos = { .num_ports = 2, .port_offsets = { 0x28000, 0x29000 }, .prio = 0, @@ -722,7 +722,7 @@ static struct qcom_icc_node qnm_camnoc_hf = { .link_nodes = { &qns_mem_noc_hf }, }; -static struct qcom_icc_qosbox qnm_camnoc_icp_qos = { +static const struct qcom_icc_qosbox qnm_camnoc_icp_qos = { .num_ports = 1, .port_offsets = { 0x2a000 }, .prio = 4, @@ -739,7 +739,7 @@ static struct qcom_icc_node qnm_camnoc_icp = { .link_nodes = { &qns_mem_noc_sf }, }; -static struct qcom_icc_qosbox qnm_camnoc_sf_qos = { +static const struct qcom_icc_qosbox qnm_camnoc_sf_qos = { .num_ports = 2, .port_offsets = { 0x2b000, 0x2c000 }, .prio = 0, @@ -756,7 +756,7 @@ static struct qcom_icc_node qnm_camnoc_sf = { .link_nodes = { &qns_mem_noc_sf }, }; -static struct qcom_icc_qosbox qnm_mdp_qos = { +static const struct qcom_icc_qosbox qnm_mdp_qos = { .num_ports = 2, .port_offsets = { 0x2d000, 0x2e000 }, .prio = 0, @@ -781,7 +781,7 @@ static struct qcom_icc_node qnm_vapss_hcp = { .link_nodes = { &qns_mem_noc_sf }, }; -static struct qcom_icc_qosbox qnm_video_qos = { +static const struct qcom_icc_qosbox qnm_video_qos = { .num_ports = 2, .port_offsets = { 0x30000, 0x31000 }, .prio = 0, @@ -798,7 +798,7 @@ static struct qcom_icc_node qnm_video = { .link_nodes = { &qns_mem_noc_sf }, }; -static struct qcom_icc_qosbox qnm_video_cv_cpu_qos = { +static const struct qcom_icc_qosbox qnm_video_cv_cpu_qos = { .num_ports = 1, .port_offsets = { 0x32000 }, .prio = 4, @@ -815,7 +815,7 @@ static struct qcom_icc_node qnm_video_cv_cpu = { .link_nodes = { &qns_mem_noc_sf }, }; -static struct qcom_icc_qosbox qnm_video_cvp_qos = { +static const struct qcom_icc_qosbox qnm_video_cvp_qos = { .num_ports = 2, .port_offsets = { 0x33000, 0x34000 }, .prio = 0, @@ -832,7 +832,7 @@ static struct qcom_icc_node qnm_video_cvp = { .link_nodes = { &qns_mem_noc_sf }, }; -static struct qcom_icc_qosbox qnm_video_v_cpu_qos = { +static const struct qcom_icc_qosbox qnm_video_v_cpu_qos = { .num_ports = 1, .port_offsets = { 0x35000 }, .prio = 4, @@ -873,7 +873,7 @@ static struct qcom_icc_node qsm_pcie_anoc_cfg = { .link_nodes = { &srvc_pcie_aggre_noc }, }; -static struct qcom_icc_qosbox xm_pcie3_0_qos = { +static const struct qcom_icc_qosbox xm_pcie3_0_qos = { .num_ports = 1, .port_offsets = { 0xb000 }, .prio = 3, @@ -890,7 +890,7 @@ static struct qcom_icc_node xm_pcie3_0 = { .link_nodes = { &qns_pcie_mem_noc }, }; -static struct qcom_icc_qosbox xm_pcie3_1_qos = { +static const struct qcom_icc_qosbox xm_pcie3_1_qos = { .num_ports = 1, .port_offsets = { 0xc000 }, .prio = 2, @@ -923,7 +923,7 @@ static struct qcom_icc_node qnm_aggre2_noc = { .link_nodes = { &qns_gemnoc_sf }, }; -static struct qcom_icc_qosbox qnm_apss_noc_qos = { +static const struct qcom_icc_qosbox qnm_apss_noc_qos = { .num_ports = 1, .port_offsets = { 0x1c000 }, .prio = 2, diff --git a/drivers/interconnect/qcom/smd-rpm.c b/drivers/interconnect/qcom/smd-rpm.c index dbc7ae50b02b..6ac9aef942ad 100644 --- a/drivers/interconnect/qcom/smd-rpm.c +++ b/drivers/interconnect/qcom/smd-rpm.c @@ -20,7 +20,7 @@ static struct qcom_smd_rpm *icc_smd_rpm; struct icc_rpm_smd_req { __le32 key; __le32 nbytes; - __le32 value; + __le64 value; }; bool qcom_icc_rpm_smd_available(void) @@ -29,12 +29,12 @@ bool qcom_icc_rpm_smd_available(void) } EXPORT_SYMBOL_GPL(qcom_icc_rpm_smd_available); -int qcom_icc_rpm_smd_send(int ctx, int rsc_type, int id, u32 val) +int qcom_icc_rpm_smd_send(int ctx, int rsc_type, int id, u64 val) { struct icc_rpm_smd_req req = { .key = cpu_to_le32(RPM_KEY_BW), - .nbytes = cpu_to_le32(sizeof(u32)), - .value = cpu_to_le32(val), + .nbytes = cpu_to_le32(sizeof(u64)), + .value = cpu_to_le64(val), }; return qcom_rpm_smd_write(icc_smd_rpm, ctx, rsc_type, id, &req, diff --git a/drivers/interconnect/samsung/exynos.c b/drivers/interconnect/samsung/exynos.c index 8e8f56186a36..9a4789fadac2 100644 --- a/drivers/interconnect/samsung/exynos.c +++ b/drivers/interconnect/samsung/exynos.c @@ -98,6 +98,7 @@ static void exynos_generic_icc_remove(struct platform_device *pdev) struct exynos_icc_priv *priv = platform_get_drvdata(pdev); icc_provider_deregister(&priv->provider); + dev_pm_qos_remove_request(&priv->qos_req); icc_nodes_remove(&priv->provider); } diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 7364931dad3a..c40e89cc5724 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -256,6 +256,8 @@ config HP_ILO config QCOM_COINCELL tristate "Qualcomm coincell charger support" depends on MFD_SPMI_PMIC || COMPILE_TEST + depends on SPMI + select REGMAP_SPMI help This driver supports the coincell block found inside of Qualcomm PMICs. The coincell charger provides a means to @@ -568,6 +570,19 @@ config MCHP_LAN966X_PCI - lan966x-miim (MDIO_MSCC_MIIM) - lan966x-switch (LAN966X_SWITCH) +config TC9564_PCI + tristate "Toshiba TC9564 PCI function support" + depends on PCI_DYNAMIC_OF_NODES + depends on PCI_QUIRKS + default m + help + This enables support for two PCI functions implemented by the + embedded PCIe endpoint in the Toshiba TC9564 SoC. A platform + driver uses a pci-ep-bus node in devicetree to provide MMIO + access to other SoC devices through PCI function BARs. A PCI + quirk causes the ranges property in the endpoint's devicetree + node to be updated dynamically. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index e8d8d5d88c0d..7cc3d615d37a 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -71,3 +71,4 @@ obj-y += keba/ obj-y += amd-sbi/ obj-$(CONFIG_MISC_RP1) += rp1/ obj-$(CONFIG_INTEL_SSEI) += issei/ +obj-$(CONFIG_TC9564_PCI) += tc9564-pci.o diff --git a/drivers/misc/ad525x_dpot.c b/drivers/misc/ad525x_dpot.c index a4e22fd4a107..79054250976e 100644 --- a/drivers/misc/ad525x_dpot.c +++ b/drivers/misc/ad525x_dpot.c @@ -451,10 +451,8 @@ static ssize_t sysfs_set_reg(struct device *dev, int err; if (reg & DPOT_ADDR_OTP_EN) { - if (sysfs_streq(buf, "enabled")) - set_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask); - else - clear_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask); + assign_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask, + sysfs_streq(buf, "enabled")); return count; } @@ -699,9 +697,6 @@ static umode_t ad525x_is_visible(struct kobject *kobj, struct attribute *attr, struct dpot_data *data = dev_get_drvdata(dev); int rdac; - if (!data) - return 0; - rdac = ad525x_attr_index(attr, dpot_attrib_wipers); if (rdac >= 0) return data->wipers & BIT(rdac) ? attr->mode : 0; diff --git a/drivers/misc/altera-stapl/altera-jtag.c b/drivers/misc/altera-stapl/altera-jtag.c index 27e8e0c9e8cf..0238600107b0 100644 --- a/drivers/misc/altera-stapl/altera-jtag.c +++ b/drivers/misc/altera-stapl/altera-jtag.c @@ -17,9 +17,9 @@ #include "altera-jtag.h" #define alt_jtag_io(a, b, c)\ - astate->config->jtag_io(astate->config->dev, a, b, c); + astate->config->jtag_io(astate->config->dev, a, b, c) -#define alt_malloc(a) kzalloc(a, GFP_KERNEL); +#define alt_malloc(a) kzalloc(a, GFP_KERNEL) /* * This structure shows, for each JTAG state, which state is reached after diff --git a/drivers/misc/cardreader/alcor_pci.c b/drivers/misc/cardreader/alcor_pci.c index 9e0f4f91763e..e76fd4b5d6cc 100644 --- a/drivers/misc/cardreader/alcor_pci.c +++ b/drivers/misc/cardreader/alcor_pci.c @@ -188,7 +188,6 @@ static void alcor_pci_remove(struct pci_dev *pdev) pci_set_drvdata(pdev, NULL); } -#ifdef CONFIG_PM_SLEEP static int alcor_suspend(struct device *dev) { return 0; @@ -203,9 +202,8 @@ static int alcor_resume(struct device *dev) return 0; } -#endif /* CONFIG_PM_SLEEP */ -static SIMPLE_DEV_PM_OPS(alcor_pci_pm_ops, alcor_suspend, alcor_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(alcor_pci_pm_ops, alcor_suspend, alcor_resume); static struct pci_driver alcor_driver = { .name = DRV_NAME_ALCOR_PCI, @@ -213,7 +211,7 @@ static struct pci_driver alcor_driver = { .probe = alcor_pci_probe, .remove = alcor_pci_remove, .driver = { - .pm = &alcor_pci_pm_ops + .pm = pm_sleep_ptr(&alcor_pci_pm_ops) }, }; diff --git a/drivers/misc/cb710/core.c b/drivers/misc/cb710/core.c index 8e0d11e59c4e..2ab6f6884766 100644 --- a/drivers/misc/cb710/core.c +++ b/drivers/misc/cb710/core.c @@ -166,7 +166,7 @@ void cb710_set_irq_handler(struct cb710_slot *slot, } EXPORT_SYMBOL_GPL(cb710_set_irq_handler); -static int __maybe_unused cb710_suspend(struct device *dev_d) +static int cb710_suspend(struct device *dev_d) { struct pci_dev *pdev = to_pci_dev(dev_d); struct cb710_chip *chip = pci_get_drvdata(pdev); @@ -175,7 +175,7 @@ static int __maybe_unused cb710_suspend(struct device *dev_d) return 0; } -static int __maybe_unused cb710_resume(struct device *dev_d) +static int cb710_resume(struct device *dev_d) { struct pci_dev *pdev = to_pci_dev(dev_d); struct cb710_chip *chip = pci_get_drvdata(pdev); @@ -296,14 +296,14 @@ static const struct pci_device_id cb710_pci_tbl[] = { { } }; -static SIMPLE_DEV_PM_OPS(cb710_pm_ops, cb710_suspend, cb710_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(cb710_pm_ops, cb710_suspend, cb710_resume); static struct pci_driver cb710_driver = { .name = KBUILD_MODNAME, .id_table = cb710_pci_tbl, .probe = cb710_probe, .remove = cb710_remove_one, - .driver.pm = &cb710_pm_ops, + .driver.pm = pm_sleep_ptr(&cb710_pm_ops), }; static int __init cb710_init_module(void) diff --git a/drivers/misc/cs5535-mfgpt.c b/drivers/misc/cs5535-mfgpt.c index cdd0e7bda68d..9abfc44f70f4 100644 --- a/drivers/misc/cs5535-mfgpt.c +++ b/drivers/misc/cs5535-mfgpt.c @@ -45,7 +45,8 @@ static struct cs5535_mfgpt_chip { int cs5535_mfgpt_toggle_event(struct cs5535_mfgpt_timer *timer, int cmp, int event, int enable) { - uint32_t msr, mask, value, dummy; + uint32_t msr, mask; + struct msr val; int shift = (cmp == MFGPT_CMP1) ? 0 : 8; if (!timer) { @@ -82,14 +83,14 @@ int cs5535_mfgpt_toggle_event(struct cs5535_mfgpt_timer *timer, int cmp, return -EIO; } - rdmsr(msr, value, dummy); + rdmsrq(msr, val.q); if (enable) - value |= mask; + val.l |= mask; else - value &= ~mask; + val.l &= ~mask; - wrmsr(msr, value, dummy); + wrmsrq(msr, val.q); return 0; } EXPORT_SYMBOL_GPL(cs5535_mfgpt_toggle_event); @@ -97,7 +98,7 @@ EXPORT_SYMBOL_GPL(cs5535_mfgpt_toggle_event); int cs5535_mfgpt_set_irq(struct cs5535_mfgpt_timer *timer, int cmp, int *irq, int enable) { - uint32_t zsel, lpc, dummy; + struct msr zsel, lpc; int shift; if (!timer) { @@ -113,30 +114,31 @@ int cs5535_mfgpt_set_irq(struct cs5535_mfgpt_timer *timer, int cmp, int *irq, * IRQ of the 1st. This can only happen if forcing an IRQ, calling this * with *irq==0 is safe. Currently there _are_ no 2 drivers. */ - rdmsr(MSR_PIC_ZSEL_LOW, zsel, dummy); + rdmsrq(MSR_PIC_ZSEL_LOW, zsel.q); shift = ((cmp == MFGPT_CMP1 ? 0 : 4) + timer->nr % 4) * 4; - if (((zsel >> shift) & 0xF) == 2) + if (((zsel.l >> shift) & 0xF) == 2) return -EIO; /* Choose IRQ: if none supplied, keep IRQ already set or use default */ if (!*irq) - *irq = (zsel >> shift) & 0xF; + *irq = (zsel.l >> shift) & 0xF; if (!*irq) *irq = CONFIG_CS5535_MFGPT_DEFAULT_IRQ; /* Can't use IRQ if it's 0 (=disabled), 2, or routed to LPC */ if (*irq < 1 || *irq == 2 || *irq > 15) return -EIO; - rdmsr(MSR_PIC_IRQM_LPC, lpc, dummy); - if (lpc & (1 << *irq)) + rdmsrq(MSR_PIC_IRQM_LPC, lpc.q); + if (lpc.l & (1 << *irq)) return -EIO; /* All chosen and checked - go for it */ if (cs5535_mfgpt_toggle_event(timer, cmp, MFGPT_EVENT_IRQ, enable)) return -EIO; if (enable) { - zsel = (zsel & ~(0xF << shift)) | (*irq << shift); - wrmsr(MSR_PIC_ZSEL_LOW, zsel, dummy); + zsel.l = (zsel.l & ~(0xF << shift)) | (*irq << shift); + zsel.h = lpc.h; + wrmsrq(MSR_PIC_ZSEL_LOW, zsel.q); } return 0; @@ -249,11 +251,8 @@ EXPORT_SYMBOL_GPL(cs5535_mfgpt_write); */ static void reset_all_timers(void) { - uint32_t val, dummy; - /* The following undocumented bit resets the MFGPT timers */ - val = 0xFF; dummy = 0; - wrmsr(MSR_MFGPT_SETUP, val, dummy); + wrmsrq(MSR_MFGPT_SETUP, 0xff); } /* diff --git a/drivers/misc/enclosure.c b/drivers/misc/enclosure.c index de457378c501..bdbff5178ec7 100644 --- a/drivers/misc/enclosure.c +++ b/drivers/misc/enclosure.c @@ -148,8 +148,7 @@ enclosure_register(struct device *dev, const char *name, int components, return edev; err: - put_device(edev->edev.parent); - kfree(edev); + put_device(&edev->edev); return ERR_PTR(err); } EXPORT_SYMBOL_GPL(enclosure_register); diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index d4fac2caca86..af18ff1992ee 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -70,8 +70,6 @@ #define ADSP_MMAP_HEAP_ADDR 4 /* MAP static DMA buffer on DSP User PD */ #define ADSP_MMAP_DMA_BUFFER 6 -/* Add memory to static PD pool protection thru hypervisor */ -#define ADSP_MMAP_REMOTE_HEAP_ADDR 8 /* Add memory to userPD pool, for user heap */ #define ADSP_MMAP_ADD_PAGES 0x1000 /* Add memory to userPD pool, for LLC heap */ @@ -314,10 +312,14 @@ struct fastrpc_channel_ctx { struct kref refcount; /* Flag if dsp attributes are cached */ bool valid_attributes; + /* Flag if audio PD init mem was allocated */ + bool audio_init_mem; + /* Audio PD reserved remote heap region */ + phys_addr_t remote_heap_addr; + u64 remote_heap_size; u32 dsp_attributes[FASTRPC_MAX_DSP_ATTRIBUTES]; struct fastrpc_device *secure_fdevice; struct fastrpc_device *fdevice; - struct fastrpc_buf *remote_heap; struct list_head invoke_interrupted_mmaps; bool secure; bool unsigned_support; @@ -927,7 +929,7 @@ static int fastrpc_map_attach(struct fastrpc_user *fl, int fd, dev_dbg(sess->dev, "Bad size passed len 0x%llx map size 0x%llx\n", len, map->size); err = -EINVAL; - goto map_err; + goto get_err; } map->va = sg_virt(map->table->sgl); map->len = len; @@ -950,7 +952,7 @@ static int fastrpc_map_attach(struct fastrpc_user *fl, int fd, dev_err(sess->dev, "Failed to assign memory with dma_addr %pad size 0x%llx err %d\n", &map->dma_addr, map->len, err); - goto map_err; + goto get_err; } } spin_lock(&fl->lock); @@ -1454,15 +1456,24 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, struct fastrpc_init_create_static init; struct fastrpc_invoke_args *args; struct fastrpc_phy_page pages[1]; + struct fastrpc_channel_ctx *cctx = fl->cctx; char *name; int err; - bool scm_done = false; struct { int client_id; u32 namelen; u32 pageslen; } inbuf; u32 sc; + unsigned long flags; + bool sent_heap = false; + + if (!cctx->remote_heap_addr || !cctx->remote_heap_size) { + err = -ENOMEM; + dev_err(fl->sctx->dev, + "remote heap memory region is not added\n"); + return err; + } args = kzalloc_objs(*args, FASTRPC_CREATE_STATIC_PROCESS_NARGS); if (!args) @@ -1486,31 +1497,6 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, inbuf.client_id = fl->client_id; inbuf.namelen = init.namelen; inbuf.pageslen = 0; - if (!fl->cctx->remote_heap) { - err = fastrpc_remote_heap_alloc(fl, fl->sctx->dev, init.memlen, - &fl->cctx->remote_heap); - if (err) - goto err_name; - - /* Map if we have any heap VMIDs associated with this ADSP Static Process. */ - if (fl->cctx->vmcount) { - u64 src_perms = BIT(QCOM_SCM_VMID_HLOS); - - err = qcom_scm_assign_mem(fl->cctx->remote_heap->dma_addr, - (u64)fl->cctx->remote_heap->size, - &src_perms, - fl->cctx->vmperms, fl->cctx->vmcount); - if (err) { - dev_err(fl->sctx->dev, - "Failed to assign memory with dma_addr %pad size 0x%llx err %d\n", - &fl->cctx->remote_heap->dma_addr, - fl->cctx->remote_heap->size, err); - goto err_map; - } - scm_done = true; - inbuf.pageslen = 1; - } - } fl->pd = USER_PD; @@ -1522,8 +1508,25 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, args[1].length = inbuf.namelen; args[1].fd = -1; - pages[0].addr = fl->cctx->remote_heap->dma_addr; - pages[0].size = fl->cctx->remote_heap->size; + /* + * Audio PD is a static PD and retains the remote heap + * information across daemon restarts. Therefore only + * the first attach should provide heap information to + * DSP. Subsequent attaches reuse the previously + * initialized memory pool. + */ + spin_lock_irqsave(&cctx->lock, flags); + if (!cctx->audio_init_mem) { + pages[0].addr = cctx->remote_heap_addr; + pages[0].size = cctx->remote_heap_size; + cctx->audio_init_mem = true; + inbuf.pageslen = 1; + sent_heap = true; + } else { + pages[0].addr = 0; + pages[0].size = 0; + } + spin_unlock_irqrestore(&cctx->lock, flags); args[2].ptr = (u64)(uintptr_t) pages; args[2].length = sizeof(*pages); @@ -1541,27 +1544,11 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, return 0; err_invoke: - if (fl->cctx->vmcount && scm_done) { - u64 src_perms = 0; - struct qcom_scm_vmperm dst_perms; - u32 i; - - for (i = 0; i < fl->cctx->vmcount; i++) - src_perms |= BIT(fl->cctx->vmperms[i].vmid); - - dst_perms.vmid = QCOM_SCM_VMID_HLOS; - dst_perms.perm = QCOM_SCM_PERM_RWX; - err = qcom_scm_assign_mem(fl->cctx->remote_heap->dma_addr, - (u64)fl->cctx->remote_heap->size, - &src_perms, &dst_perms, 1); - if (err) - dev_err(fl->sctx->dev, "Failed to assign memory dma_addr %pad size 0x%llx err %d\n", - &fl->cctx->remote_heap->dma_addr, fl->cctx->remote_heap->size, err); + if (sent_heap) { + spin_lock_irqsave(&cctx->lock, flags); + cctx->audio_init_mem = false; + spin_unlock_irqrestore(&cctx->lock, flags); } -err_map: - fastrpc_buf_free(fl->cctx->remote_heap); - fl->cctx->remote_heap = NULL; -err_name: kfree(name); err: kfree(args); @@ -1783,7 +1770,7 @@ static int fastrpc_device_open(struct inode *inode, struct file *filp) fl->sctx = fastrpc_session_alloc(fl); if (!fl->sctx) { - dev_err(&cctx->rpdev->dev, "No session available\n"); + dev_err_ratelimited(&cctx->rpdev->dev, "No session available\n"); mutex_destroy(&fl->mutex); kfree(fl); fastrpc_channel_ctx_put(cctx); @@ -1937,7 +1924,7 @@ static int fastrpc_get_info_from_kernel(struct fastrpc_ioctl_capability *cap, kfree(dsp_attributes); return -EOPNOTSUPP; } else if (err) { - dev_err(&cctx->rpdev->dev, "Error: dsp information is incorrect err: %d\n", err); + dev_dbg(&cctx->rpdev->dev, "Error: dsp information is incorrect err: %d\n", err); kfree(dsp_attributes); return err; } @@ -2086,7 +2073,7 @@ static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp) if (copy_from_user(&req, argp, sizeof(req))) return -EFAULT; - if (req.flags != ADSP_MMAP_ADD_PAGES && req.flags != ADSP_MMAP_REMOTE_HEAP_ADDR) { + if (req.flags != ADSP_MMAP_ADD_PAGES) { dev_err(dev, "flag not supported 0x%x\n", req.flags); return -EINVAL; @@ -2097,10 +2084,7 @@ static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp) return -EINVAL; } - if (req.flags == ADSP_MMAP_REMOTE_HEAP_ADDR) - err = fastrpc_remote_heap_alloc(fl, dev, req.size, &buf); - else - err = fastrpc_buf_alloc(fl, dev, req.size, &buf); + err = fastrpc_buf_alloc(fl, dev, req.size, &buf); if (err) { dev_err(dev, "failed to allocate buffer\n"); @@ -2139,20 +2123,6 @@ static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp) /* let the client know the address to use */ req.vaddrout = rsp_msg.vaddr; - /* Add memory to static PD pool, protection thru hypervisor */ - if (req.flags == ADSP_MMAP_REMOTE_HEAP_ADDR && fl->cctx->vmcount) { - u64 src_perms = BIT(QCOM_SCM_VMID_HLOS); - - err = qcom_scm_assign_mem(buf->dma_addr, (u64)buf->size, - &src_perms, fl->cctx->vmperms, fl->cctx->vmcount); - if (err) { - dev_err(fl->sctx->dev, - "Failed to assign memory dma_addr %pad size 0x%llx err %d", - &buf->dma_addr, buf->size, err); - goto err_assign; - } - } - spin_lock(&fl->lock); list_add_tail(&buf->node, &fl->mmaps); spin_unlock(&fl->lock); @@ -2533,6 +2503,45 @@ static const struct of_device_id fastrpc_poll_supported_machines[] __maybe_unuse {}, }; +static int fastrpc_init_reserved_mem(struct fastrpc_channel_ctx *cctx, + struct device *rdev, u32 domain_id) +{ + struct resource res; + u64 src_perms; + int err; + + /* Only SDSP and ADSP domains use a reserved remote heap region */ + if (domain_id != SDSP_DOMAIN_ID && domain_id != ADSP_DOMAIN_ID) + return 0; + + err = of_reserved_mem_region_to_resource(rdev->of_node, 0, &res); + if (err) { + /* + * The reserved-memory region is optional at probe time. For + * the Audio PD (ADSP) domain its absence is validated later in + * fastrpc_init_create_static_process(), so keep probing here. + */ + return 0; + } + + /* + * Audio PD (ADSP) is a static PD: cache the region so its lifetime + * is tied to the rpmsg channel instead of being controlled by + * userspace. + */ + if (domain_id == ADSP_DOMAIN_ID) { + cctx->remote_heap_addr = res.start; + cctx->remote_heap_size = resource_size(&res); + } + + if (!cctx->vmcount) + return 0; + + src_perms = BIT(QCOM_SCM_VMID_HLOS); + return qcom_scm_assign_mem(res.start, resource_size(&res), &src_perms, + cctx->vmperms, cctx->vmcount); +} + static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) { struct device *rdev = &rpdev->dev; @@ -2558,8 +2567,8 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) return -EINVAL; } - if (of_reserved_mem_device_init_by_idx(rdev, rdev->of_node, 0)) - dev_info(rdev, "no reserved DMA memory for FASTRPC\n"); + if (devm_of_reserved_mem_device_init(rdev)) + dev_dbg(rdev, "no reserved DMA memory for FASTRPC\n"); vmcount = of_property_read_variable_u32_array(rdev->of_node, "qcom,vmids", &vmids[0], 0, FASTRPC_MAX_VMIDS); @@ -2580,21 +2589,9 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) } } - if (domain_id == SDSP_DOMAIN_ID) { - struct resource res; - u64 src_perms; - - err = of_reserved_mem_region_to_resource(rdev->of_node, 0, &res); - if (!err) { - src_perms = BIT(QCOM_SCM_VMID_HLOS); - - err = qcom_scm_assign_mem(res.start, resource_size(&res), &src_perms, - data->vmperms, data->vmcount); - if (err) - goto err_free_data; - } - - } + err = fastrpc_init_reserved_mem(data, rdev, domain_id); + if (err) + goto err_free_data; secure_dsp = !(of_property_read_bool(rdev->of_node, "qcom,non-secure-domain")); data->secure = secure_dsp; @@ -2694,8 +2691,25 @@ static void fastrpc_rpmsg_remove(struct rpmsg_device *rpdev) list_for_each_entry_safe(buf, b, &cctx->invoke_interrupted_mmaps, node) list_del(&buf->node); - if (cctx->remote_heap) - fastrpc_buf_free(cctx->remote_heap); + if (cctx->remote_heap_size && cctx->vmcount) { + u64 src_perms = 0; + int err, i; + struct qcom_scm_vmperm dst_perms; + + for (i = 0; i < cctx->vmcount; i++) + src_perms |= BIT(cctx->vmperms[i].vmid); + + dst_perms.vmid = QCOM_SCM_VMID_HLOS; + dst_perms.perm = QCOM_SCM_PERM_RWX; + + err = qcom_scm_assign_mem(cctx->remote_heap_addr, + cctx->remote_heap_size, &src_perms, + &dst_perms, 1); + if (err) + dev_err(&rpdev->dev, + "Failed to assign memory back to HLOS: addr %pa size %#llx err %d\n", + &cctx->remote_heap_addr, cctx->remote_heap_size, err); + } of_platform_depopulate(&rpdev->dev); diff --git a/drivers/misc/gehc-achc.c b/drivers/misc/gehc-achc.c index 9792189c0923..2c1222edb5dc 100644 --- a/drivers/misc/gehc-achc.c +++ b/drivers/misc/gehc-achc.c @@ -369,7 +369,7 @@ static int ezport_firmware_flash_data(struct spi_device *spi, dev_dbg(&spi->dev, "EzPort verify flashed data...\n"); ret = ezport_firmware_compare_data(spi, data, size); - /* allow missing FW verfication in secure mode */ + /* allow missing FW verification in secure mode */ if (ret == -EACCES) ret = 0; diff --git a/drivers/misc/genwqe/card_base.c b/drivers/misc/genwqe/card_base.c index e602eb3c51fa..3eb843af31a5 100644 --- a/drivers/misc/genwqe/card_base.c +++ b/drivers/misc/genwqe/card_base.c @@ -286,7 +286,7 @@ int genwqe_flash_readback_fails(struct genwqe_dev *cd) * @cd: GenWQE device information * * Note: From a design perspective it turned out to be a bad idea to - * use codes here to specifiy the frequency/speed values. An old + * use codes here to specify the frequency/speed values. An old * driver cannot understand new codes and is therefore always a * problem. Better is to measure out the value or put the * speed/frequency directly into a register which is always a valid @@ -904,7 +904,7 @@ static int genwqe_reload_bistream(struct genwqe_dev *cd) * * Condition for the health-thread to trigger: * a) when a kthread_stop() request comes in or - * b) a critical GFIR occured + * b) a critical GFIR occurred * * Informational GFIRs are checked and potentially printed in * GENWQE_HEALTH_CHECK_INTERVAL seconds. @@ -1210,10 +1210,10 @@ static int genwqe_probe(struct pci_dev *pci_dev, } /** - * genwqe_remove() - Called when device is removed (hot-plugable) + * genwqe_remove() - Called when device is removed (hot-pluggable) * @pci_dev: PCI device information struct * - * Or when driver is unloaded respecitively when unbind is done. + * Or when driver is unloaded respectively when unbind is done. */ static void genwqe_remove(struct pci_dev *pci_dev) { diff --git a/drivers/misc/genwqe/card_ddcb.c b/drivers/misc/genwqe/card_ddcb.c index 969178573940..ebe1029c9fb5 100644 --- a/drivers/misc/genwqe/card_ddcb.c +++ b/drivers/misc/genwqe/card_ddcb.c @@ -460,7 +460,7 @@ pick_next_one: /** * __genwqe_wait_ddcb(): Waits until DDCB is completed * @cd: pointer to genwqe device descriptor - * @req: pointer to requsted DDCB parameters + * @req: pointer to requested DDCB parameters * * The Service Layer will update the RETC in DDCB when processing is * pending or done. @@ -543,7 +543,7 @@ int __genwqe_wait_ddcb(struct genwqe_dev *cd, struct ddcb_requ *req) return -EINVAL; } - /* Severe error occured. Driver is forced to stop operation */ + /* Severe error occurred. Driver is forced to stop operation */ if (cd->card_state != GENWQE_CARD_USED) { dev_err(&pci_dev->dev, "[%s] err: DDCB#%d forced to stop (rc=%d)\n", @@ -1103,6 +1103,9 @@ static void free_ddcb_queue(struct genwqe_dev *cd, struct ddcb_queue *queue) queue_size = roundup(queue->ddcb_max * sizeof(struct ddcb), PAGE_SIZE); + kfree(queue->ddcb_waitqs); + queue->ddcb_waitqs = NULL; + kfree(queue->ddcb_req); queue->ddcb_req = NULL; diff --git a/drivers/misc/genwqe/card_utils.c b/drivers/misc/genwqe/card_utils.c index e3b5fb337182..7c57cb881d7f 100644 --- a/drivers/misc/genwqe/card_utils.c +++ b/drivers/misc/genwqe/card_utils.c @@ -1003,7 +1003,7 @@ u64 genwqe_read_vreg(struct genwqe_dev *cd, u32 reg, int func) * @cd: genwqe device descriptor * * Note: From a design perspective it turned out to be a bad idea to - * use codes here to specifiy the frequency/speed values. An old + * use codes here to specify the frequency/speed values. An old * driver cannot understand new codes and is therefore always a * problem. Better is to measure out the value or put the * speed/frequency directly into a register which is always a valid diff --git a/drivers/misc/ibmvmc.c b/drivers/misc/ibmvmc.c index da82b1edb7c9..6afef0788b90 100644 --- a/drivers/misc/ibmvmc.c +++ b/drivers/misc/ibmvmc.c @@ -154,7 +154,7 @@ static void ibmvmc_release_crq_queue(struct crq_server_adapter *adapter) dma_unmap_single(adapter->dev, queue->msg_token, queue->size * sizeof(*queue->msgs), DMA_BIDIRECTIONAL); - free_page((unsigned long)queue->msgs); + kfree(queue->msgs); } /** @@ -2130,7 +2130,7 @@ static int ibmvmc_init_crq_queue(struct crq_server_adapter *adapter) int rc = 0; int retrc = 0; - queue->msgs = (struct ibmvmc_crq_msg *)get_zeroed_page(GFP_KERNEL); + queue->msgs = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!queue->msgs) goto malloc_failed; @@ -2176,6 +2176,7 @@ static int ibmvmc_init_crq_queue(struct crq_server_adapter *adapter) rc = vio_enable_interrupts(vdev); if (rc != 0) { dev_err(adapter->dev, "Error %d enabling interrupts!!!\n", rc); + free_irq(vdev->irq, (void *)adapter); goto req_irq_failed; } @@ -2192,7 +2193,7 @@ reg_crq_failed: queue->msg_token, queue->size * sizeof(*queue->msgs), DMA_BIDIRECTIONAL); map_failed: - free_page((unsigned long)queue->msgs); + kfree(queue->msgs); malloc_failed: return -ENOMEM; } diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c index c9b526a212d0..4e162905d0c0 100644 --- a/drivers/misc/isl29003.c +++ b/drivers/misc/isl29003.c @@ -416,7 +416,6 @@ static void isl29003_remove(struct i2c_client *client) kfree(i2c_get_clientdata(client)); } -#ifdef CONFIG_PM_SLEEP static int isl29003_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); @@ -441,12 +440,7 @@ static int isl29003_resume(struct device *dev) data->power_state_before_suspend); } -static SIMPLE_DEV_PM_OPS(isl29003_pm_ops, isl29003_suspend, isl29003_resume); -#define ISL29003_PM_OPS (&isl29003_pm_ops) - -#else -#define ISL29003_PM_OPS NULL -#endif /* CONFIG_PM_SLEEP */ +static DEFINE_SIMPLE_DEV_PM_OPS(isl29003_pm_ops, isl29003_suspend, isl29003_resume); static const struct i2c_device_id isl29003_id[] = { { .name = "isl29003" }, @@ -457,7 +451,7 @@ MODULE_DEVICE_TABLE(i2c, isl29003_id); static struct i2c_driver isl29003_driver = { .driver = { .name = ISL29003_DRV_NAME, - .pm = ISL29003_PM_OPS, + .pm = pm_sleep_ptr(&isl29003_pm_ops), }, .probe = isl29003_probe, .remove = isl29003_remove, diff --git a/drivers/misc/issei/Kconfig b/drivers/misc/issei/Kconfig index 12a3fd809969..dfcfa2d30b9e 100644 --- a/drivers/misc/issei/Kconfig +++ b/drivers/misc/issei/Kconfig @@ -2,6 +2,7 @@ # Copyright (C) 2023-2026 Intel Corporation config INTEL_SSEI tristate "Intel Silicon Security Engine Interface" + depends on X86 help The ISSEI (Intel Silicon Security Engine Interface) subsystem provides a communication channel between the host and the diff --git a/drivers/misc/lis3lv02d/lis3lv02d_spi.c b/drivers/misc/lis3lv02d/lis3lv02d_spi.c index 5bf95176a00e..7ef073414673 100644 --- a/drivers/misc/lis3lv02d/lis3lv02d_spi.c +++ b/drivers/misc/lis3lv02d/lis3lv02d_spi.c @@ -105,7 +105,6 @@ static void lis302dl_spi_remove(struct spi_device *spi) lis3lv02d_remove_fs(&lis3_dev); } -#ifdef CONFIG_PM_SLEEP static int lis3lv02d_spi_suspend(struct device *dev) { struct spi_device *spi = to_spi_device(dev); @@ -127,15 +126,14 @@ static int lis3lv02d_spi_resume(struct device *dev) return 0; } -#endif -static SIMPLE_DEV_PM_OPS(lis3lv02d_spi_pm, lis3lv02d_spi_suspend, +static DEFINE_SIMPLE_DEV_PM_OPS(lis3lv02d_spi_pm, lis3lv02d_spi_suspend, lis3lv02d_spi_resume); static struct spi_driver lis302dl_spi_driver = { .driver = { .name = DRV_NAME, - .pm = &lis3lv02d_spi_pm, + .pm = pm_sleep_ptr(&lis3lv02d_spi_pm), .of_match_table = of_match_ptr(lis302dl_spi_dt_ids), }, .probe = lis302dl_spi_probe, diff --git a/drivers/misc/mei/gsc-me.c b/drivers/misc/mei/gsc-me.c index 376840b2c354..b38ddb04f2e8 100644 --- a/drivers/misc/mei/gsc-me.c +++ b/drivers/misc/mei/gsc-me.c @@ -242,7 +242,7 @@ static int __maybe_unused mei_gsc_pm_runtime_resume(struct device *device) if (mei_me_hw_use_polling(hw)) { hw->is_active = true; - wake_up(&hw->wait_active); + wake_up_interruptible(&hw->wait_active); } mutex_unlock(&dev->device_lock); diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index e7fbc02fb70f..1c3ab9eb8b1b 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -5,6 +5,7 @@ */ #include <linux/bitfield.h> +#include <linux/cleanup.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/kthread.h> @@ -1290,28 +1291,15 @@ irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id) } EXPORT_SYMBOL_GPL(mei_me_irq_quick_handler); -/** - * mei_me_irq_thread_handler - function called after ISR to handle the interrupt - * processing. - * - * @irq: The irq number - * @dev_id: pointer to the device structure - * - * Return: irqreturn_t - * - */ -irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) +static irqreturn_t __mei_me_irq_thread_handler(struct mei_device *dev) { - struct mei_device *dev = (struct mei_device *) dev_id; struct list_head cmpl_list; bool pg_blocked; s32 slots; u32 hcsr; int rets = 0; - dev_dbg(&dev->dev, "function called after ISR to handle the interrupt processing.\n"); /* initialize our complete list */ - mutex_lock(&dev->device_lock); hcsr = mei_hcsr_read(dev); me_intr_clear(dev, hcsr); @@ -1406,13 +1394,33 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) end: dev_dbg(&dev->dev, "interrupt thread end ret = %d\n", rets); mei_me_intr_enable(dev); - mutex_unlock(&dev->device_lock); return IRQ_HANDLED; } + +/** + * mei_me_irq_thread_handler - function called after ISR to handle the interrupt + * processing. + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * Return: irqreturn_t + * + */ +irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) +{ + struct mei_device *dev = (struct mei_device *) dev_id; + + dev_dbg(&dev->dev, "function called after ISR to handle the interrupt processing.\n"); + + guard(mutex)(&dev->device_lock); + + return __mei_me_irq_thread_handler(dev); +} EXPORT_SYMBOL_GPL(mei_me_irq_thread_handler); -#define MEI_POLLING_TIMEOUT_ACTIVE 100 -#define MEI_POLLING_TIMEOUT_IDLE 500 +#define MEI_POLL_ACTIVE_MS 20 +#define MEI_POLL_IDLE_MS 500 /** * mei_me_polling_thread - interrupt register polling thread @@ -1420,48 +1428,59 @@ EXPORT_SYMBOL_GPL(mei_me_irq_thread_handler); * @_dev: mei device * * The thread monitors the interrupt source register and calls - * mei_me_irq_thread_handler() to handle the firmware + * __mei_me_irq_thread_handler() to handle the firmware * input. * - * The function polls in MEI_POLLING_TIMEOUT_ACTIVE timeout + * The function polls in MEI_POLL_ACTIVE_MS timeout * in case there was an event, in idle case the polling - * time increases yet again by MEI_POLLING_TIMEOUT_ACTIVE - * up to MEI_POLLING_TIMEOUT_IDLE. + * time increases yet again by MEI_POLL_ACTIVE_MS + * up to MEI_POLL_IDLE_MS. * * Return: always 0 */ int mei_me_polling_thread(void *_dev) { struct mei_device *dev = _dev; - irqreturn_t irq_ret; - long polling_timeout = MEI_POLLING_TIMEOUT_ACTIVE; + long polling_timeout = MEI_POLL_ACTIVE_MS; + bool have_interrupt; dev_dbg(&dev->dev, "kernel thread is running\n"); while (!kthread_should_stop()) { struct mei_me_hw *hw = to_me_hw(dev); u32 hcsr; - wait_event_timeout(hw->wait_active, - hw->is_active || kthread_should_stop(), - msecs_to_jiffies(MEI_POLLING_TIMEOUT_IDLE)); + if (!hw->is_active) + wait_event_interruptible(hw->wait_active, + hw->is_active || kthread_should_stop()); + else + wait_event_interruptible_timeout(hw->wait_active, + hw->is_active || kthread_should_stop(), + msecs_to_jiffies(MEI_POLL_IDLE_MS)); if (kthread_should_stop()) break; - hcsr = mei_hcsr_read(dev); - if (me_intr_src(hcsr)) { - polling_timeout = MEI_POLLING_TIMEOUT_ACTIVE; - irq_ret = mei_me_irq_thread_handler(1, dev); - if (irq_ret != IRQ_HANDLED) - dev_err(&dev->dev, "irq_ret %d\n", irq_ret); - } else { - /* - * Increase timeout by MEI_POLLING_TIMEOUT_ACTIVE - * up to MEI_POLLING_TIMEOUT_IDLE - */ - polling_timeout = clamp_val(polling_timeout + MEI_POLLING_TIMEOUT_ACTIVE, - MEI_POLLING_TIMEOUT_ACTIVE, - MEI_POLLING_TIMEOUT_IDLE); + scoped_guard(mutex, &dev->device_lock) { + if (!hw->is_active) { + have_interrupt = false; + } else { + hcsr = mei_hcsr_read(dev); + have_interrupt = !!me_intr_src(hcsr); + } + + if (have_interrupt) { + polling_timeout = MEI_POLL_ACTIVE_MS; + dev_dbg(&dev->dev, "polling interrupt processing\n"); + __mei_me_irq_thread_handler(dev); + } else { + /* + * Increase timeout by MEI_POLL_ACTIVE_MS + * up to MEI_POLL_IDLE_MS + */ + polling_timeout = clamp_val(polling_timeout + MEI_POLL_ACTIVE_MS, + MEI_POLL_ACTIVE_MS, + MEI_POLL_IDLE_MS); + } } schedule_timeout_interruptible(msecs_to_jiffies(polling_timeout)); diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 766f119f7ed0..82444d67db2a 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -305,10 +305,8 @@ static void mei_reset_work(struct work_struct *work) schedule_work(&dev->reset_work); } -void mei_stop(struct mei_device *dev) +static void __mei_stop(struct mei_device *dev) { - dev_dbg(&dev->dev, "stopping the device.\n"); - mutex_lock(&dev->device_lock); mei_set_devstate(dev, MEI_DEV_POWERING_DOWN); mutex_unlock(&dev->device_lock); @@ -318,6 +316,35 @@ void mei_stop(struct mei_device *dev) mutex_unlock(&dev->device_lock); mei_cancel_work(dev); +} + +/** + * mei_stop_fast - stop driver, clean bus and disable driver without resetting HW link + * + * @dev: the device structure + */ +void mei_stop_fast(struct mei_device *dev) +{ + dev_dbg(&dev->dev, "stopping the device fast.\n"); + + __mei_stop(dev); + + mutex_lock(&dev->device_lock); + mei_set_devstate(dev, MEI_DEV_DISABLED); + mutex_unlock(&dev->device_lock); +} +EXPORT_SYMBOL_GPL(mei_stop_fast); + +/** + * mei_stop - stop driver, clean bus and disable driver with resetting HW link + * + * @dev: the device structure + */ +void mei_stop(struct mei_device *dev) +{ + dev_dbg(&dev->dev, "stopping the device.\n"); + + __mei_stop(dev); mei_clear_interrupts(dev); mei_synchronize_irq(dev); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index e651b06704a1..79a7c3e63397 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -736,6 +736,7 @@ int mei_reset(struct mei_device *dev); int mei_start(struct mei_device *dev); int mei_restart(struct mei_device *dev); void mei_stop(struct mei_device *dev); +void mei_stop_fast(struct mei_device *dev); void mei_cancel_work(struct mei_device *dev); void mei_set_devstate(struct mei_device *dev, enum mei_dev_state state); diff --git a/drivers/misc/mei/pci-csc.c b/drivers/misc/mei/pci-csc.c index 6e1e8008f267..fc09cfb5cdc7 100644 --- a/drivers/misc/mei/pci-csc.c +++ b/drivers/misc/mei/pci-csc.c @@ -11,6 +11,7 @@ #include <linux/err.h> #include <linux/errno.h> #include <linux/interrupt.h> +#include <linux/kthread.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/pci.h> @@ -26,6 +27,10 @@ #define MEI_CSC_HECI2_OFFSET 0x1000 +static bool use_polling = true; +module_param(use_polling, bool, 0600); +MODULE_PARM_DESC(use_polling, "Use polling instead of interrupts"); + static int mei_csc_read_fws(const struct mei_device *mdev, int where, const char *name, u32 *val) { struct mei_me_hw *hw = to_me_hw(mdev); @@ -87,20 +92,40 @@ static int mei_csc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) pci_set_drvdata(pdev, mdev); - err = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX | PCI_IRQ_MSI); - if (err < 0) { - dev_err_probe(dev, err, "Failed to allocate IRQ.\n"); - goto err_mei_unreg; + if (use_polling) { + dev_dbg(dev, "Using polling thread\n"); + hw->irq = -1; } - hw->irq = pci_irq_vector(pdev, 0); - - /* request and enable interrupt */ - err = request_threaded_irq(hw->irq, - mei_me_irq_quick_handler, mei_me_irq_thread_handler, - IRQF_SHARED | IRQF_ONESHOT, KBUILD_MODNAME, mdev); - if (err) - goto err_free_irq_vectors; + /* use polling */ + if (mei_me_hw_use_polling(hw)) { + mei_disable_interrupts(mdev); + mei_clear_interrupts(mdev); + init_waitqueue_head(&hw->wait_active); + hw->is_active = true; /* start in active mode for initialization */ + hw->polling_thread = kthread_run(mei_me_polling_thread, mdev, + "kmecscirqd/%s", dev_name(dev)); + if (IS_ERR(hw->polling_thread)) { + err = PTR_ERR(hw->polling_thread); + dev_err_probe(dev, err, "unable to create kernel thread.\n"); + goto err_mei_unreg; + } + } else { + err = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX | PCI_IRQ_MSI); + if (err < 0) { + dev_err_probe(dev, err, "Failed to allocate IRQ.\n"); + goto err_mei_unreg; + } + + hw->irq = pci_irq_vector(pdev, 0); + + /* request and enable interrupt */ + err = request_threaded_irq(hw->irq, + mei_me_irq_quick_handler, mei_me_irq_thread_handler, + IRQF_SHARED | IRQF_ONESHOT, KBUILD_MODNAME, mdev); + if (err) + goto err_free_irq_vectors; + } /* * Continue to char device setup in spite of firmware handshake failure. @@ -126,7 +151,8 @@ static int mei_csc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) return 0; err_free_irq_vectors: - pci_free_irq_vectors(pdev); + if (!mei_me_hw_use_polling(hw)) + pci_free_irq_vectors(pdev); err_mei_unreg: mei_deregister(mdev); return err; @@ -141,9 +167,14 @@ static void mei_csc_shutdown(struct pci_dev *pdev) mei_stop(mdev); + if (mei_me_hw_use_polling(hw)) + kthread_stop(hw->polling_thread); + mei_disable_interrupts(mdev); - free_irq(hw->irq, mdev); - pci_free_irq_vectors(pdev); + if (!mei_me_hw_use_polling(hw)) { + free_irq(hw->irq, mdev); + pci_free_irq_vectors(pdev); + } } static void mei_csc_remove(struct pci_dev *pdev) @@ -210,6 +241,9 @@ static int mei_csc_pm_runtime_suspend(struct device *dev) return -EAGAIN; hw->pg_state = MEI_PG_ON; + if (mei_me_hw_use_polling(hw)) + hw->is_active = false; + return 0; } @@ -219,8 +253,13 @@ static int mei_csc_pm_runtime_resume(struct device *dev) struct mei_me_hw *hw = to_me_hw(mdev); irqreturn_t irq_ret; - scoped_guard(mutex, &mdev->device_lock) + scoped_guard(mutex, &mdev->device_lock) { hw->pg_state = MEI_PG_OFF; + if (mei_me_hw_use_polling(hw)) { + hw->is_active = true; + wake_up_interruptible(&hw->wait_active); + } + } /* Process all queues that wait for resume */ irq_ret = mei_me_irq_thread_handler(1, mdev); @@ -230,6 +269,77 @@ static int mei_csc_pm_runtime_resume(struct device *dev) return 0; } +static pci_ers_result_t mei_csc_pci_error_detected(struct pci_dev *pdev, pci_channel_state_t state) +{ + struct mei_device *mdev = pci_get_drvdata(pdev); + struct mei_me_hw *hw = to_me_hw(mdev); + + dev_info(&pdev->dev, "error recovery: error detected. state %d\n", state); + + scoped_guard(mutex, &mdev->device_lock) + if (mei_me_hw_use_polling(hw)) + hw->is_active = false; + + mei_synchronize_irq(mdev); + mei_stop_fast(mdev); + pci_disable_device(pdev); + + switch (state) { + case pci_channel_io_normal: + return PCI_ERS_RESULT_CAN_RECOVER; + case pci_channel_io_perm_failure: + return PCI_ERS_RESULT_DISCONNECT; + case pci_channel_io_frozen: + return PCI_ERS_RESULT_NEED_RESET; + default: + dev_err(&pdev->dev, "Unknown state %d\n", state); + return PCI_ERS_RESULT_NEED_RESET; + } +} + +static pci_ers_result_t mei_csc_pci_error_slot_reset(struct pci_dev *pdev) +{ + int err; + + pci_restore_state(pdev); + pci_set_master(pdev); + + err = pci_enable_device(pdev); + if (err < 0) { + dev_err(&pdev->dev, "Cannot re-enable PCI device after reset. err = %d\n", err); + return PCI_ERS_RESULT_DISCONNECT; + } + + return PCI_ERS_RESULT_RECOVERED; +} + +static void mei_csc_pci_error_resume(struct pci_dev *pdev) +{ + struct mei_device *mdev = pci_get_drvdata(pdev); + struct mei_me_hw *hw = to_me_hw(mdev); + + dev_info(&pdev->dev, "error recovery: resume\n"); + + scoped_guard(mutex, &mdev->device_lock) { + if (mei_me_hw_use_polling(hw)) { + hw->is_active = true; + wake_up_interruptible(&hw->wait_active); + } + } + + if (mei_restart(mdev)) + return; + + /* Start timer if stopped in error */ + schedule_delayed_work(&mdev->timer_work, HZ); +} + +static const struct pci_error_handlers mei_csc_pci_error_handlers = { + .error_detected = mei_csc_pci_error_detected, + .slot_reset = mei_csc_pci_error_slot_reset, + .resume = mei_csc_pci_error_resume, +}; + static const struct dev_pm_ops mei_csc_pm_ops = { .prepare = pm_sleep_ptr(mei_csc_pci_prepare), .complete = pm_sleep_ptr(mei_csc_pci_complete), @@ -250,6 +360,7 @@ static struct pci_driver mei_csc_driver = { .probe = mei_csc_probe, .remove = mei_csc_remove, .shutdown = mei_csc_shutdown, + .err_handler = &mei_csc_pci_error_handlers, .driver = { .pm = &mei_csc_pm_ops, .probe_type = PROBE_PREFER_ASYNCHRONOUS, diff --git a/drivers/misc/mei/vsc-tp.c b/drivers/misc/mei/vsc-tp.c index 432bc59f11f9..5410ec1c9328 100644 --- a/drivers/misc/mei/vsc-tp.c +++ b/drivers/misc/mei/vsc-tp.c @@ -293,7 +293,7 @@ int vsc_tp_xfer(struct vsc_tp *tp, u8 cmd, const void *obuf, size_t olen, pkt->hdr.seq = cpu_to_le32(++tp->seq); memcpy(pkt->buf, obuf, olen); - crc = ~crc32(~0, (u8 *)pkt, sizeof(pkt) + olen); + crc = ~crc32(~0, (u8 *)pkt, sizeof(pkt->hdr) + olen); memcpy(pkt->buf + olen, &crc, sizeof(crc)); ret = vsc_tp_wakeup_request(tp); @@ -496,30 +496,30 @@ static int vsc_tp_probe(struct spi_device *spi) init_waitqueue_head(&tp->xfer_wait); tp->spi = spi; + mutex_init(&tp->mutex); + mutex_init(&tp->event_notify_mutex); + INIT_WORK(&tp->event_work, vsc_tp_event_work); + irq_set_status_flags(spi->irq, IRQ_DISABLE_UNLAZY); ret = request_threaded_irq(spi->irq, NULL, vsc_tp_isr, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(dev), tp); if (ret) - return ret; - - mutex_init(&tp->mutex); - mutex_init(&tp->event_notify_mutex); - INIT_WORK(&tp->event_work, vsc_tp_event_work); + goto err_destroy_lock; /* only one child acpi device */ ret = acpi_dev_for_each_child(ACPI_COMPANION(dev), vsc_tp_match_any, &adev); if (!ret) { ret = -ENODEV; - goto err_destroy_lock; + goto err_free_irq; } pinfo.fwnode = acpi_fwnode_handle(adev); pdev = platform_device_register_full(&pinfo); if (IS_ERR(pdev)) { ret = PTR_ERR(pdev); - goto err_destroy_lock; + goto err_free_irq; } tp->pdev = pdev; @@ -527,9 +527,10 @@ static int vsc_tp_probe(struct spi_device *spi) return 0; -err_destroy_lock: +err_free_irq: free_irq(spi->irq, tp); +err_destroy_lock: cancel_work_sync(&tp->event_work); mutex_destroy(&tp->event_notify_mutex); mutex_destroy(&tp->mutex); diff --git a/drivers/misc/nsm.c b/drivers/misc/nsm.c index a3a9f9bfc6b7..b6d3fa79a5e8 100644 --- a/drivers/misc/nsm.c +++ b/drivers/misc/nsm.c @@ -117,7 +117,7 @@ static int cbor_object_get_array(u8 *cbor_object, size_t cbor_object_size, u8 ** array_len_p = &cbor_object[1]; switch (cbor_short_size) { - case CBOR_SHORT_SIZE_MAX_VALUE: /* short encoding */ + case 0 ... CBOR_SHORT_SIZE_MAX_VALUE: /* short encoding */ array_len = cbor_short_size; break; case CBOR_LONG_SIZE_U8: @@ -132,6 +132,8 @@ static int cbor_object_get_array(u8 *cbor_object, size_t cbor_object_size, u8 ** case CBOR_LONG_SIZE_U64: array_len = be64_to_cpup((__be64 *)array_len_p); break; + default: + return -EFAULT; } if (cbor_object_size < array_offset) @@ -439,6 +441,8 @@ static int nsm_device_probe(struct virtio_device *vdev) mutex_init(&nsm->lock); + virtio_device_ready(vdev); + /* Register as hwrng provider */ nsm->hwrng = (struct hwrng) { .read = nsm_rng_read, @@ -471,6 +475,7 @@ static int nsm_device_probe(struct virtio_device *vdev) err_misc: hwrng_unregister(&nsm->hwrng); err_hwrng: + virtio_reset_device(vdev); vdev->config->del_vqs(vdev); err_init_vq: return rc; diff --git a/drivers/misc/pch_phub.c b/drivers/misc/pch_phub.c index 15785597da40..5d3c5a298ba4 100644 --- a/drivers/misc/pch_phub.c +++ b/drivers/misc/pch_phub.c @@ -705,14 +705,14 @@ static void pch_phub_remove(struct pci_dev *pdev) kfree(chip); } -static int __maybe_unused pch_phub_suspend(struct device *dev_d) +static int pch_phub_suspend(struct device *dev_d) { device_wakeup_disable(dev_d); return 0; } -static int __maybe_unused pch_phub_resume(struct device *dev_d) +static int pch_phub_resume(struct device *dev_d) { device_wakeup_disable(dev_d); @@ -729,14 +729,14 @@ static const struct pci_device_id pch_phub_pcidev_id[] = { }; MODULE_DEVICE_TABLE(pci, pch_phub_pcidev_id); -static SIMPLE_DEV_PM_OPS(pch_phub_pm_ops, pch_phub_suspend, pch_phub_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(pch_phub_pm_ops, pch_phub_suspend, pch_phub_resume); static struct pci_driver pch_phub_driver = { .name = "pch_phub", .id_table = pch_phub_pcidev_id, .probe = pch_phub_probe, .remove = pch_phub_remove, - .driver.pm = &pch_phub_pm_ops, + .driver.pm = pm_sleep_ptr(&pch_phub_pm_ops), }; module_pci_driver(pch_phub_driver); diff --git a/drivers/misc/phantom.c b/drivers/misc/phantom.c index 331cae539290..ed57de14b876 100644 --- a/drivers/misc/phantom.c +++ b/drivers/misc/phantom.c @@ -459,7 +459,7 @@ static void phantom_remove(struct pci_dev *pdev) pci_disable_device(pdev); } -static int __maybe_unused phantom_suspend(struct device *dev_d) +static int phantom_suspend(struct device *dev_d) { struct phantom_device *dev = dev_get_drvdata(dev_d); @@ -471,7 +471,7 @@ static int __maybe_unused phantom_suspend(struct device *dev_d) return 0; } -static int __maybe_unused phantom_resume(struct device *dev_d) +static int phantom_resume(struct device *dev_d) { struct phantom_device *dev = dev_get_drvdata(dev_d); @@ -488,14 +488,14 @@ static struct pci_device_id phantom_pci_tbl[] = { }; MODULE_DEVICE_TABLE(pci, phantom_pci_tbl); -static SIMPLE_DEV_PM_OPS(phantom_pm_ops, phantom_suspend, phantom_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(phantom_pm_ops, phantom_suspend, phantom_resume); static struct pci_driver phantom_pci_driver = { .name = "phantom", .id_table = phantom_pci_tbl, .probe = phantom_probe, .remove = phantom_remove, - .driver.pm = &phantom_pm_ops, + .driver.pm = pm_sleep_ptr(&phantom_pm_ops), }; static CLASS_ATTR_STRING(version, 0444, PHANTOM_VERSION); diff --git a/drivers/misc/qcom-coincell.c b/drivers/misc/qcom-coincell.c index b58bd34fc471..8da3736652c3 100644 --- a/drivers/misc/qcom-coincell.c +++ b/drivers/misc/qcom-coincell.c @@ -9,11 +9,11 @@ #include <linux/of.h> #include <linux/regmap.h> #include <linux/platform_device.h> +#include <linux/spmi.h> struct qcom_coincell { struct device *dev; struct regmap *regmap; - u32 base_addr; }; #define QCOM_COINCELL_REG_RSET 0x44 @@ -35,7 +35,7 @@ static int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset, /* if disabling, just do that and skip other operations */ if (!enable) return regmap_write(chgr->regmap, - chgr->base_addr + QCOM_COINCELL_REG_ENABLE, 0); + QCOM_COINCELL_REG_ENABLE, 0); /* find index for current-limiting resistor */ for (i = 0; i < ARRAY_SIZE(qcom_rset_map); i++) @@ -58,7 +58,7 @@ static int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset, } rc = regmap_write(chgr->regmap, - chgr->base_addr + QCOM_COINCELL_REG_RSET, i); + QCOM_COINCELL_REG_RSET, i); if (rc) { /* * This is mainly to flag a bad base_addr (reg) from dts. @@ -71,19 +71,28 @@ static int qcom_coincell_chgr_config(struct qcom_coincell *chgr, int rset, } rc = regmap_write(chgr->regmap, - chgr->base_addr + QCOM_COINCELL_REG_VSET, j); + QCOM_COINCELL_REG_VSET, j); if (rc) return rc; /* set 'enable' register */ return regmap_write(chgr->regmap, - chgr->base_addr + QCOM_COINCELL_REG_ENABLE, + QCOM_COINCELL_REG_ENABLE, QCOM_COINCELL_ENABLE); } static int qcom_coincell_probe(struct platform_device *pdev) { - struct device_node *node = pdev->dev.of_node; + struct regmap_config qcom_coincell_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xff, + .fast_io = true, + }; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct spmi_subdevice *sub_sdev; + struct spmi_device *sparent; struct qcom_coincell chgr; u32 rset = 0; u32 vset = 0; @@ -92,16 +101,25 @@ static int qcom_coincell_probe(struct platform_device *pdev) chgr.dev = &pdev->dev; - chgr.regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!chgr.regmap) { - dev_err(chgr.dev, "Unable to get regmap\n"); - return -EINVAL; - } - - rc = of_property_read_u32(node, "reg", &chgr.base_addr); + rc = device_property_read_u32(dev, "reg", &qcom_coincell_regmap_config.reg_base); if (rc) return rc; + sparent = spmi_get_parent_spmi_device(dev); + if (!sparent) + return -ENODEV; + + sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent); + if (IS_ERR(sub_sdev)) + return PTR_ERR(sub_sdev); + + chgr.regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, + &qcom_coincell_regmap_config); + if (IS_ERR(chgr.regmap)) { + dev_err(chgr.dev, "Unable to get regmap\n"); + return PTR_ERR(chgr.regmap); + } + enable = !of_property_read_bool(node, "qcom,charger-disable"); if (enable) { @@ -142,3 +160,4 @@ module_platform_driver(qcom_coincell_driver); MODULE_DESCRIPTION("Qualcomm PMIC coincell charger driver"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("SPMI"); diff --git a/drivers/misc/tc9564-pci.c b/drivers/misc/tc9564-pci.c new file mode 100644 index 000000000000..e4d00dfecfca --- /dev/null +++ b/drivers/misc/tc9564-pci.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (C) 2026 by RISCstar Solutions Corporation. All rights reserved. + */ + +/* + * The Toshiba TC9564 implements a PCIe Gen 3 switch that connects an + * upstream x4 port to three downstream PCIe ports. Two of the downstream + * ports are external, and the third is internal, implementing a PCIe + * endpoint which implements two PCIe functions. Each PCIe function drives + * a Synopsys XGMAC Ethernet interface capable of 10 Gbps operation. + * + * The TC9564 implements other functionality, including an embedded MCU, + * a UART, a GPIO controller, a reset controller, a clock controller, and + * interrupt handling. These features are separate from (and in some + * cases used by) both Ethernet XGMACs. Each Ethernet MAC must be + * attached to a working PHY for it to be functional, and for this + * reason either of them (or both!) might not be usable/used. + * + * This PCI driver binds to the Toshiba TC9564 (physical) PCI function + * (VID 0x1179, DID 0x0220). + */ + +#include <linux/device.h> +#include <linux/irqdomain.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/pci.h> + +static void tc9564_function_remove(struct pci_dev *pdev) +{ + of_platform_depopulate(&pdev->dev); + pci_clear_master(pdev); + pci_disable_device(pdev); +} + +static int +tc9564_function_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct device *dev = &pdev->dev; + struct device_node *np; + int ret; + + /* Despite being a PCI device, we require devicetree */ + np = dev_of_node(dev); + if (!np) + return dev_err_probe(dev, -EINVAL, "no devicetree node\n"); + + ret = pci_enable_device(pdev); + if (ret) + return dev_err_probe(dev, ret, "failed to enable device\n"); + + pci_set_master(pdev); + + /* Scan for pci-ep-bus (simple-bus) nodes and probe their sub-devices */ + ret = of_platform_default_populate(np, NULL, dev); + if (ret) + goto err_depopulate; + + return 0; + +err_depopulate: + tc9564_function_remove(pdev); + + return dev_err_probe(dev, ret, "failed to populate platform bus\n"); +} + +static const struct pci_device_id tc9564_function_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_TOSHIBA, 0x0220), }, + { }, +}; +MODULE_DEVICE_TABLE(pci, tc9564_function_id_table); + +static struct pci_driver tc9564_function_driver = { + .name = KBUILD_MODNAME, + .id_table = tc9564_function_id_table, + .probe = tc9564_function_probe, + .remove = tc9564_function_remove, +}; +module_pci_driver(tc9564_function_driver); + +MODULE_DESCRIPTION("Toshiba TC9564 PCIe Embedded Function Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/tifm_7xx1.c b/drivers/misc/tifm_7xx1.c index 4677d5a30941..50e7dc502f16 100644 --- a/drivers/misc/tifm_7xx1.c +++ b/drivers/misc/tifm_7xx1.c @@ -207,7 +207,7 @@ static void tifm_7xx1_switch_media(struct work_struct *work) spin_unlock_irqrestore(&fm->lock, flags); } -static int __maybe_unused tifm_7xx1_suspend(struct device *dev_d) +static int tifm_7xx1_suspend(struct device *dev_d) { struct pci_dev *dev = to_pci_dev(dev_d); struct tifm_adapter *fm = pci_get_drvdata(dev); @@ -224,7 +224,7 @@ static int __maybe_unused tifm_7xx1_suspend(struct device *dev_d) return 0; } -static int __maybe_unused tifm_7xx1_resume(struct device *dev_d) +static int tifm_7xx1_resume(struct device *dev_d) { struct pci_dev *dev = to_pci_dev(dev_d); struct tifm_adapter *fm = pci_get_drvdata(dev); @@ -406,14 +406,14 @@ static const struct pci_device_id tifm_7xx1_pci_tbl[] = { { } }; -static SIMPLE_DEV_PM_OPS(tifm_7xx1_pm_ops, tifm_7xx1_suspend, tifm_7xx1_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(tifm_7xx1_pm_ops, tifm_7xx1_suspend, tifm_7xx1_resume); static struct pci_driver tifm_7xx1_driver = { .name = DRIVER_NAME, .id_table = tifm_7xx1_pci_tbl, .probe = tifm_7xx1_probe, .remove = tifm_7xx1_remove, - .driver.pm = &tifm_7xx1_pm_ops, + .driver.pm = pm_sleep_ptr(&tifm_7xx1_pm_ops), }; module_pci_driver(tifm_7xx1_driver); diff --git a/drivers/misc/tifm_core.c b/drivers/misc/tifm_core.c index aac8f0933e43..03d1324bc253 100644 --- a/drivers/misc/tifm_core.c +++ b/drivers/misc/tifm_core.c @@ -97,7 +97,6 @@ static void tifm_device_remove(struct device *dev) sock->card_event = tifm_dummy_event; sock->data_event = tifm_dummy_event; drv->remove(sock); - sock->dev.driver = NULL; } put_device(dev); diff --git a/drivers/misc/tsl2550.c b/drivers/misc/tsl2550.c index c871ea860bb5..df6931ef267e 100644 --- a/drivers/misc/tsl2550.c +++ b/drivers/misc/tsl2550.c @@ -398,8 +398,6 @@ static void tsl2550_remove(struct i2c_client *client) kfree(i2c_get_clientdata(client)); } -#ifdef CONFIG_PM_SLEEP - static int tsl2550_suspend(struct device *dev) { return tsl2550_set_power_state(to_i2c_client(dev), 0); @@ -410,14 +408,7 @@ static int tsl2550_resume(struct device *dev) return tsl2550_set_power_state(to_i2c_client(dev), 1); } -static SIMPLE_DEV_PM_OPS(tsl2550_pm_ops, tsl2550_suspend, tsl2550_resume); -#define TSL2550_PM_OPS (&tsl2550_pm_ops) - -#else - -#define TSL2550_PM_OPS NULL - -#endif /* CONFIG_PM_SLEEP */ +static DEFINE_SIMPLE_DEV_PM_OPS(tsl2550_pm_ops, tsl2550_suspend, tsl2550_resume); static const struct i2c_device_id tsl2550_id[] = { { .name = "tsl2550" }, @@ -435,7 +426,7 @@ static struct i2c_driver tsl2550_driver = { .driver = { .name = TSL2550_DRV_NAME, .of_match_table = tsl2550_of_match, - .pm = TSL2550_PM_OPS, + .pm = pm_sleep_ptr(&tsl2550_pm_ops), }, .probe = tsl2550_probe, .remove = tsl2550_remove, diff --git a/drivers/misc/vmw_vmci/vmci_datagram.c b/drivers/misc/vmw_vmci/vmci_datagram.c index 6193a22c3ab5..73b43b097f92 100644 --- a/drivers/misc/vmw_vmci/vmci_datagram.c +++ b/drivers/misc/vmw_vmci/vmci_datagram.c @@ -434,7 +434,7 @@ EXPORT_SYMBOL_GPL(vmci_datagram_create_handle_priv); * @out_handle: vmci_handle that is populated as a result of this function. * * Creates a host context datagram endpoint and returns a handle to - * it. Same as vmci_datagram_create_handle_priv without the priviledge + * it. Same as vmci_datagram_create_handle_priv without the privilege * flags argument. */ int vmci_datagram_create_handle(u32 resource_id, diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.c b/drivers/misc/vmw_vmci/vmci_queue_pair.c index 46bba9bef553..e1a11820be01 100644 --- a/drivers/misc/vmw_vmci/vmci_queue_pair.c +++ b/drivers/misc/vmw_vmci/vmci_queue_pair.c @@ -2668,7 +2668,7 @@ static ssize_t qp_dequeue_locked(struct vmci_queue *produce_q, * @consume_qsize: Desired size of the consumer queue. * @peer: ContextID of the peer. * @flags: VMCI flags. - * @priv_flags: VMCI priviledge flags. + * @priv_flags: VMCI privilege flags. * * This is the client interface for allocating the memory for a * vmci_qp structure and then attaching to the underlying diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index c36c2a4c2a0b..d2f5bb236663 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -328,6 +328,17 @@ config NVMEM_MTK_EFUSE This driver can also be built as a module. If so, the module will be called efuse-mtk. +config NVMEM_MT6323_EFUSE + tristate "MediaTek MT6323 PMIC EFUSE support" + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on MFD_MT6397 + help + This is a driver to access hardware related data like sensor + calibration, etc. + + This driver can also be built as a module. If so, the module + will be called efuse-mt6323. + config NVMEM_MXS_OCOTP tristate "Freescale MXS On-Chip OTP Memory Support" depends on ARCH_MXS || COMPILE_TEST @@ -468,6 +479,7 @@ config NVMEM_SNVS_LPGPR config NVMEM_SPMI_SDAM tristate "SPMI SDAM Support" depends on SPMI + select REGMAP_SPMI help This driver supports the Shared Direct Access Memory Module on Qualcomm Technologies, Inc. PMICs. It provides the clients @@ -541,8 +553,9 @@ config NVMEM_U_BOOT_ENV config NVMEM_UNIPHIER_EFUSE tristate "UniPhier SoCs eFuse support" - depends on ARCH_UNIPHIER || COMPILE_TEST + depends on ARCH_UNIPHIER || ARCH_K3 || COMPILE_TEST depends on HAS_IOMEM + default y if ARCH_K3 help This is a simple driver to dump specified values of UniPhier SoC from eFuse. diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 2bbfb9ff1885..5e70bad9f0f9 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -53,6 +53,8 @@ obj-$(CONFIG_NVMEM_MESON_MX_EFUSE) += nvmem_meson_mx_efuse.o nvmem_meson_mx_efuse-y := meson-mx-efuse.o obj-$(CONFIG_NVMEM_MICROCHIP_OTPC) += nvmem-microchip-otpc.o nvmem-microchip-otpc-y := microchip-otpc.o +obj-$(CONFIG_NVMEM_MT6323_EFUSE) += nvmem_mt6323-efuse.o +nvmem_mt6323-efuse-y := mt6323-efuse.o obj-$(CONFIG_NVMEM_MTK_EFUSE) += nvmem_mtk-efuse.o nvmem_mtk-efuse-y := mtk-efuse.o obj-$(CONFIG_NVMEM_MXS_OCOTP) += nvmem-mxs-ocotp.o diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 0556d140170a..7d4cce8e2742 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -57,7 +57,12 @@ static BLOCKING_NOTIFIER_HEAD(nvmem_notifier); static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes) { - struct nvmem_operations *ops = nvmem->ops; + struct nvmem_operations *ops; + + guard(srcu)(&nvmem->srcu); + ops = srcu_dereference(nvmem->ops, &nvmem->srcu); + if (!ops) + return -ENODEV; if (!ops->reg_read) return -EOPNOTSUPP; @@ -68,9 +73,14 @@ static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes) { - struct nvmem_operations *ops = nvmem->ops; + struct nvmem_operations *ops; int ret, wr_ok; + guard(srcu)(&nvmem->srcu); + ops = srcu_dereference(nvmem->ops, &nvmem->srcu); + if (!ops) + return -ENODEV; + if (!ops->reg_write) return -EOPNOTSUPP; @@ -289,7 +299,7 @@ static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj, static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem) { - struct nvmem_operations *ops = nvmem->ops; + struct nvmem_operations *ops = rcu_dereference_raw(nvmem->ops); umode_t mode = 0400; @@ -333,7 +343,7 @@ static umode_t nvmem_attr_is_visible(struct kobject *kobj, { struct device *dev = kobj_to_dev(kobj); struct nvmem_device *nvmem = to_nvmem_device(dev); - struct nvmem_operations *ops = nvmem->ops; + struct nvmem_operations *ops = rcu_dereference_raw(nvmem->ops); /* * If the device has no .reg_write operation, do not allow @@ -485,7 +495,7 @@ static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem) /* Allocate an array of attributes with a sentinel */ ncells = list_count_nodes(&nvmem->cells); pattrs = devm_kcalloc(&nvmem->dev, ncells + 1, - sizeof(struct bin_attribute *), GFP_KERNEL); + sizeof(*pattrs), GFP_KERNEL); if (!pattrs) return -ENOMEM; @@ -535,24 +545,6 @@ static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem) #endif /* CONFIG_NVMEM_SYSFS */ -static void nvmem_release(struct device *dev) -{ - struct nvmem_device *nvmem = to_nvmem_device(dev); - - ida_free(&nvmem_ida, nvmem->id); - gpiod_put(nvmem->wp_gpio); - kfree(nvmem->ops); - kfree(nvmem); -} - -static const struct device_type nvmem_provider_type = { - .release = nvmem_release, -}; - -static const struct bus_type nvmem_bus_type = { - .name = "nvmem", -}; - static void nvmem_cell_entry_drop(struct nvmem_cell_entry *cell) { blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_REMOVE, cell); @@ -571,6 +563,25 @@ static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem) nvmem_cell_entry_drop(cell); } +static void nvmem_release(struct device *dev) +{ + struct nvmem_device *nvmem = to_nvmem_device(dev); + + gpiod_put(nvmem->wp_gpio); + nvmem_device_remove_all_cells(nvmem); + ida_free(&nvmem_ida, nvmem->id); + cleanup_srcu_struct(&nvmem->srcu); + kfree(nvmem); +} + +static const struct device_type nvmem_provider_type = { + .release = nvmem_release, +}; + +static const struct bus_type nvmem_bus_type = { + .name = "nvmem", +}; + static void nvmem_cell_entry_add(struct nvmem_cell_entry *cell) { scoped_guard(mutex, &nvmem_mutex) @@ -918,7 +929,21 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) nvmem->dev.type = &nvmem_provider_type; nvmem->dev.bus = &nvmem_bus_type; nvmem->dev.parent = config->dev; - nvmem->ops = ops; + INIT_LIST_HEAD(&nvmem->cells); + + /* + * Must happen before we assign the release() callback in + * device_initialize(). + */ + rval = init_srcu_struct(&nvmem->srcu); + if (rval) { + ida_free(&nvmem_ida, nvmem->id); + kfree(ops); + kfree(nvmem); + return ERR_PTR(rval); + } + + rcu_assign_pointer(nvmem->ops, ops); device_initialize(&nvmem->dev); @@ -931,8 +956,6 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) goto err_put_device; } - kref_init(&nvmem->refcnt); - INIT_LIST_HEAD(&nvmem->cells); nvmem->fixup_dt_cell_info = config->fixup_dt_cell_info; ops->reg_read = config->reg_read; @@ -993,20 +1016,20 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) if (config->cells) { rval = nvmem_add_cells(nvmem, config->cells, config->ncells); if (rval) - goto err_remove_cells; + goto err_remove_compat; } if (config->add_legacy_fixed_of_cells) { rval = nvmem_add_cells_from_legacy_of(nvmem); if (rval) - goto err_remove_cells; + goto err_remove_compat; } dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name); rval = device_add(&nvmem->dev); if (rval) - goto err_remove_cells; + goto err_remove_compat; rval = nvmem_populate_layout(nvmem); if (rval) @@ -1032,40 +1055,40 @@ err_destroy_layout: #endif err_remove_dev: device_del(&nvmem->dev); -err_remove_cells: - nvmem_device_remove_all_cells(nvmem); +err_remove_compat: nvmem_sysfs_remove_compat(nvmem); err_put_device: + ops = rcu_replace_pointer(nvmem->ops, NULL, true); + synchronize_srcu(&nvmem->srcu); put_device(&nvmem->dev); + kfree(ops); return ERR_PTR(rval); } EXPORT_SYMBOL_GPL(nvmem_register); -static void nvmem_device_release(struct kref *kref) +/** + * nvmem_unregister() - Unregister previously registered nvmem device + * + * @nvmem: Pointer to previously registered nvmem device. + */ +void nvmem_unregister(struct nvmem_device *nvmem) { - struct nvmem_device *nvmem; + struct nvmem_operations *ops; - nvmem = container_of(kref, struct nvmem_device, refcnt); + if (!nvmem) + return; blocking_notifier_call_chain(&nvmem_notifier, NVMEM_REMOVE, nvmem); - nvmem_sysfs_remove_compat(nvmem); + ops = rcu_replace_pointer(nvmem->ops, NULL, true); + synchronize_srcu(&nvmem->srcu); - nvmem_device_remove_all_cells(nvmem); + nvmem_sysfs_remove_compat(nvmem); nvmem_destroy_layout(nvmem); - device_unregister(&nvmem->dev); -} + kfree(ops); -/** - * nvmem_unregister() - Unregister previously registered nvmem device - * - * @nvmem: Pointer to previously registered nvmem device. - */ -void nvmem_unregister(struct nvmem_device *nvmem) -{ - if (nvmem) - kref_put(&nvmem->refcnt, nvmem_device_release); + device_unregister(&nvmem->dev); } EXPORT_SYMBOL_GPL(nvmem_unregister); @@ -1126,8 +1149,6 @@ static struct nvmem_device *nvmem_device_match(void *data, return ERR_PTR(-EINVAL); } - kref_get(&nvmem->refcnt); - return nvmem; } @@ -1243,9 +1264,8 @@ EXPORT_SYMBOL_GPL(devm_nvmem_device_put); */ void nvmem_device_put(struct nvmem_device *nvmem) { - put_device(&nvmem->dev); module_put(nvmem->owner); - kref_put(&nvmem->refcnt, nvmem_device_release); + put_device(&nvmem->dev); } EXPORT_SYMBOL_GPL(nvmem_device_put); @@ -1598,12 +1618,14 @@ static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void *p = *b++ >> bit_offset; /* setup rest of the bytes if any */ - for (i = 1; i < cell->bytes; i++) { + for (i = 1; i < (cell->bytes - bytes_offset); i++) { /* Get bits from next byte and shift them towards msb */ *p++ |= *b << (BITS_PER_BYTE - bit_offset); *p = *b++ >> bit_offset; } + /* point to end of the buffer unused bits will be cleared */ + p = buf + cell->bytes - 1; } else if (p != b) { memmove(p, b, cell->bytes - bytes_offset); p += cell->bytes - 1; diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h index 4e610deeaa7b..bc7a99f5aefb 100644 --- a/drivers/nvmem/internals.h +++ b/drivers/nvmem/internals.h @@ -6,6 +6,7 @@ #include <linux/device.h> #include <linux/nvmem-consumer.h> #include <linux/nvmem-provider.h> +#include <linux/srcu.h> /* Hold pointers to callbacks owned by the nvmem provider module. */ struct nvmem_operations { @@ -16,10 +17,10 @@ struct nvmem_operations { struct nvmem_device { struct module *owner; struct device dev; + struct srcu_struct srcu; int stride; int word_size; int id; - struct kref refcnt; size_t size; bool read_only; bool root_only; @@ -34,7 +35,7 @@ struct nvmem_device { unsigned int nkeepout; struct gpio_desc *wp_gpio; struct nvmem_layout *layout; - struct nvmem_operations *ops; + struct nvmem_operations __rcu *ops; void *priv; bool sysfs_cells_populated; }; diff --git a/drivers/nvmem/layouts.c b/drivers/nvmem/layouts.c index 07a34be9669c..409909e3244f 100644 --- a/drivers/nvmem/layouts.c +++ b/drivers/nvmem/layouts.c @@ -156,7 +156,18 @@ static int nvmem_layout_bus_populate(struct nvmem_device *nvmem, struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem) { - return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout"); + struct device_node *np; + + /* Search for nvmem-layout child */ + np = of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout"); + if (np) + return np; + + /* The nvmem of_node is itself a fixed-layout node */ + if (of_device_is_compatible(nvmem->dev.of_node, "fixed-layout")) + return of_node_get(nvmem->dev.of_node); + + return NULL; } EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container); diff --git a/drivers/nvmem/layouts/onie-tlv.c b/drivers/nvmem/layouts/onie-tlv.c index 8b0f3c1b8a0e..4da3a704388e 100644 --- a/drivers/nvmem/layouts/onie-tlv.c +++ b/drivers/nvmem/layouts/onie-tlv.c @@ -128,6 +128,7 @@ static int onie_tlv_add_cells(struct device *dev, struct nvmem_device *nvmem, ret = nvmem_add_one_cell(nvmem, &cell); if (ret) { + of_node_put(cell.np); of_node_put(layout); return ret; } diff --git a/drivers/nvmem/layouts/sl28vpd.c b/drivers/nvmem/layouts/sl28vpd.c index e93b020b0836..79de1e6947d0 100644 --- a/drivers/nvmem/layouts/sl28vpd.c +++ b/drivers/nvmem/layouts/sl28vpd.c @@ -126,6 +126,7 @@ static int sl28vpd_add_cells(struct nvmem_layout *layout) ret = nvmem_add_one_cell(nvmem, &info); if (ret) { + of_node_put(info.np); of_node_put(layout_np); return ret; } diff --git a/drivers/nvmem/mt6323-efuse.c b/drivers/nvmem/mt6323-efuse.c new file mode 100644 index 000000000000..de5e30215c22 --- /dev/null +++ b/drivers/nvmem/mt6323-efuse.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2026 Roman Vivchar <rva333@protonmail.com> + */ + +#include <linux/err.h> +#include <linux/mfd/mt6397/core.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/types.h> + +#include <linux/mfd/mt6323/registers.h> + +#define MT6323_EFUSE_DOUT_BASE MT6323_EFUSE_DOUT_0_15 +#define MT6323_EFUSE_SIZE 24 + +static int mt6323_efuse_read(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct regmap *map = context; + u16 *buf = val; + u32 tmp; + int ret; + + /* + * A manual loop using regmap_read is required because PWRAP is not + * a continuous MMIO space, but rather a FSM that doesn't implement the + * necessary read callback for the regmap_read_raw and regmap_read_bulk + * functions. + */ + for (size_t i = 0; i < bytes; i += sizeof(*buf)) { + ret = regmap_read(map, MT6323_EFUSE_DOUT_BASE + offset + i, &tmp); + if (ret) + return ret; + + *buf++ = tmp; + } + + return 0; +} + +static int mt6323_efuse_probe(struct platform_device *pdev) +{ + struct mt6397_chip *mt6323 = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct nvmem_config config = { + .name = "mt6323-efuse", + .stride = 2, + .word_size = 2, + .size = MT6323_EFUSE_SIZE, + .reg_read = mt6323_efuse_read, + }; + struct nvmem_device *nvmem; + + if (!mt6323 || !mt6323->regmap) + return dev_err_probe(dev, -ENODEV, "failed to get parent\n"); + + config.dev = dev; + config.priv = mt6323->regmap; + + nvmem = devm_nvmem_register(dev, &config); + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id mt6323_efuse_of_match[] = { + { .compatible = "mediatek,mt6323-efuse" }, + { } +}; +MODULE_DEVICE_TABLE(of, mt6323_efuse_of_match); + +static struct platform_driver mt6323_efuse_driver = { + .probe = mt6323_efuse_probe, + .driver = { + .name = "mt6323-efuse", + .of_match_table = mt6323_efuse_of_match, + }, +}; +module_platform_driver(mt6323_efuse_driver); + +MODULE_DESCRIPTION("MediaTek MT6323 PMIC EFUSE driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/qcom-spmi-sdam.c b/drivers/nvmem/qcom-spmi-sdam.c index 4f1cca6eab71..2aec95dff05d 100644 --- a/drivers/nvmem/qcom-spmi-sdam.c +++ b/drivers/nvmem/qcom-spmi-sdam.c @@ -9,6 +9,7 @@ #include <linux/nvmem-provider.h> #include <linux/platform_device.h> #include <linux/regmap.h> +#include <linux/spmi.h> #define SDAM_MEM_START 0x40 #define REGISTER_MAP_ID 0x40 @@ -20,7 +21,6 @@ struct sdam_chip { struct regmap *regmap; struct nvmem_config sdam_config; - unsigned int base; unsigned int size; }; @@ -73,7 +73,7 @@ static int sdam_read(void *priv, unsigned int offset, void *val, return -EINVAL; } - rc = regmap_bulk_read(sdam->regmap, sdam->base + offset, val, bytes); + rc = regmap_bulk_read(sdam->regmap, offset, val, bytes); if (rc < 0) dev_err(dev, "Failed to read SDAM offset %#x len=%zd, rc=%d\n", offset, bytes, rc); @@ -100,7 +100,7 @@ static int sdam_write(void *priv, unsigned int offset, void *val, return -EINVAL; } - rc = regmap_bulk_write(sdam->regmap, sdam->base + offset, val, bytes); + rc = regmap_bulk_write(sdam->regmap, offset, val, bytes); if (rc < 0) dev_err(dev, "Failed to write SDAM offset %#x len=%zd, rc=%d\n", offset, bytes, rc); @@ -110,8 +110,17 @@ static int sdam_write(void *priv, unsigned int offset, void *val, static int sdam_probe(struct platform_device *pdev) { + struct regmap_config sdam_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xff, + .fast_io = true, + }; struct sdam_chip *sdam; struct nvmem_device *nvmem; + struct spmi_device *sparent; + struct spmi_subdevice *sub_sdev; + struct device *dev = &pdev->dev; unsigned int val; int rc; @@ -119,19 +128,26 @@ static int sdam_probe(struct platform_device *pdev) if (!sdam) return -ENOMEM; - sdam->regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!sdam->regmap) { - dev_err(&pdev->dev, "Failed to get regmap handle\n"); - return -ENXIO; - } + sparent = spmi_get_parent_spmi_device(dev); + if (!sparent) + return -ENODEV; - rc = of_property_read_u32(pdev->dev.of_node, "reg", &sdam->base); + sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent); + if (IS_ERR(sub_sdev)) + return PTR_ERR(sub_sdev); + + rc = device_property_read_u32(dev, "reg", &sdam_regmap_config.reg_base); if (rc < 0) { - dev_err(&pdev->dev, "Failed to get SDAM base, rc=%d\n", rc); + dev_err(dev, "Failed to get SDAM base, rc=%d\n", rc); return -EINVAL; } - rc = regmap_read(sdam->regmap, sdam->base + SDAM_SIZE, &val); + sdam->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &sdam_regmap_config); + if (IS_ERR(sdam->regmap)) + return dev_err_probe(dev, PTR_ERR(sdam->regmap), + "Failed to get regmap handle\n"); + + rc = regmap_read(sdam->regmap, SDAM_SIZE, &val); if (rc < 0) { dev_err(&pdev->dev, "Failed to read SDAM_SIZE rc=%d\n", rc); return -EINVAL; @@ -159,7 +175,7 @@ static int sdam_probe(struct platform_device *pdev) } dev_dbg(&pdev->dev, "SDAM base=%#x size=%u registered successfully\n", - sdam->base, sdam->size); + sdam_regmap_config.reg_base, sdam->size); return 0; } @@ -181,3 +197,4 @@ module_platform_driver(sdam_driver); MODULE_DESCRIPTION("QCOM SPMI SDAM driver"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("SPMI"); diff --git a/drivers/nvmem/rockchip-otp.c b/drivers/nvmem/rockchip-otp.c index 2c0feb036f3f..f8a8c7cece98 100644 --- a/drivers/nvmem/rockchip-otp.c +++ b/drivers/nvmem/rockchip-otp.c @@ -12,6 +12,7 @@ #include <linux/io.h> #include <linux/iopoll.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/nvmem-provider.h> #include <linux/reset.h> #include <linux/slab.h> @@ -80,6 +81,8 @@ struct rockchip_otp { void __iomem *base; struct reset_control *rst; const struct rockchip_data *data; + /* Serializes access to the OTP controller state machine */ + struct mutex mutex; struct clk_bulk_data clks[]; }; @@ -272,10 +275,12 @@ static int rockchip_otp_read(void *context, unsigned int offset, if (!otp->data || !otp->data->reg_read) return -EINVAL; + mutex_lock(&otp->mutex); + ret = clk_bulk_prepare_enable(otp->data->num_clks, otp->clks); if (ret < 0) { dev_err(otp->dev, "failed to prepare/enable clks\n"); - return ret; + goto unlock; } offset += otp->data->read_offset; @@ -308,6 +313,9 @@ static int rockchip_otp_read(void *context, unsigned int offset, err: clk_bulk_disable_unprepare(otp->data->num_clks, otp->clks); +unlock: + mutex_unlock(&otp->mutex); + return ret; } @@ -431,6 +439,11 @@ static int rockchip_otp_probe(struct platform_device *pdev) otp->data = data; otp->dev = dev; + + ret = devm_mutex_init(dev, &otp->mutex); + if (ret) + return ret; + otp->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(otp->base)) return dev_err_probe(dev, PTR_ERR(otp->base), diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index c51c59115a0c..7f1f47ea2ee4 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -6562,6 +6562,7 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_XILINX, 0x5020, of_pci_make_dev_node); DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_XILINX, 0x5021, of_pci_make_dev_node); DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_REDHAT, 0x0005, of_pci_make_dev_node); DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_EFAR, 0x9660, of_pci_make_dev_node); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_TOSHIBA, 0x0220, of_pci_make_dev_node); /* * Devices known to require a longer delay before first config space access diff --git a/drivers/peci/controller/peci-aspeed.c b/drivers/peci/controller/peci-aspeed.c index a0c99ecf7f38..1c747ce8840b 100644 --- a/drivers/peci/controller/peci-aspeed.c +++ b/drivers/peci/controller/peci-aspeed.c @@ -413,7 +413,7 @@ static struct clk *devm_aspeed_peci_register_clk_div(struct device *dev, struct struct aspeed_peci *priv) { struct clk_aspeed_peci *peci_clk; - struct clk_init_data init; + struct clk_init_data init = {}; const char *parent_name; char name[32]; int ret; @@ -529,14 +529,14 @@ static int aspeed_peci_probe(struct platform_device *pdev) if (priv->irq < 0) return priv->irq; + init_completion(&priv->xfer_complete); + spin_lock_init(&priv->lock); + ret = devm_request_irq(&pdev->dev, priv->irq, aspeed_peci_irq_handler, 0, "peci-aspeed", priv); if (ret) return ret; - init_completion(&priv->xfer_complete); - spin_lock_init(&priv->lock); - priv->rst = devm_reset_control_get(&pdev->dev, NULL); if (IS_ERR(priv->rst)) return dev_err_probe(priv->dev, PTR_ERR(priv->rst), diff --git a/drivers/peci/controller/peci-npcm.c b/drivers/peci/controller/peci-npcm.c index 931868991241..3cc4e959aff3 100644 --- a/drivers/peci/controller/peci-npcm.c +++ b/drivers/peci/controller/peci-npcm.c @@ -253,14 +253,14 @@ static int npcm_peci_probe(struct platform_device *pdev) if (priv->irq < 0) return priv->irq; + init_completion(&priv->xfer_complete); + spin_lock_init(&priv->lock); + ret = devm_request_irq(&pdev->dev, priv->irq, npcm_peci_irq_handler, 0, "peci-npcm-irq", priv); if (ret) return ret; - init_completion(&priv->xfer_complete); - spin_lock_init(&priv->lock); - ret = npcm_peci_init_ctrl(priv); if (ret) return ret; diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig index d910a5d1a1ac..c5e260a4c7f8 100644 --- a/drivers/phy/qualcomm/Kconfig +++ b/drivers/phy/qualcomm/Kconfig @@ -138,7 +138,9 @@ config PHY_QCOM_QUSB2 config PHY_QCOM_EUSB2_REPEATER tristate "Qualcomm PMIC eUSB2 Repeater Driver" depends on OF && (ARCH_QCOM || COMPILE_TEST) + depends on SPMI select GENERIC_PHY + select REGMAP_SPMI help Enable support for the USB high-speed eUSB2 repeater on Qualcomm PMICs. The repeater is paired with a Synopsys or M31 eUSB2 Phy diff --git a/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c b/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c index efeec4709a15..89bc1c89fc85 100644 --- a/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c +++ b/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c @@ -9,6 +9,7 @@ #include <linux/regmap.h> #include <linux/of.h> #include <linux/phy/phy.h> +#include <linux/spmi.h> /* eUSB2 status registers */ #define EUSB2_RPTR_STATUS 0x08 @@ -66,7 +67,6 @@ struct eusb2_repeater { struct phy *phy; struct regulator_bulk_data *vregs; const struct eusb2_repeater_cfg *cfg; - u32 base; enum phy_mode mode; }; @@ -143,7 +143,6 @@ static int eusb2_repeater_init(struct phy *phy) struct eusb2_repeater *rptr = phy_get_drvdata(phy); struct device_node *np = rptr->dev->of_node; struct regmap *regmap = rptr->regmap; - u32 base = rptr->base; u32 poll_val; s32 dt_val; int ret; @@ -154,37 +153,37 @@ static int eusb2_repeater_init(struct phy *phy) if (ret) return ret; - regmap_write(regmap, base + EUSB2_EN_CTL1, EUSB2_RPTR_EN); + regmap_write(regmap, EUSB2_EN_CTL1, EUSB2_RPTR_EN); /* Write registers from init table */ for (int i = 0; i < rptr->cfg->init_tbl_num; i++) - regmap_write(regmap, base + rptr->cfg->init_tbl[i].reg, + regmap_write(regmap, rptr->cfg->init_tbl[i].reg, rptr->cfg->init_tbl[i].value); /* Override registers from devicetree values */ if (!of_property_read_u8(np, "qcom,tune-usb2-preem", &val)) - regmap_write(regmap, base + EUSB2_TUNE_USB2_PREEM, val); + regmap_write(regmap, EUSB2_TUNE_USB2_PREEM, val); if (!of_property_read_u8(np, "qcom,tune-usb2-disc-thres", &val)) - regmap_write(regmap, base + EUSB2_TUNE_HSDISC, val); + regmap_write(regmap, EUSB2_TUNE_HSDISC, val); if (!of_property_read_u8(np, "qcom,tune-usb2-amplitude", &val)) - regmap_write(regmap, base + EUSB2_TUNE_IUSB2, val); + regmap_write(regmap, EUSB2_TUNE_IUSB2, val); if (!of_property_read_u8(np, "qcom,tune-res-fsdif", &val)) - regmap_write(regmap, base + EUSB2_TUNE_RES_FSDIF, val); + regmap_write(regmap, EUSB2_TUNE_RES_FSDIF, val); if (!of_property_read_s32(np, "qcom,squelch-detector-bp", &dt_val)) { for (i = 0; i < ARRAY_SIZE(squelch_detector); i++) { if (squelch_detector[i] == dt_val) { - regmap_write(regmap, base + EUSB2_TUNE_SQUELCH_U, i); + regmap_write(regmap, EUSB2_TUNE_SQUELCH_U, i); break; } } } /* Wait for status OK */ - ret = regmap_read_poll_timeout(regmap, base + EUSB2_RPTR_STATUS, poll_val, + ret = regmap_read_poll_timeout(regmap, EUSB2_RPTR_STATUS, poll_val, poll_val & RPTR_OK, 10, 5); if (ret) dev_err(rptr->dev, "initialization timed-out\n"); @@ -197,7 +196,6 @@ static int eusb2_repeater_set_mode(struct phy *phy, { struct eusb2_repeater *rptr = phy_get_drvdata(phy); struct regmap *regmap = rptr->regmap; - u32 base = rptr->base; switch (mode) { case PHY_MODE_USB_HOST: @@ -206,8 +204,8 @@ static int eusb2_repeater_set_mode(struct phy *phy, * per eUSB 1.2 Spec. Below implement software workaround until * PHY and controller is fixing seen observation. */ - regmap_write(regmap, base + EUSB2_FORCE_EN_5, F_CLK_19P2M_EN); - regmap_write(regmap, base + EUSB2_FORCE_VAL_5, V_CLK_19P2M_EN); + regmap_write(regmap, EUSB2_FORCE_EN_5, F_CLK_19P2M_EN); + regmap_write(regmap, EUSB2_FORCE_VAL_5, V_CLK_19P2M_EN); break; case PHY_MODE_USB_DEVICE: /* @@ -216,8 +214,8 @@ static int eusb2_repeater_set_mode(struct phy *phy, * repeater doesn't clear previous value due to shared * regulators (say host <-> device mode switch). */ - regmap_write(regmap, base + EUSB2_FORCE_EN_5, 0); - regmap_write(regmap, base + EUSB2_FORCE_VAL_5, 0); + regmap_write(regmap, EUSB2_FORCE_EN_5, 0); + regmap_write(regmap, EUSB2_FORCE_VAL_5, 0); break; default: return -EINVAL; @@ -242,11 +240,18 @@ static const struct phy_ops eusb2_repeater_ops = { static int eusb2_repeater_probe(struct platform_device *pdev) { + struct regmap_config eusb2_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xff, + .fast_io = true, + }; + struct spmi_device *sparent; struct eusb2_repeater *rptr; + struct spmi_subdevice *sub_sdev; struct device *dev = &pdev->dev; struct phy_provider *phy_provider; struct device_node *np = dev->of_node; - u32 res; int ret; rptr = devm_kzalloc(dev, sizeof(*rptr), GFP_KERNEL); @@ -260,15 +265,21 @@ static int eusb2_repeater_probe(struct platform_device *pdev) if (!rptr->cfg) return -EINVAL; - rptr->regmap = dev_get_regmap(dev->parent, NULL); - if (!rptr->regmap) + sparent = spmi_get_parent_spmi_device(dev); + if (!sparent) return -ENODEV; - ret = of_property_read_u32(np, "reg", &res); + sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent); + if (IS_ERR(sub_sdev)) + return PTR_ERR(sub_sdev); + + ret = device_property_read_u32(dev, "reg", &eusb2_regmap_config.reg_base); if (ret < 0) return ret; - rptr->base = res; + rptr->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &eusb2_regmap_config); + if (IS_ERR(rptr->regmap)) + return PTR_ERR(rptr->regmap); ret = eusb2_repeater_init_vregs(rptr); if (ret < 0) { @@ -335,3 +346,4 @@ module_platform_driver(eusb2_repeater_driver); MODULE_DESCRIPTION("Qualcomm PMIC eUSB2 Repeater driver"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("SPMI"); diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c index fa241ca8feb0..3f34db896ece 100644 --- a/drivers/platform/goldfish/goldfish_pipe.c +++ b/drivers/platform/goldfish/goldfish_pipe.c @@ -708,12 +708,11 @@ static int goldfish_pipe_open(struct inode *inode, struct file *file) init_waitqueue_head(&pipe->wake_queue); /* - * Command buffer needs to be allocated on its own page to make sure - * it is physically contiguous in host's address space. + * The command buffer is passed to the host as a physical address, so + * it must be physically contiguous, which kmalloc() guarantees. */ BUILD_BUG_ON(sizeof(struct goldfish_pipe_command) > PAGE_SIZE); - pipe->command_buffer = - (struct goldfish_pipe_command *)__get_free_page(GFP_KERNEL); + pipe->command_buffer = kmalloc_obj(*pipe->command_buffer); if (!pipe->command_buffer) { status = -ENOMEM; goto err_pipe; @@ -749,7 +748,7 @@ err_cmd: dev->pipes[id] = NULL; err_id_locked: spin_unlock_irqrestore(&dev->lock, flags); - free_page((unsigned long)pipe->command_buffer); + kfree(pipe->command_buffer); err_pipe: kfree(pipe); return status; @@ -770,7 +769,7 @@ static int goldfish_pipe_release(struct inode *inode, struct file *filp) spin_unlock_irqrestore(&dev->lock, flags); filp->private_data = NULL; - free_page((unsigned long)pipe->command_buffer); + kfree(pipe->command_buffer); kfree(pipe); return 0; } @@ -833,13 +832,12 @@ static int goldfish_pipe_device_init(struct platform_device *pdev, /* * We're going to pass two buffers, open_command_params and - * signalled_pipe_buffers, to the host. This means each of those buffers - * needs to be contained in a single physical page. The easiest choice - * is to just allocate a page and place the buffers in it. + * signalled_pipe_buffers, to the host as physical addresses. This means + * each of those buffers needs to be physically contiguous, which + * kmalloc() guarantees. */ BUILD_BUG_ON(sizeof(struct goldfish_pipe_dev_buffers) > PAGE_SIZE); - dev->buffers = (struct goldfish_pipe_dev_buffers *) - __get_free_page(GFP_KERNEL); + dev->buffers = kmalloc_obj(*dev->buffers); if (!dev->buffers) { kfree(dev->pipes); misc_deregister(&dev->miscdev); @@ -867,7 +865,7 @@ static void goldfish_pipe_device_deinit(struct platform_device *pdev, { misc_deregister(&dev->miscdev); kfree(dev->pipes); - free_page((unsigned long)dev->buffers); + kfree(dev->buffers); } static int goldfish_pipe_probe(struct platform_device *pdev) diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c index 7e108982a582..cd7ba680b841 100644 --- a/drivers/power/reset/qcom-pon.c +++ b/drivers/power/reset/qcom-pon.c @@ -11,6 +11,7 @@ #include <linux/reboot.h> #include <linux/reboot-mode.h> #include <linux/regmap.h> +#include <linux/spmi.h> #define PON_SOFT_RB_SPARE 0x8f @@ -22,7 +23,6 @@ struct qcom_pon { struct device *dev; struct regmap *regmap; - u32 baseaddr; struct reboot_mode_driver reboot_mode; long reason_shift; }; @@ -35,7 +35,7 @@ static int qcom_pon_reboot_mode_write(struct reboot_mode_driver *reboot, int ret; ret = regmap_update_bits(pon->regmap, - pon->baseaddr + PON_SOFT_RB_SPARE, + PON_SOFT_RB_SPARE, GENMASK(7, pon->reason_shift), magic << pon->reason_shift); if (ret < 0) @@ -46,6 +46,15 @@ static int qcom_pon_reboot_mode_write(struct reboot_mode_driver *reboot, static int qcom_pon_probe(struct platform_device *pdev) { + struct regmap_config qcom_pon_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xff, + .fast_io = true, + }; + struct device *dev = &pdev->dev; + struct spmi_subdevice *sub_sdev; + struct spmi_device *sparent; struct qcom_pon *pon; long reason_shift; int error; @@ -56,17 +65,22 @@ static int qcom_pon_probe(struct platform_device *pdev) pon->dev = &pdev->dev; - pon->regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!pon->regmap) { - dev_err(&pdev->dev, "failed to locate regmap\n"); + sparent = spmi_get_parent_spmi_device(dev); + if (!sparent) return -ENODEV; - } - error = of_property_read_u32(pdev->dev.of_node, "reg", - &pon->baseaddr); + sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent); + if (IS_ERR(sub_sdev)) + return PTR_ERR(sub_sdev); + + error = device_property_read_u32(dev, "reg", &qcom_pon_regmap_config.reg_base); if (error) return error; + pon->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &qcom_pon_regmap_config); + if (IS_ERR(pon->regmap)) + return PTR_ERR(pon->regmap); + reason_shift = (long)of_device_get_match_data(&pdev->dev); if (reason_shift != NO_REASON_SHIFT) { @@ -106,3 +120,4 @@ module_platform_driver(qcom_pon_driver); MODULE_DESCRIPTION("Qualcomm Power On driver"); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("SPMI"); diff --git a/drivers/pps/generators/pps_gen.c b/drivers/pps/generators/pps_gen.c index 5e207c75e340..474e17c36458 100644 --- a/drivers/pps/generators/pps_gen.c +++ b/drivers/pps/generators/pps_gen.c @@ -63,23 +63,34 @@ static long pps_gen_cdev_ioctl(struct file *file, switch (cmd) { case PPS_GEN_SETENABLE: - dev_dbg(pps_gen->dev, "PPS_GEN_SETENABLE\n"); + dev_dbg(&pps_gen->dev, "PPS_GEN_SETENABLE\n"); ret = get_user(status, uiuarg); if (ret) return -EFAULT; - ret = pps_gen->info->enable(pps_gen, status); - if (ret) - return ret; - pps_gen->enabled = status; + scoped_guard(mutex, &pps_gen->info_lock) { + if (!pps_gen->info) + return -ENODEV; + + ret = pps_gen->info->enable(pps_gen, status); + if (ret) + return ret; + pps_gen->enabled = status; + } break; case PPS_GEN_USESYSTEMCLOCK: - dev_dbg(pps_gen->dev, "PPS_GEN_USESYSTEMCLOCK\n"); + dev_dbg(&pps_gen->dev, "PPS_GEN_USESYSTEMCLOCK\n"); + + scoped_guard(mutex, &pps_gen->info_lock) { + if (!pps_gen->info) + return -ENODEV; + status = pps_gen->info->use_system_clock; + } - ret = put_user(pps_gen->info->use_system_clock, uiuarg); + ret = put_user(status, uiuarg); if (ret) return -EFAULT; @@ -89,14 +100,17 @@ static long pps_gen_cdev_ioctl(struct file *file, struct pps_gen_event info; unsigned int ev = pps_gen->last_ev; - dev_dbg(pps_gen->dev, "PPS_GEN_FETCHEVENT\n"); + dev_dbg(&pps_gen->dev, "PPS_GEN_FETCHEVENT\n"); ret = wait_event_interruptible(pps_gen->queue, - ev != pps_gen->last_ev); + ev != pps_gen->last_ev || + !READ_ONCE(pps_gen->info)); if (ret == -ERESTARTSYS) { - dev_dbg(pps_gen->dev, "pending signal caught\n"); + dev_dbg(&pps_gen->dev, "pending signal caught\n"); return -EINTR; } + if (!READ_ONCE(pps_gen->info)) + return -ENODEV; spin_lock_irq(&pps_gen->lock); info.sequence = pps_gen->sequence; @@ -121,7 +135,7 @@ static int pps_gen_cdev_open(struct inode *inode, struct file *file) struct pps_gen_device *pps_gen = container_of(inode->i_cdev, struct pps_gen_device, cdev); - get_device(pps_gen->dev); + get_device(&pps_gen->dev); file->private_data = pps_gen; return 0; } @@ -130,7 +144,7 @@ static int pps_gen_cdev_release(struct inode *inode, struct file *file) { struct pps_gen_device *pps_gen = file->private_data; - put_device(pps_gen->dev); + put_device(&pps_gen->dev); return 0; } @@ -151,19 +165,15 @@ static void pps_gen_device_destruct(struct device *dev) { struct pps_gen_device *pps_gen = dev_get_drvdata(dev); - cdev_del(&pps_gen->cdev); - pr_debug("deallocating pps-gen%d\n", pps_gen->id); ida_free(&pps_gen_ida, pps_gen->id); - kfree(dev); kfree(pps_gen); } static int pps_gen_register_cdev(struct pps_gen_device *pps_gen) { int err; - dev_t devt; err = ida_alloc_max(&pps_gen_ida, PPS_GEN_MAX_SOURCES - 1, GFP_KERNEL); if (err < 0) { @@ -171,46 +181,73 @@ static int pps_gen_register_cdev(struct pps_gen_device *pps_gen) pr_err("too many PPS sources in the system\n"); err = -EBUSY; } + kfree(pps_gen); return err; } pps_gen->id = err; - devt = MKDEV(MAJOR(pps_gen_devt), pps_gen->id); + /* + * From here on pps_gen belongs to its device and is freed by + * pps_gen_device_destruct(). The cdev holds a reference to the + * device, so pps_gen stays around until the last file is closed. + */ + device_initialize(&pps_gen->dev); + pps_gen->dev.class = &pps_gen_class; + pps_gen->dev.parent = pps_gen->info->parent; + pps_gen->dev.devt = MKDEV(MAJOR(pps_gen_devt), pps_gen->id); + pps_gen->dev.release = pps_gen_device_destruct; + dev_set_drvdata(&pps_gen->dev, pps_gen); cdev_init(&pps_gen->cdev, &pps_gen_cdev_fops); pps_gen->cdev.owner = pps_gen->info->owner; - err = cdev_add(&pps_gen->cdev, devt, 1); + err = dev_set_name(&pps_gen->dev, "pps-gen%d", pps_gen->id); + if (err) + goto put_dev; + + err = cdev_device_add(&pps_gen->cdev, &pps_gen->dev); if (err) { pr_err("failed to add char device %d:%d\n", MAJOR(pps_gen_devt), pps_gen->id); - goto free_ida; + goto put_dev; } - pps_gen->dev = device_create(&pps_gen_class, pps_gen->info->parent, devt, - pps_gen, "pps-gen%d", pps_gen->id); - if (IS_ERR(pps_gen->dev)) { - err = PTR_ERR(pps_gen->dev); - goto del_cdev; - } - pps_gen->dev->release = pps_gen_device_destruct; - dev_set_drvdata(pps_gen->dev, pps_gen); pr_debug("generator got cdev (%d:%d)\n", MAJOR(pps_gen_devt), pps_gen->id); return 0; -del_cdev: - cdev_del(&pps_gen->cdev); -free_ida: - ida_free(&pps_gen_ida, pps_gen->id); +put_dev: + put_device(&pps_gen->dev); return err; } static void pps_gen_unregister_cdev(struct pps_gen_device *pps_gen) { pr_debug("unregistering pps-gen%d\n", pps_gen->id); - device_destroy(&pps_gen_class, pps_gen->dev->devt); + cdev_device_del(&pps_gen->cdev, &pps_gen->dev); + + /* + * An open file keeps pps_gen around, but the driver may free info as + * soon as we return. The sysfs files are gone now, so wait for the + * ioctls using info and make later ones fail. + * + * The driver may have stopped the generator before calling us, but + * userspace could have enabled it again since. Nothing can enable it + * after this point, so stop it here for good. + */ + scoped_guard(mutex, &pps_gen->info_lock) { + if (pps_gen->enabled) { + pps_gen->info->enable(pps_gen, false); + pps_gen->enabled = false; + } + WRITE_ONCE(pps_gen->info, NULL); + } + + /* Wake up the readers in PPS_GEN_FETCHEVENT, they fail now as well */ + wake_up_interruptible_all(&pps_gen->queue); + + put_device(&pps_gen->dev); } /* @@ -241,21 +278,19 @@ struct pps_gen_device *pps_gen_register_source(const struct pps_gen_source_info pps_gen->info = info; pps_gen->enabled = false; + mutex_init(&pps_gen->info_lock); init_waitqueue_head(&pps_gen->queue); spin_lock_init(&pps_gen->lock); - /* Create the char device */ + /* Create the char device, this frees pps_gen on failure */ err = pps_gen_register_cdev(pps_gen); if (err < 0) { pr_err(" unable to create char device\n"); - goto kfree_pps_gen; + goto pps_gen_register_source_exit; } return pps_gen; -kfree_pps_gen: - kfree(pps_gen); - pps_gen_register_source_exit: pr_err("unable to register generator\n"); @@ -289,7 +324,7 @@ void pps_gen_event(struct pps_gen_device *pps_gen, { unsigned long flags; - dev_dbg(pps_gen->dev, "PPS generator event %u\n", event); + dev_dbg(&pps_gen->dev, "PPS generator event %u\n", event); spin_lock_irqsave(&pps_gen->lock, flags); diff --git a/drivers/slimbus/messaging.c b/drivers/slimbus/messaging.c index e2dbe4a66b70..ee127ef42046 100644 --- a/drivers/slimbus/messaging.c +++ b/drivers/slimbus/messaging.c @@ -139,7 +139,7 @@ int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn) if (need_tid) { ret = slim_alloc_txn_tid(ctrl, txn); if (ret) - return ret; + goto slim_xfer_err; if (!txn->msg->comp) txn->comp = &done; diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index 6e89415712ce..eb983b6d1815 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -2,6 +2,7 @@ // Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. // Copyright (c) 2018, Linaro Limited +#include <linux/bitfield.h> #include <linux/irq.h> #include <linux/kernel.h> #include <linux/init.h> @@ -87,6 +88,10 @@ #define SLIM_ROOT_FREQ 24576000 #define LADDR_RETRY 5 +#define SLIM_CHAN_CTRL_CMD GENMASK(7, 6) +#define SLIM_CHAN_CTRL_LADDR GENMASK(4, 0) +#define SLIM_MSG_HDR_LEN 4 + /* Per spec.max 40 bytes per received message */ #define SLIM_MSGQ_BUF_LEN 40 #define QCOM_SLIM_NGD_DESC_NUM 32 @@ -1086,6 +1091,78 @@ static int qcom_slim_ngd_enable_stream(struct slim_stream_runtime *rt) return ret; } +static int qcom_slim_ngd_disable_stream(struct slim_stream_runtime *rt) +{ + struct slim_device *sdev = rt->dev; + struct slim_controller *ctrl = sdev->ctrl; + struct slim_msg_txn txn = { 0 }; + struct slim_val_inf msg = { 0 }; + u8 wbuf[SLIM_MSGQ_BUF_LEN]; + u8 rbuf[SLIM_MSGQ_BUF_LEN]; + int i, ret; + + txn.mt = SLIM_MSG_MT_DEST_REFERRED_USER; + txn.dt = SLIM_MSG_DEST_LOGICALADDR; + txn.la = SLIM_LA_MGR; + txn.ec = 0; + txn.msg = &msg; + txn.msg->num_bytes = 0; + txn.msg->wbuf = wbuf; + txn.msg->rbuf = rbuf; + + for (i = 0; i < rt->num_ports; i++) { + struct slim_port *port = &rt->ports[i]; + + if (txn.msg->num_bytes == 0) { + wbuf[txn.msg->num_bytes++] = + FIELD_PREP(SLIM_CHAN_CTRL_CMD, SLIM_CH_REMOVE) | + FIELD_PREP(SLIM_CHAN_CTRL_LADDR, sdev->laddr); + + ret = slim_alloc_txn_tid(ctrl, &txn); + if (ret) { + dev_err(&sdev->dev, "Fail to allocate TID ret:%d\n", ret); + return ret; + } + wbuf[txn.msg->num_bytes++] = txn.tid; + } + wbuf[txn.msg->num_bytes++] = port->ch.id; + } + + txn.mc = SLIM_USR_MC_CHAN_CTRL; + txn.rl = txn.msg->num_bytes + SLIM_MSG_HDR_LEN; + ret = qcom_slim_ngd_xfer_msg_sync(ctrl, &txn); + if (ret) { + slim_free_txn_tid(ctrl, &txn); + /* Controller restarting, the channels are gone anyway */ + if (ret == -EREMOTEIO) + return 0; + dev_err(&sdev->dev, "TX timed out: MC: 0x%x, mt: 0x%x, laddr: 0x%x, ret: %d\n", + txn.mc, txn.mt, sdev->laddr, ret); + return ret; + } + + txn.mc = SLIM_USR_MC_RECONFIG_NOW; + txn.msg->num_bytes = 2; + wbuf[1] = sdev->laddr; + txn.rl = txn.msg->num_bytes + SLIM_MSG_HDR_LEN; + + ret = slim_alloc_txn_tid(ctrl, &txn); + if (ret) { + dev_err(&sdev->dev, "Fail to allocate TID ret:%d\n", ret); + return ret; + } + + wbuf[0] = txn.tid; + ret = qcom_slim_ngd_xfer_msg_sync(ctrl, &txn); + if (ret) { + slim_free_txn_tid(ctrl, &txn); + dev_err(&sdev->dev, "TX timed out: MC: 0x%x, mt: 0x%x, laddr: 0x%x, ret: %d\n", + txn.mc, txn.mt, sdev->laddr, ret); + } + + return ret; +} + static int qcom_slim_ngd_get_laddr(struct slim_controller *ctrl, struct slim_eaddr *ea, u8 *laddr) { @@ -1539,7 +1616,7 @@ static int of_qcom_slim_ngd_register(struct device *parent, kfree(ngd); return ret; } - platform_device_set_of_node(ngd->pdev, node); + platform_device_set_fwnode(ngd->pdev, of_fwnode_handle(node)); ctrl->ngd = ngd; ret = platform_device_add(ngd->pdev); @@ -1614,7 +1691,7 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) IRQF_TRIGGER_HIGH | IRQF_NO_AUTOEN, "slim-ngd", ctrl); if (ret) - return dev_err_probe(&pdev->dev, ret, "request IRQ failed\n"); + return ret; ctrl->dev = dev; ctrl->framer.rootfreq = SLIM_ROOT_FREQ >> 3; @@ -1625,6 +1702,7 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) ctrl->ctrl.clkgear = SLIM_MAX_CLK_GEAR; ctrl->ctrl.get_laddr = qcom_slim_ngd_get_laddr; ctrl->ctrl.enable_stream = qcom_slim_ngd_enable_stream; + ctrl->ctrl.disable_stream = qcom_slim_ngd_disable_stream; ctrl->ctrl.xfer_msg = qcom_slim_ngd_xfer_msg; ctrl->ctrl.wakeup = NULL; ctrl->state = QCOM_SLIM_NGD_CTRL_DOWN; diff --git a/drivers/slimbus/slimbus.h b/drivers/slimbus/slimbus.h index 00a7f112574b..8e34f30601d3 100644 --- a/drivers/slimbus/slimbus.h +++ b/drivers/slimbus/slimbus.h @@ -317,6 +317,18 @@ enum slim_transport_protocol { }; /** + * enum slim_ch_control: Channel control. + * @SLIM_CH_ACTIVATE: Schedules channel or group of channels in the TDM frame. + * @SLIM_CH_SUSPEND: Keeps the TDM schedule but halts data transfer. + * @SLIM_CH_REMOVE: Drops the channel or group from the TDM frame. + */ +enum slim_ch_control { + SLIM_CH_ACTIVATE, + SLIM_CH_SUSPEND, + SLIM_CH_REMOVE, +}; + +/** * struct slim_stream_runtime - SLIMbus stream runtime instance * * @name: Name of the stream @@ -325,7 +337,7 @@ enum slim_transport_protocol { * @prot: Transport protocol used in this stream * @rate: Data rate of samples * * @bps: bits per sample - * @ratem: rate multipler which is super frame rate/data rate + * @ratem: rate multiplier which is super frame rate/data rate * @num_ports: number of ports * @ports: pointer to instance of ports * @node: list head for stream associated with slim device. diff --git a/drivers/spmi/spmi-devres.c b/drivers/spmi/spmi-devres.c index 62c4b3f24d06..c3e889fe1b6e 100644 --- a/drivers/spmi/spmi-devres.c +++ b/drivers/spmi/spmi-devres.c @@ -60,5 +60,29 @@ int devm_spmi_controller_add(struct device *parent, struct spmi_controller *ctrl } EXPORT_SYMBOL_GPL(devm_spmi_controller_add); +static void devm_spmi_subdevice_remove(void *sub_sdev) +{ + spmi_subdevice_remove(sub_sdev); +} + +struct spmi_subdevice *devm_spmi_subdevice_alloc_and_add(struct device *dev, + struct spmi_device *sparent) +{ + struct spmi_subdevice *sub_sdev; + int ret; + + sub_sdev = spmi_subdevice_alloc_and_add(sparent); + if (IS_ERR(sub_sdev)) + return sub_sdev; + + ret = devm_add_action_or_reset(dev, devm_spmi_subdevice_remove, sub_sdev); + if (ret) + return ERR_PTR(ret); + + return sub_sdev; +} +EXPORT_SYMBOL_NS_GPL(devm_spmi_subdevice_alloc_and_add, "SPMI"); + MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("SPMI devres helpers"); +MODULE_IMPORT_NS("SPMI"); diff --git a/drivers/spmi/spmi-mtk-pmif.c b/drivers/spmi/spmi-mtk-pmif.c index 1048420b5afb..833c2b30d09f 100644 --- a/drivers/spmi/spmi-mtk-pmif.c +++ b/drivers/spmi/spmi-mtk-pmif.c @@ -21,6 +21,7 @@ #define SWINF_WFVLDCLR 0x06 #define GET_SWINF(x) (((x) >> 1) & 0x7) +#define GET_SWINFERR(x) (((x) >> 18) & 0x1) #define PMIF_CMD_REG_0 0 #define PMIF_CMD_REG 1 @@ -349,6 +350,7 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, struct pmif *arb = to_mtk_pmif(ctrl); struct ch_reg *inf_reg; int ret; + u8 retry = 0; u32 data, cmd; unsigned long flags; @@ -385,17 +387,35 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, return ret; } - /* Send the command. */ cmd = (opc << 30) | (sid << 24) | ((len - 1) << 16) | addr; - pmif_writel(arb, pbus, cmd, inf_reg->ch_send); + do { + /* Send the command. */ + pmif_writel(arb, pbus, cmd, inf_reg->ch_send); + + /* + * Wait for Software Interface FSM state to be WFVLDCLR or to + * return an error. + * + * If this is WFVLDCLR, read the data and clear the valid flag; + * If error or timeout, retry for a maximum of 3 times as a + * workaround for an hardware issue. + */ + ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], + data, + GET_SWINF(data) == SWINF_WFVLDCLR || + GET_SWINFERR(data), + PMIF_DELAY_US, PMIF_TIMEOUT_US / 2); + if (ret < 0) + continue; + + if (GET_SWINFERR(data)) { + ret = -EIO; + continue; + } + + break; + } while (++retry < 3); - /* - * Wait for Software Interface FSM state to be WFVLDCLR, - * read the data and clear the valid flag. - */ - ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], - data, GET_SWINF(data) == SWINF_WFVLDCLR, - PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) { raw_spin_unlock_irqrestore(&pbus->lock, flags); dev_err(&ctrl->dev, "failed to wait for SWINF_WFVLDCLR\n"); @@ -726,9 +746,11 @@ static int mtk_spmi_bus_probe(struct platform_device *pdev, for (i = 0; i < pbus->nclks; i++) { pbus->clks[i].id = pmif_clock_names[i]; pbus->clks[i].clk = of_clk_get_by_name(node, pbus->clks[i].id); - if (IS_ERR(pbus->clks[i].clk)) + if (IS_ERR(pbus->clks[i].clk)) { + clk_bulk_put(i, pbus->clks); return dev_err_probe(&pdev->dev, PTR_ERR(pbus->clks[i].clk), "Failed to get clocks\n"); + } } err = clk_bulk_prepare_enable(pbus->nclks, pbus->clks); diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c index 2e2cb4774103..402ffeaff8e2 100644 --- a/drivers/spmi/spmi-pmic-arb.c +++ b/drivers/spmi/spmi-pmic-arb.c @@ -2060,7 +2060,7 @@ static int spmi_pmic_arb_register_buses(struct spmi_pmic_arb *pmic_arb, } } - return ret; + return 0; } static void spmi_pmic_arb_deregister_buses(struct spmi_pmic_arb *pmic_arb) diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index 57b7c0cb4240..ec97f12046f5 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -19,11 +19,13 @@ static bool is_registered; static DEFINE_IDA(ctrl_ida); +static DEFINE_IDA(spmi_subdevice_ida); static void spmi_dev_release(struct device *dev) { struct spmi_device *sdev = to_spmi_device(dev); + of_node_put(dev->of_node); kfree(sdev); } @@ -31,6 +33,19 @@ static const struct device_type spmi_dev_type = { .release = spmi_dev_release, }; +static void spmi_subdev_release(struct device *dev) +{ + struct spmi_device *sdev = to_spmi_device(dev); + struct spmi_subdevice *sub_sdev = container_of(sdev, struct spmi_subdevice, sdev); + + ida_free(&spmi_subdevice_ida, sub_sdev->devid); + kfree(sub_sdev); +} + +static const struct device_type spmi_subdev_type = { + .release = spmi_subdev_release, +}; + static void spmi_ctrl_release(struct device *dev) { struct spmi_controller *ctrl = to_spmi_controller(dev); @@ -68,15 +83,12 @@ int spmi_device_add(struct spmi_device *sdev) err = device_add(&sdev->dev); if (err < 0) { - dev_err(&sdev->dev, "Can't add %s, status %d\n", - dev_name(&sdev->dev), err); - goto err_device_add; + dev_err(&sdev->dev, "Can't add device, status %pe\n", ERR_PTR(err)); + return err; } - dev_dbg(&sdev->dev, "device %s registered\n", dev_name(&sdev->dev)); - -err_device_add: - return err; + dev_dbg(&sdev->dev, "device registered\n"); + return 0; } EXPORT_SYMBOL_GPL(spmi_device_add); @@ -90,6 +102,18 @@ void spmi_device_remove(struct spmi_device *sdev) } EXPORT_SYMBOL_GPL(spmi_device_remove); +/** + * spmi_subdevice_remove() - Remove an SPMI subdevice + * @sub_sdev: spmi_device to be removed + */ +void spmi_subdevice_remove(struct spmi_subdevice *sub_sdev) +{ + struct spmi_device *sdev = &sub_sdev->sdev; + + device_unregister(&sdev->dev); +} +EXPORT_SYMBOL_NS_GPL(spmi_subdevice_remove, "SPMI"); + static inline int spmi_cmd(struct spmi_controller *ctrl, u8 opcode, u8 sid) { @@ -408,6 +432,29 @@ struct spmi_device *spmi_find_device_by_of_node(struct device_node *np) EXPORT_SYMBOL_GPL(spmi_find_device_by_of_node); /** + * spmi_get_parent_spmi_device() - get the parent SPMI device from current dev + * @dev: pointer to a subdevice on SPMI bus + * + * Checks if the passed device is a child of an SPMI device and returns a + * handle to the parent SPMI device without incrementing any refcount. + * + * Return: Handle to the parent SPMI device or NULL + */ +struct spmi_device *spmi_get_parent_spmi_device(struct device *dev) +{ + struct device *parent; + + if (dev && dev->parent) { + parent = dev->parent; + + if (parent->type == &spmi_dev_type) + return to_spmi_device(parent); + } + return NULL; +} +EXPORT_SYMBOL_NS_GPL(spmi_get_parent_spmi_device, "SPMI"); + +/** * spmi_device_alloc() - Allocate a new SPMI device * @ctrl: associated controller * @@ -432,6 +479,58 @@ struct spmi_device *spmi_device_alloc(struct spmi_controller *ctrl) EXPORT_SYMBOL_GPL(spmi_device_alloc); /** + * spmi_subdevice_alloc_and_add(): Allocate and add a new SPMI sub-device + * @sparent: SPMI parent device with previously registered SPMI controller + * + * Returns: + * Pointer to newly allocated SPMI sub-device for success or error pointer. + */ +struct spmi_subdevice *spmi_subdevice_alloc_and_add(struct spmi_device *sparent) +{ + struct spmi_subdevice *sub_sdev; + struct spmi_device *sdev; + int ret; + + sub_sdev = kzalloc_obj(*sub_sdev); + if (!sub_sdev) + return ERR_PTR(-ENOMEM); + + sdev = &sub_sdev->sdev; + sdev->ctrl = sparent->ctrl; + sdev->usid = sparent->usid; + + ret = ida_alloc(&spmi_subdevice_ida, GFP_KERNEL); + if (ret < 0) { + kfree(sub_sdev); + return ERR_PTR(ret); + } + sub_sdev->devid = ret; + + device_initialize(&sdev->dev); + sdev->dev.parent = &sparent->dev; + sdev->dev.bus = &spmi_bus_type; + sdev->dev.type = &spmi_subdev_type; + + ret = dev_set_name(&sdev->dev, "%u-%02x.%u.auto", + sdev->ctrl->nr, sdev->usid, sub_sdev->devid); + if (ret) + goto err_put_dev; + + ret = device_add(&sdev->dev); + if (ret) { + dev_err(&sdev->dev, "Can't add device, status %pe\n", ERR_PTR(ret)); + goto err_put_dev; + } + + return sub_sdev; + +err_put_dev: + put_device(&sdev->dev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_NS_GPL(spmi_subdevice_alloc_and_add, "SPMI"); + +/** * spmi_controller_alloc() - Allocate a new SPMI controller * @parent: parent device * @size: size of private data @@ -494,8 +593,8 @@ static void of_spmi_register_devices(struct spmi_controller *ctrl) err = of_property_read_u32_array(node, "reg", reg, 2); if (err) { dev_err(&ctrl->dev, - "node %pOF err (%d) does not have 'reg' property\n", - node, err); + "node %pOF err (%pe) does not have 'reg' property\n", + node, ERR_PTR(err)); continue; } @@ -517,13 +616,13 @@ static void of_spmi_register_devices(struct spmi_controller *ctrl) if (!sdev) continue; - device_set_node(&sdev->dev, of_fwnode_handle(node)); + device_set_node(&sdev->dev, of_fwnode_handle(of_node_get(node))); sdev->usid = (u8)reg[0]; err = spmi_device_add(sdev); if (err) { dev_err(&sdev->dev, - "failure adding device. status %d\n", err); + "failure adding device. status %pe\n", ERR_PTR(err)); spmi_device_put(sdev); } } diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a b/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a index 863d3856718a..d19db00acd1d 100644 --- a/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a @@ -4,7 +4,7 @@ Contact: linux-iio@vger.kernel.org Description: Writing 1 enables the cell balance output switch corresponding to input Y. Writing 0 disables it. If the inY-inZ_balance_timer - is set to a none zero value, the corresponding switch will + is set to a non-zero value, the corresponding switch will enable for the programmed amount of time, before it automatically disables. diff --git a/drivers/staging/iio/Kconfig b/drivers/staging/iio/Kconfig index a60631c1f449..bf6addf2974f 100644 --- a/drivers/staging/iio/Kconfig +++ b/drivers/staging/iio/Kconfig @@ -5,7 +5,6 @@ menu "IIO staging drivers" depends on IIO -source "drivers/staging/iio/accel/Kconfig" source "drivers/staging/iio/adc/Kconfig" source "drivers/staging/iio/addac/Kconfig" source "drivers/staging/iio/frequency/Kconfig" diff --git a/drivers/staging/iio/Makefile b/drivers/staging/iio/Makefile index 628583535393..739e4cbf9a7a 100644 --- a/drivers/staging/iio/Makefile +++ b/drivers/staging/iio/Makefile @@ -3,7 +3,6 @@ # Makefile for the industrial I/O core. # -obj-y += accel/ obj-y += adc/ obj-y += addac/ obj-y += frequency/ diff --git a/drivers/staging/iio/accel/Kconfig b/drivers/staging/iio/accel/Kconfig deleted file mode 100644 index cee51f64bc4b..000000000000 --- a/drivers/staging/iio/accel/Kconfig +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Accelerometer drivers -# -menu "Accelerometers" - -config ADIS16203 - tristate "Analog Devices ADIS16203 Programmable 360 Degrees Inclinometer" - depends on SPI - select IIO_ADIS_LIB - select IIO_ADIS_LIB_BUFFER if IIO_BUFFER - help - Say Y here to build support for Analog Devices adis16203 Programmable - 360 Degrees Inclinometer. - - To compile this driver as a module, say M here: the module will be - called adis16203. - -endmenu diff --git a/drivers/staging/iio/accel/Makefile b/drivers/staging/iio/accel/Makefile deleted file mode 100644 index acac7bc9b9c0..000000000000 --- a/drivers/staging/iio/accel/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Makefile for industrial I/O accelerometer drivers -# - -obj-$(CONFIG_ADIS16203) += adis16203.o diff --git a/drivers/staging/iio/accel/adis16203.c b/drivers/staging/iio/accel/adis16203.c deleted file mode 100644 index 830ff38fda92..000000000000 --- a/drivers/staging/iio/accel/adis16203.c +++ /dev/null @@ -1,315 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * ADIS16203 Programmable 360 Degrees Inclinometer - * - * Copyright 2010 Analog Devices Inc. - */ - -#include <linux/device.h> - -#include <linux/iio/iio.h> -#include <linux/iio/imu/adis.h> - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/spi/spi.h> - -#define ADIS16203_STARTUP_DELAY 220 /* ms */ - -/* Flash memory write count */ -#define ADIS16203_FLASH_CNT 0x00 - -/* Output, power supply */ -#define ADIS16203_SUPPLY_OUT 0x02 - -/* Output, auxiliary ADC input */ -#define ADIS16203_AUX_ADC 0x08 - -/* Output, temperature */ -#define ADIS16203_TEMP_OUT 0x0A - -/* Output, x-axis inclination */ -#define ADIS16203_XINCL_OUT 0x0C - -/* Output, y-axis inclination */ -#define ADIS16203_YINCL_OUT 0x0E - -/* Incline null calibration */ -#define ADIS16203_INCL_NULL 0x18 - -/* Alarm 1 amplitude threshold */ -#define ADIS16203_ALM_MAG1 0x20 - -/* Alarm 2 amplitude threshold */ -#define ADIS16203_ALM_MAG2 0x22 - -/* Alarm 1, sample period */ -#define ADIS16203_ALM_SMPL1 0x24 - -/* Alarm 2, sample period */ -#define ADIS16203_ALM_SMPL2 0x26 - -/* Alarm control */ -#define ADIS16203_ALM_CTRL 0x28 - -/* Auxiliary DAC data */ -#define ADIS16203_AUX_DAC 0x30 - -/* General-purpose digital input/output control */ -#define ADIS16203_GPIO_CTRL 0x32 - -/* Miscellaneous control */ -#define ADIS16203_MSC_CTRL 0x34 - -/* Internal sample period (rate) control */ -#define ADIS16203_SMPL_PRD 0x36 - -/* Operation, filter configuration */ -#define ADIS16203_AVG_CNT 0x38 - -/* Operation, sleep mode control */ -#define ADIS16203_SLP_CNT 0x3A - -/* Diagnostics, system status register */ -#define ADIS16203_DIAG_STAT 0x3C - -/* Operation, system command register */ -#define ADIS16203_GLOB_CMD 0x3E - -/* MSC_CTRL */ - -/* Self-test at power-on: 1 = disabled, 0 = enabled */ -#define ADIS16203_MSC_CTRL_PWRUP_SELF_TEST BIT(10) - -/* Reverses rotation of both inclination outputs */ -#define ADIS16203_MSC_CTRL_REVERSE_ROT_EN BIT(9) - -/* Self-test enable */ -#define ADIS16203_MSC_CTRL_SELF_TEST_EN BIT(8) - -/* Data-ready enable: 1 = enabled, 0 = disabled */ -#define ADIS16203_MSC_CTRL_DATA_RDY_EN BIT(2) - -/* Data-ready polarity: 1 = active high, 0 = active low */ -#define ADIS16203_MSC_CTRL_ACTIVE_HIGH BIT(1) - -/* Data-ready line selection: 1 = DIO1, 0 = DIO0 */ -#define ADIS16203_MSC_CTRL_DATA_RDY_DIO1 BIT(0) - -/* DIAG_STAT */ - -/* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */ -#define ADIS16203_DIAG_STAT_ALARM2 BIT(9) - -/* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */ -#define ADIS16203_DIAG_STAT_ALARM1 BIT(8) - -/* Self-test diagnostic error flag */ -#define ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT 5 - -/* SPI communications failure */ -#define ADIS16203_DIAG_STAT_SPI_FAIL_BIT 3 - -/* Flash update failure */ -#define ADIS16203_DIAG_STAT_FLASH_UPT_BIT 2 - -/* Power supply above 3.625 V */ -#define ADIS16203_DIAG_STAT_POWER_HIGH_BIT 1 - -/* Power supply below 2.975 V */ -#define ADIS16203_DIAG_STAT_POWER_LOW_BIT 0 - -/* GLOB_CMD */ - -#define ADIS16203_GLOB_CMD_SW_RESET BIT(7) -#define ADIS16203_GLOB_CMD_CLEAR_STAT BIT(4) -#define ADIS16203_GLOB_CMD_FACTORY_CAL BIT(1) - -#define ADIS16203_ERROR_ACTIVE BIT(14) - -enum adis16203_scan { - ADIS16203_SCAN_INCLI_X, - ADIS16203_SCAN_INCLI_Y, - ADIS16203_SCAN_SUPPLY, - ADIS16203_SCAN_AUX_ADC, - ADIS16203_SCAN_TEMP, -}; - -#define DRIVER_NAME "adis16203" - -static const u8 adis16203_addresses[] = { - [ADIS16203_SCAN_INCLI_X] = ADIS16203_INCL_NULL, -}; - -static int adis16203_write_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int val, - int val2, - long mask) -{ - struct adis *st = iio_priv(indio_dev); - /* currently only one writable parameter which keeps this simple */ - u8 addr = adis16203_addresses[chan->scan_index]; - - return adis_write_reg_16(st, addr, val & 0x3FFF); -} - -static int adis16203_read_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int *val, int *val2, - long mask) -{ - struct adis *st = iio_priv(indio_dev); - int ret; - u8 addr; - s16 val16; - - switch (mask) { - case IIO_CHAN_INFO_RAW: - return adis_single_conversion(indio_dev, chan, - ADIS16203_ERROR_ACTIVE, val); - case IIO_CHAN_INFO_SCALE: - switch (chan->type) { - case IIO_VOLTAGE: - if (chan->channel == 0) { - *val = 1; - *val2 = 220000; /* 1.22 mV */ - } else { - *val = 0; - *val2 = 610000; /* 0.61 mV */ - } - return IIO_VAL_INT_PLUS_MICRO; - case IIO_TEMP: - *val = -470; /* -0.47 C */ - *val2 = 0; - return IIO_VAL_INT_PLUS_MICRO; - case IIO_INCLI: - *val = 0; - *val2 = 25000; /* 0.025 degree */ - return IIO_VAL_INT_PLUS_MICRO; - default: - return -EINVAL; - } - case IIO_CHAN_INFO_OFFSET: - *val = 25000 / -470 - 1278; /* 25 C = 1278 */ - return IIO_VAL_INT; - case IIO_CHAN_INFO_CALIBBIAS: - addr = adis16203_addresses[chan->scan_index]; - ret = adis_read_reg_16(st, addr, &val16); - if (ret) - return ret; - *val = sign_extend32(val16, 13); - return IIO_VAL_INT; - default: - return -EINVAL; - } -} - -static const struct iio_chan_spec adis16203_channels[] = { - ADIS_SUPPLY_CHAN(ADIS16203_SUPPLY_OUT, ADIS16203_SCAN_SUPPLY, 0, 12), - ADIS_AUX_ADC_CHAN(ADIS16203_AUX_ADC, ADIS16203_SCAN_AUX_ADC, 0, 12), - ADIS_INCLI_CHAN(X, ADIS16203_XINCL_OUT, ADIS16203_SCAN_INCLI_X, - BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), - /* Fixme: Not what it appears to be - see data sheet */ - ADIS_INCLI_CHAN(Y, ADIS16203_YINCL_OUT, ADIS16203_SCAN_INCLI_Y, - 0, 0, 14), - ADIS_TEMP_CHAN(ADIS16203_TEMP_OUT, ADIS16203_SCAN_TEMP, 0, 12), - IIO_CHAN_SOFT_TIMESTAMP(5), -}; - -static const struct iio_info adis16203_info = { - .read_raw = adis16203_read_raw, - .write_raw = adis16203_write_raw, - .update_scan_mode = adis_update_scan_mode, -}; - -static const char * const adis16203_status_error_msgs[] = { - [ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT] = "Self test failure", - [ADIS16203_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", - [ADIS16203_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed", - [ADIS16203_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", - [ADIS16203_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 2.975V", -}; - -static const struct adis_timeout adis16203_timeouts = { - .reset_ms = ADIS16203_STARTUP_DELAY, - .sw_reset_ms = ADIS16203_STARTUP_DELAY, - .self_test_ms = ADIS16203_STARTUP_DELAY -}; - -static const struct adis_data adis16203_data = { - .read_delay = 20, - .msc_ctrl_reg = ADIS16203_MSC_CTRL, - .glob_cmd_reg = ADIS16203_GLOB_CMD, - .diag_stat_reg = ADIS16203_DIAG_STAT, - - .self_test_mask = ADIS16203_MSC_CTRL_SELF_TEST_EN, - .self_test_reg = ADIS16203_MSC_CTRL, - .self_test_no_autoclear = true, - .timeouts = &adis16203_timeouts, - - .status_error_msgs = adis16203_status_error_msgs, - .status_error_mask = BIT(ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT) | - BIT(ADIS16203_DIAG_STAT_SPI_FAIL_BIT) | - BIT(ADIS16203_DIAG_STAT_FLASH_UPT_BIT) | - BIT(ADIS16203_DIAG_STAT_POWER_HIGH_BIT) | - BIT(ADIS16203_DIAG_STAT_POWER_LOW_BIT), -}; - -static int adis16203_probe(struct spi_device *spi) -{ - int ret; - struct iio_dev *indio_dev; - struct adis *st; - - /* setup the industrialio driver allocated elements */ - indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); - if (!indio_dev) - return -ENOMEM; - st = iio_priv(indio_dev); - /* this is only used for removal purposes */ - spi_set_drvdata(spi, indio_dev); - - indio_dev->name = spi->dev.driver->name; - indio_dev->channels = adis16203_channels; - indio_dev->num_channels = ARRAY_SIZE(adis16203_channels); - indio_dev->info = &adis16203_info; - indio_dev->modes = INDIO_DIRECT_MODE; - - ret = adis_init(st, indio_dev, spi, &adis16203_data); - if (ret) - return ret; - - ret = devm_adis_setup_buffer_and_trigger(st, indio_dev, NULL); - if (ret) - return ret; - - /* Get the device into a sane initial state */ - ret = __adis_initial_startup(st); - if (ret) - return ret; - - return devm_iio_device_register(&spi->dev, indio_dev); -} - -static const struct of_device_id adis16203_of_match[] = { - { .compatible = "adi,adis16203" }, - { } -}; - -MODULE_DEVICE_TABLE(of, adis16203_of_match); - -static struct spi_driver adis16203_driver = { - .driver = { - .name = "adis16203", - .of_match_table = adis16203_of_match, - }, - .probe = adis16203_probe, -}; -module_spi_driver(adis16203_driver); - -MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); -MODULE_DESCRIPTION("Analog Devices ADIS16203 Programmable 360 Degrees Inclinometer"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("spi:adis16203"); -MODULE_IMPORT_NS("IIO_ADISLIB"); diff --git a/drivers/staging/iio/adc/ad7816.c b/drivers/staging/iio/adc/ad7816.c index 30644d2d7c54..2a15d7598abb 100644 --- a/drivers/staging/iio/adc/ad7816.c +++ b/drivers/staging/iio/adc/ad7816.c @@ -5,19 +5,21 @@ * Copyright 2010 Analog Devices Inc. */ -#include <linux/interrupt.h> -#include <linux/gpio/consumer.h> +#include <linux/cleanup.h> #include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> #include <linux/kernel.h> -#include <linux/slab.h> -#include <linux/sysfs.h> #include <linux/list.h> -#include <linux/spi/spi.h> #include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> +#include <linux/iio/events.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> -#include <linux/iio/events.h> /* * AD7816 config masks @@ -47,6 +49,7 @@ struct ad7816_chip_info { struct gpio_desc *rdwr_pin; struct gpio_desc *convert_pin; struct gpio_desc *busy_pin; + struct mutex lock; /* protect device state during SPI transfers */ u8 oti_data[AD7816_CS_MAX + 1]; u8 channel_id; /* 0 always be temperature */ u8 mode; @@ -67,9 +70,11 @@ static int ad7816_spi_read(struct ad7816_chip_info *chip, u16 *data) int ret; __be16 buf; + guard(mutex)(&chip->lock); + gpiod_set_value(chip->rdwr_pin, 1); gpiod_set_value(chip->rdwr_pin, 0); - ret = spi_write(spi_dev, &chip->channel_id, sizeof(chip->channel_id)); + ret = spi_write_then_read(spi_dev, &chip->channel_id, sizeof(chip->channel_id), NULL, 0); if (ret < 0) { dev_err(&spi_dev->dev, "SPI channel setting error\n"); return ret; @@ -91,7 +96,7 @@ static int ad7816_spi_read(struct ad7816_chip_info *chip, u16 *data) gpiod_set_value(chip->rdwr_pin, 0); gpiod_set_value(chip->rdwr_pin, 1); - ret = spi_read(spi_dev, &buf, sizeof(*data)); + ret = spi_write_then_read(spi_dev, NULL, 0, &buf, sizeof(buf)); if (ret < 0) { dev_err(&spi_dev->dev, "SPI data read error\n"); return ret; @@ -107,9 +112,11 @@ static int ad7816_spi_write(struct ad7816_chip_info *chip, u8 data) struct spi_device *spi_dev = chip->spi_dev; int ret; + guard(mutex)(&chip->lock); + gpiod_set_value(chip->rdwr_pin, 1); gpiod_set_value(chip->rdwr_pin, 0); - ret = spi_write(spi_dev, &data, sizeof(data)); + ret = spi_write_then_read(spi_dev, &data, sizeof(data), NULL, 0); if (ret < 0) dev_err(&spi_dev->dev, "SPI oti data write error\n"); @@ -136,6 +143,8 @@ static ssize_t ad7816_store_mode(struct device *dev, struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct ad7816_chip_info *chip = iio_priv(indio_dev); + guard(mutex)(&chip->lock); + if (strcmp(buf, "full") == 0) { gpiod_set_value(chip->rdwr_pin, 1); chip->mode = AD7816_FULL; @@ -200,6 +209,8 @@ static ssize_t ad7816_store_channel(struct device *dev, return -EINVAL; } + guard(mutex)(&chip->lock); + chip->channel_id = data; return len; @@ -360,6 +371,10 @@ static int ad7816_probe(struct spi_device *spi_dev) return -ENOMEM; chip = iio_priv(indio_dev); + ret = devm_mutex_init(&spi_dev->dev, &chip->lock); + if (ret) + return ret; + chip->spi_dev = spi_dev; for (i = 0; i <= AD7816_CS_MAX; i++) chip->oti_data[i] = 203; diff --git a/drivers/staging/iio/frequency/ad9832.c b/drivers/staging/iio/frequency/ad9832.c index a78249812b26..35f823f24f9b 100644 --- a/drivers/staging/iio/frequency/ad9832.c +++ b/drivers/staging/iio/frequency/ad9832.c @@ -118,6 +118,12 @@ static unsigned long ad9832_calc_freqreg(unsigned long mclk, unsigned long fout) { u64 freqreg = (u64)fout << AD9832_FREQ_BITS; + /* + * mclk is an unsigned long, which triggers a Coccinelle false positive + * warning about using do_div() for 64-by-32 division. However, mclk + * for this hardware will always fit within 32 bits, so do_div() is + * safe to use here. + */ do_div(freqreg, mclk); return freqreg; } diff --git a/drivers/staging/iio/frequency/ad9834.c b/drivers/staging/iio/frequency/ad9834.c index 0d20f796e0f0..4cebb9d0d924 100644 --- a/drivers/staging/iio/frequency/ad9834.c +++ b/drivers/staging/iio/frequency/ad9834.c @@ -103,6 +103,12 @@ static unsigned int ad9834_calc_freqreg(unsigned long mclk, unsigned long fout) { unsigned long long freqreg = (u64)fout * (u64)BIT(AD9834_FREQ_BITS); + /* + * mclk is an unsigned long, which triggers a Coccinelle false positive + * warning about using a do_div() for 64-by-32 division. However, mclk + * for this hardware will always fit within 32 bits, so do_div() is + * safe to use here. + */ do_div(freqreg, mclk); return freqreg; } |
