diff options
Diffstat (limited to 'drivers')
667 files changed, 37589 insertions, 12273 deletions
diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig index 5b3b8041f827..e2e402c9d175 100644 --- a/drivers/android/Kconfig +++ b/drivers/android/Kconfig @@ -4,6 +4,7 @@ menu "Android" config ANDROID_BINDER_IPC bool "Android Binder IPC Driver" depends on MMU + depends on NET default n help Binder is used in Android for both communication between processes, @@ -13,6 +14,19 @@ 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 && !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 @@ -27,7 +41,7 @@ config ANDROID_BINDERFS config ANDROID_BINDER_DEVICES string "Android Binder devices" - depends on ANDROID_BINDER_IPC + depends on ANDROID_BINDER_IPC || ANDROID_BINDER_IPC_RUST default "binder,hwbinder,vndbinder" help Default value for the binder.devices parameter. diff --git a/drivers/android/Makefile b/drivers/android/Makefile index c5d47be0276c..e0c650d3898e 100644 --- a/drivers/android/Makefile +++ b/drivers/android/Makefile @@ -2,5 +2,6 @@ ccflags-y += -I$(src) # needed for trace events obj-$(CONFIG_ANDROID_BINDERFS) += binderfs.o -obj-$(CONFIG_ANDROID_BINDER_IPC) += binder.o binder_alloc.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 index 312b462e349d..8c99ceaa303b 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -74,6 +74,7 @@ #include <linux/cacheflush.h> +#include "binder_netlink.h" #include "binder_internal.h" #include "binder_trace.h" @@ -2993,6 +2994,69 @@ static void binder_set_txn_from_error(struct binder_transaction *t, int id, 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 + */ +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, @@ -3042,6 +3106,32 @@ static void binder_transaction(struct binder_proc *proc, binder_set_extended_error(&thread->ee, t_debug_id, BR_OK, 0); binder_inner_proc_unlock(proc); + t = kzalloc(sizeof(*t), GFP_KERNEL); + 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 = task_euid(proc->tsk); + 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; @@ -3228,24 +3318,13 @@ static void binder_transaction(struct binder_proc *proc, } 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; - /* TODO: reuse incoming transaction for reply */ - t = kzalloc(sizeof(*t), GFP_KERNEL); - if (t == NULL) { - 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); - tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL); if (tcomplete == NULL) { binder_txn_error("%d:%d cannot allocate work for transaction\n", @@ -3257,9 +3336,6 @@ static void binder_transaction(struct binder_proc *proc, } binder_stats_created(BINDER_STAT_TRANSACTION_COMPLETE); - t->debug_id = t_debug_id; - t->start_time = t_start_time; - if (reply) binder_debug(BINDER_DEBUG_TRANSACTION, "%d:%d BC_REPLY %d -> %d:%d, data size %lld-%lld-%lld\n", @@ -3275,19 +3351,6 @@ static void binder_transaction(struct binder_proc *proc, (u64)tr->data_size, (u64)tr->offsets_size, (u64)extra_buffers_size); - if (!reply && !(tr->flags & TF_ONE_WAY)) - t->from = thread; - else - t->from = NULL; - t->from_pid = proc->pid; - t->from_tid = thread->pid; - t->sender_euid = task_euid(proc->tsk); - t->to_proc = target_proc; - t->to_thread = target_thread; - t->code = tr->code; - t->flags = tr->flags; - t->priority = task_nice(current); - if (target_node && target_node->txn_security_ctx) { u32 secid; size_t added_size; @@ -3680,11 +3743,13 @@ static void binder_transaction(struct binder_proc *proc, return_error_line = __LINE__; goto err_copy_data_failed; } - if (t->buffer->oneway_spam_suspect) + if (t->buffer->oneway_spam_suspect) { tcomplete->type = BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT; - else + binder_netlink_report(proc, t, tr->data_size, + BR_ONEWAY_SPAM_SUSPECT); + } else { tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; - t->work.type = BINDER_WORK_TRANSACTION; + } if (reply) { binder_enqueue_thread_work(thread, tcomplete); @@ -3712,7 +3777,6 @@ static void binder_transaction(struct binder_proc *proc, * the target replies (or there is an error). */ binder_enqueue_deferred_thread_work_ilocked(thread, tcomplete); - t->need_reply = 1; t->from_parent = thread->transaction_stack; thread->transaction_stack = t; binder_inner_proc_unlock(proc); @@ -3733,8 +3797,11 @@ static void binder_transaction(struct binder_proc *proc, * process and is put in a pending queue, waiting for the target * process to be unfrozen. */ - if (return_error == BR_TRANSACTION_PENDING_FROZEN) + if (return_error == BR_TRANSACTION_PENDING_FROZEN) { tcomplete->type = BINDER_WORK_TRANSACTION_PENDING; + binder_netlink_report(proc, t, tr->data_size, + return_error); + } binder_enqueue_thread_work(thread, tcomplete); if (return_error && return_error != BR_TRANSACTION_PENDING_FROZEN) @@ -3783,9 +3850,6 @@ err_get_secctx_failed: err_alloc_tcomplete_failed: if (trace_binder_txn_latency_free_enabled()) binder_txn_latency_free(t); - kfree(t); - binder_stats_deleted(BINDER_STAT_TRANSACTION); -err_alloc_t_failed: err_bad_todo_list: err_bad_call_stack: err_empty_call_stack: @@ -3796,6 +3860,11 @@ err_invalid_target_handle: 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" : @@ -6324,13 +6393,13 @@ static void print_binder_transaction_ilocked(struct seq_file *m, 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 r%d elapsed %lldms", + "%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->need_reply, + t->code, t->flags, t->priority, t->is_async, t->is_reply, ktime_ms_delta(current_time, t->start_time)); spin_unlock(&t->lock); @@ -7062,12 +7131,19 @@ static int __init binder_init(void) } } - ret = init_binderfs(); + 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); @@ -7088,5 +7164,3 @@ device_initcall(binder_init); #define CREATE_TRACE_POINTS #include "binder_trace.h" - -MODULE_LICENSE("GPL v2"); diff --git a/drivers/android/binder/Makefile b/drivers/android/binder/Makefile new file mode 100644 index 000000000000..09eabb527fa0 --- /dev/null +++ b/drivers/android/binder/Makefile @@ -0,0 +1,9 @@ +# 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 := \ + rust_binder_main.o \ + rust_binderfs.o \ + rust_binder_events.o \ + page_range_helper.o diff --git a/drivers/android/binder/allocation.rs b/drivers/android/binder/allocation.rs new file mode 100644 index 000000000000..7f65a9c3a0e5 --- /dev/null +++ b/drivers/android/binder/allocation.rs @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use core::mem::{size_of, size_of_val, MaybeUninit}; +use core::ops::Range; + +use kernel::{ + bindings, + fs::file::{File, FileDescriptorReservation}, + prelude::*, + sync::{aref::ARef, Arc}, + transmute::{AsBytes, FromBytes}, + uaccess::UserSliceReader, + uapi, +}; + +use crate::{ + deferred_close::DeferredFdCloser, + defs::*, + node::{Node, NodeRef}, + process::Process, + DArc, +}; + +#[derive(Default)] +pub(crate) struct AllocationInfo { + /// Range within the allocation where we can find the offsets to the object descriptors. + pub(crate) offsets: Option<Range<usize>>, + /// The target node of the transaction this allocation is associated to. + /// Not set for replies. + pub(crate) target_node: Option<NodeRef>, + /// When this allocation is dropped, call `pending_oneway_finished` on the node. + /// + /// This is used to serialize oneway transaction on the same node. Binder guarantees that + /// oneway transactions to the same node are delivered sequentially in the order they are sent. + pub(crate) oneway_node: Option<DArc<Node>>, + /// Zero the data in the buffer on free. + pub(crate) clear_on_free: bool, + /// List of files embedded in this transaction. + file_list: FileList, +} + +/// Represents an allocation that the kernel is currently using. +/// +/// When allocations are idle, the range allocator holds the data related to them. +/// +/// # Invariants +/// +/// This allocation corresponds to an allocation in the range allocator, so the relevant pages are +/// marked in use in the page range. +pub(crate) struct Allocation { + pub(crate) offset: usize, + size: usize, + pub(crate) ptr: usize, + pub(crate) process: Arc<Process>, + allocation_info: Option<AllocationInfo>, + free_on_drop: bool, + pub(crate) oneway_spam_detected: bool, + #[allow(dead_code)] + pub(crate) debug_id: usize, +} + +impl Allocation { + pub(crate) fn new( + process: Arc<Process>, + debug_id: usize, + offset: usize, + size: usize, + ptr: usize, + oneway_spam_detected: bool, + ) -> Self { + Self { + process, + offset, + size, + ptr, + debug_id, + oneway_spam_detected, + allocation_info: None, + free_on_drop: true, + } + } + + fn size_check(&self, offset: usize, size: usize) -> Result { + let overflow_fail = offset.checked_add(size).is_none(); + let cmp_size_fail = offset.wrapping_add(size) > self.size; + if overflow_fail || cmp_size_fail { + return Err(EFAULT); + } + Ok(()) + } + + pub(crate) fn copy_into( + &self, + reader: &mut UserSliceReader, + offset: usize, + size: usize, + ) -> Result { + self.size_check(offset, size)?; + + // SAFETY: While this object exists, the range allocator will keep the range allocated, and + // in turn, the pages will be marked as in use. + unsafe { + self.process + .pages + .copy_from_user_slice(reader, self.offset + offset, size) + } + } + + pub(crate) fn read<T: FromBytes>(&self, offset: usize) -> Result<T> { + self.size_check(offset, size_of::<T>())?; + + // SAFETY: While this object exists, the range allocator will keep the range allocated, and + // in turn, the pages will be marked as in use. + unsafe { self.process.pages.read(self.offset + offset) } + } + + pub(crate) fn write<T: ?Sized>(&self, offset: usize, obj: &T) -> Result { + self.size_check(offset, size_of_val::<T>(obj))?; + + // SAFETY: While this object exists, the range allocator will keep the range allocated, and + // in turn, the pages will be marked as in use. + unsafe { self.process.pages.write(self.offset + offset, obj) } + } + + pub(crate) fn fill_zero(&self) -> Result { + // SAFETY: While this object exists, the range allocator will keep the range allocated, and + // in turn, the pages will be marked as in use. + unsafe { self.process.pages.fill_zero(self.offset, self.size) } + } + + pub(crate) fn keep_alive(mut self) { + self.process + .buffer_make_freeable(self.offset, self.allocation_info.take()); + self.free_on_drop = false; + } + + pub(crate) fn set_info(&mut self, info: AllocationInfo) { + self.allocation_info = Some(info); + } + + pub(crate) fn get_or_init_info(&mut self) -> &mut AllocationInfo { + self.allocation_info.get_or_insert_with(Default::default) + } + + pub(crate) fn set_info_offsets(&mut self, offsets: Range<usize>) { + self.get_or_init_info().offsets = Some(offsets); + } + + pub(crate) fn set_info_oneway_node(&mut self, oneway_node: DArc<Node>) { + self.get_or_init_info().oneway_node = Some(oneway_node); + } + + pub(crate) fn set_info_clear_on_drop(&mut self) { + self.get_or_init_info().clear_on_free = true; + } + + pub(crate) fn set_info_target_node(&mut self, target_node: NodeRef) { + self.get_or_init_info().target_node = Some(target_node); + } + + /// Reserve enough space to push at least `num_fds` fds. + pub(crate) fn info_add_fd_reserve(&mut self, num_fds: usize) -> Result { + self.get_or_init_info() + .file_list + .files_to_translate + .reserve(num_fds, GFP_KERNEL)?; + + Ok(()) + } + + pub(crate) fn info_add_fd( + &mut self, + file: ARef<File>, + buffer_offset: usize, + close_on_free: bool, + ) -> Result { + self.get_or_init_info().file_list.files_to_translate.push( + FileEntry { + file, + buffer_offset, + close_on_free, + }, + GFP_KERNEL, + )?; + + Ok(()) + } + + pub(crate) fn set_info_close_on_free(&mut self, cof: FdsCloseOnFree) { + self.get_or_init_info().file_list.close_on_free = cof.0; + } + + pub(crate) fn translate_fds(&mut self) -> Result<TranslatedFds> { + let file_list = match self.allocation_info.as_mut() { + Some(info) => &mut info.file_list, + None => return Ok(TranslatedFds::new()), + }; + + let files = core::mem::take(&mut file_list.files_to_translate); + + 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)?; + for file_info in files { + let res = FileDescriptorReservation::get_unused_fd_flags(bindings::O_CLOEXEC)?; + let fd = res.reserved_fd(); + self.write::<u32>(file_info.buffer_offset, &fd)?; + + reservations.push( + Reservation { + res, + file: file_info.file, + }, + GFP_KERNEL, + )?; + if file_info.close_on_free { + close_on_free.push(fd, GFP_KERNEL)?; + } + } + + Ok(TranslatedFds { + reservations, + close_on_free: FdsCloseOnFree(close_on_free), + }) + } + + /// Should the looper return to userspace when freeing this allocation? + pub(crate) fn looper_need_return_on_free(&self) -> bool { + // Closing fds involves pushing task_work for execution when we return to userspace. Hence, + // we should return to userspace asap if we are closing fds. + match self.allocation_info { + Some(ref info) => !info.file_list.close_on_free.is_empty(), + None => false, + } + } +} + +impl Drop for Allocation { + fn drop(&mut self) { + if !self.free_on_drop { + return; + } + + if let Some(mut info) = self.allocation_info.take() { + if let Some(oneway_node) = info.oneway_node.as_ref() { + oneway_node.pending_oneway_finished(); + } + + info.target_node = None; + + if let Some(offsets) = info.offsets.clone() { + let view = AllocationView::new(self, offsets.start); + for i in offsets.step_by(size_of::<usize>()) { + if view.cleanup_object(i).is_err() { + pr_warn!("Error cleaning up object at offset {}\n", i) + } + } + } + + for &fd in &info.file_list.close_on_free { + let closer = match DeferredFdCloser::new(GFP_KERNEL) { + Ok(closer) => closer, + Err(kernel::alloc::AllocError) => { + // Ignore allocation failures. + break; + } + }; + + // Here, we ignore errors. The operation can fail if the fd is not valid, or if the + // method is called from a kthread. However, this is always called from a syscall, + // so the latter case cannot happen, and we don't care about the first case. + let _ = closer.close_fd(fd); + } + + if info.clear_on_free { + if let Err(e) = self.fill_zero() { + pr_warn!("Failed to clear data on free: {:?}", e); + } + } + } + + self.process.buffer_raw_free(self.ptr); + } +} + +/// A wrapper around `Allocation` that is being created. +/// +/// If the allocation is destroyed while wrapped in this wrapper, then the allocation will be +/// considered to be part of a failed transaction. Successful transactions avoid that by calling +/// `success`, which skips the destructor. +#[repr(transparent)] +pub(crate) struct NewAllocation(pub(crate) Allocation); + +impl NewAllocation { + pub(crate) fn success(self) -> Allocation { + // This skips the destructor. + // + // SAFETY: This type is `#[repr(transparent)]`, so the layout matches. + unsafe { core::mem::transmute(self) } + } +} + +impl core::ops::Deref for NewAllocation { + type Target = Allocation; + fn deref(&self) -> &Allocation { + &self.0 + } +} + +impl core::ops::DerefMut for NewAllocation { + fn deref_mut(&mut self) -> &mut Allocation { + &mut self.0 + } +} + +/// 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 +/// this view, use the `alloc` field of this struct directly. +pub(crate) struct AllocationView<'a> { + pub(crate) alloc: &'a mut Allocation, + limit: usize, +} + +impl<'a> AllocationView<'a> { + pub(crate) fn new(alloc: &'a mut Allocation, limit: usize) -> Self { + AllocationView { alloc, limit } + } + + pub(crate) fn read<T: FromBytes>(&self, offset: usize) -> Result<T> { + if offset.checked_add(size_of::<T>()).ok_or(EINVAL)? > self.limit { + return Err(EINVAL); + } + self.alloc.read(offset) + } + + pub(crate) fn write<T: AsBytes>(&self, offset: usize, obj: &T) -> Result { + if offset.checked_add(size_of::<T>()).ok_or(EINVAL)? > self.limit { + return Err(EINVAL); + } + self.alloc.write(offset, obj) + } + + pub(crate) fn copy_into( + &self, + reader: &mut UserSliceReader, + offset: usize, + size: usize, + ) -> Result { + if offset.checked_add(size).ok_or(EINVAL)? > self.limit { + return Err(EINVAL); + } + self.alloc.copy_into(reader, offset, size) + } + + pub(crate) fn transfer_binder_object( + &self, + offset: usize, + obj: &uapi::flat_binder_object, + strong: bool, + node_ref: NodeRef, + ) -> Result { + let mut newobj = FlatBinderObject::default(); + let node = node_ref.node.clone(); + if Arc::ptr_eq(&node_ref.node.owner, &self.alloc.process) { + // The receiving process is the owner of the node, so send it a binder object (instead + // of a handle). + let (ptr, cookie) = node.get_id(); + newobj.hdr.type_ = if strong { + BINDER_TYPE_BINDER + } else { + BINDER_TYPE_WEAK_BINDER + }; + newobj.flags = obj.flags; + newobj.__bindgen_anon_1.binder = ptr as _; + newobj.cookie = cookie as _; + self.write(offset, &newobj)?; + // Increment the user ref count on the node. It will be decremented as part of the + // destruction of the buffer, when we see a binder or weak-binder object. + node.update_refcount(true, 1, strong); + } else { + // The receiving process is different from the owner, so we need to insert a handle to + // the binder object. + let handle = self + .alloc + .process + .as_arc_borrow() + .insert_or_update_handle(node_ref, false)?; + newobj.hdr.type_ = if strong { + BINDER_TYPE_HANDLE + } else { + BINDER_TYPE_WEAK_HANDLE + }; + newobj.flags = obj.flags; + newobj.__bindgen_anon_1.handle = handle; + if self.write(offset, &newobj).is_err() { + // Decrement ref count on the handle we just created. + let _ = self + .alloc + .process + .as_arc_borrow() + .update_ref(handle, false, strong); + return Err(EINVAL); + } + } + + Ok(()) + } + + fn cleanup_object(&self, index_offset: usize) -> Result { + let offset = self.alloc.read(index_offset)?; + let header = self.read::<BinderObjectHeader>(offset)?; + match header.type_ { + BINDER_TYPE_WEAK_BINDER | BINDER_TYPE_BINDER => { + let obj = self.read::<FlatBinderObject>(offset)?; + let strong = header.type_ == BINDER_TYPE_BINDER; + // SAFETY: The type is `BINDER_TYPE_{WEAK_}BINDER`, so the `binder` field is + // populated. + let ptr = unsafe { obj.__bindgen_anon_1.binder }; + let cookie = obj.cookie; + self.alloc.process.update_node(ptr, cookie, strong); + Ok(()) + } + BINDER_TYPE_WEAK_HANDLE | BINDER_TYPE_HANDLE => { + let obj = self.read::<FlatBinderObject>(offset)?; + let strong = header.type_ == BINDER_TYPE_HANDLE; + // SAFETY: The type is `BINDER_TYPE_{WEAK_}HANDLE`, so the `handle` field is + // populated. + let handle = unsafe { obj.__bindgen_anon_1.handle }; + self.alloc + .process + .as_arc_borrow() + .update_ref(handle, false, strong) + } + _ => Ok(()), + } + } +} + +/// A binder object as it is serialized. +/// +/// # Invariants +/// +/// All bytes must be initialized, and the value of `self.hdr.type_` must be one of the allowed +/// types. +#[repr(C)] +pub(crate) union BinderObject { + hdr: uapi::binder_object_header, + fbo: uapi::flat_binder_object, + fdo: uapi::binder_fd_object, + bbo: uapi::binder_buffer_object, + fdao: uapi::binder_fd_array_object, +} + +/// A view into a `BinderObject` that can be used in a match statement. +pub(crate) enum BinderObjectRef<'a> { + Binder(&'a mut uapi::flat_binder_object), + Handle(&'a mut uapi::flat_binder_object), + Fd(&'a mut uapi::binder_fd_object), + Ptr(&'a mut uapi::binder_buffer_object), + Fda(&'a mut uapi::binder_fd_array_object), +} + +impl BinderObject { + pub(crate) fn read_from(reader: &mut UserSliceReader) -> Result<BinderObject> { + let object = Self::read_from_inner(|slice| { + let read_len = usize::min(slice.len(), reader.len()); + reader.clone_reader().read_slice(&mut slice[..read_len])?; + Ok(()) + })?; + + // If we used a object type smaller than the largest object size, then we've read more + // bytes than we needed to. However, we used `.clone_reader()` to avoid advancing the + // original reader. Now, we call `skip` so that the caller's reader is advanced by the + // right amount. + // + // The `skip` call fails if the reader doesn't have `size` bytes available. This could + // happen if the type header corresponds to an object type that is larger than the rest of + // the reader. + // + // Any extra bytes beyond the size of the object are inaccessible after this call, so + // reading them again from the `reader` later does not result in TOCTOU bugs. + reader.skip(object.size())?; + + Ok(object) + } + + /// Use the provided reader closure to construct a `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<()>, + { + let mut obj = MaybeUninit::<BinderObject>::zeroed(); + + // SAFETY: The lengths of `BinderObject` and `[u8; size_of::<BinderObject>()]` are equal, + // and the byte array has an alignment requirement of one, so the pointer cast is okay. + // Additionally, `obj` was initialized to zeros, so the byte array will not be + // uninitialized. + (reader)(unsafe { &mut *obj.as_mut_ptr().cast() })?; + + // SAFETY: The entire object is initialized, so accessing this field is safe. + let type_ = unsafe { obj.assume_init_ref().hdr.type_ }; + if Self::type_to_size(type_).is_none() { + // The value of `obj.hdr_type_` was invalid. + return Err(EINVAL); + } + + // SAFETY: All bytes are initialized (since we zeroed them at the start) and we checked + // that `self.hdr.type_` is one of the allowed types, so the type invariants are satisfied. + unsafe { Ok(obj.assume_init()) } + } + + pub(crate) fn as_ref(&mut self) -> BinderObjectRef<'_> { + use BinderObjectRef::*; + // SAFETY: The constructor ensures that all bytes of `self` are initialized, and all + // variants of this union accept all initialized bit patterns. + unsafe { + match self.hdr.type_ { + BINDER_TYPE_WEAK_BINDER | BINDER_TYPE_BINDER => Binder(&mut self.fbo), + BINDER_TYPE_WEAK_HANDLE | BINDER_TYPE_HANDLE => Handle(&mut self.fbo), + BINDER_TYPE_FD => Fd(&mut self.fdo), + BINDER_TYPE_PTR => Ptr(&mut self.bbo), + BINDER_TYPE_FDA => Fda(&mut self.fdao), + // SAFETY: By the type invariant, the value of `self.hdr.type_` cannot have any + // other value than the ones checked above. + _ => core::hint::unreachable_unchecked(), + } + } + } + + pub(crate) fn size(&self) -> usize { + // SAFETY: The entire object is initialized, so accessing this field is safe. + let type_ = unsafe { self.hdr.type_ }; + + // SAFETY: The type invariants guarantee that the type field is correct. + unsafe { Self::type_to_size(type_).unwrap_unchecked() } + } + + fn type_to_size(type_: u32) -> Option<usize> { + match type_ { + BINDER_TYPE_WEAK_BINDER => Some(size_of::<uapi::flat_binder_object>()), + BINDER_TYPE_BINDER => Some(size_of::<uapi::flat_binder_object>()), + BINDER_TYPE_WEAK_HANDLE => Some(size_of::<uapi::flat_binder_object>()), + BINDER_TYPE_HANDLE => Some(size_of::<uapi::flat_binder_object>()), + BINDER_TYPE_FD => Some(size_of::<uapi::binder_fd_object>()), + BINDER_TYPE_PTR => Some(size_of::<uapi::binder_buffer_object>()), + BINDER_TYPE_FDA => Some(size_of::<uapi::binder_fd_array_object>()), + _ => None, + } + } +} + +#[derive(Default)] +struct FileList { + files_to_translate: KVec<FileEntry>, + close_on_free: KVec<u32>, +} + +struct FileEntry { + /// The file for which a descriptor will be created in the recipient process. + file: ARef<File>, + /// The offset in the buffer where the file descriptor is stored. + buffer_offset: usize, + /// Whether this fd should be closed when the allocation is freed. + close_on_free: bool, +} + +pub(crate) struct TranslatedFds { + reservations: KVec<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, +} + +struct Reservation { + res: FileDescriptorReservation, + file: ARef<File>, +} + +impl TranslatedFds { + pub(crate) fn new() -> Self { + Self { + reservations: KVec::new(), + close_on_free: FdsCloseOnFree(KVec::new()), + } + } + + pub(crate) fn commit(self) -> FdsCloseOnFree { + for entry in self.reservations { + entry.res.fd_install(entry.file); + } + + self.close_on_free + } +} + +pub(crate) struct FdsCloseOnFree(KVec<u32>); diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs new file mode 100644 index 000000000000..3d135ec03ca7 --- /dev/null +++ b/drivers/android/binder/context.rs @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{ + error::Error, + list::{List, ListArc, ListLinks}, + prelude::*, + security, + str::{CStr, CString}, + sync::{Arc, Mutex}, + task::Kuid, +}; + +use crate::{error::BinderError, node::NodeRef, process::Process}; + +kernel::sync::global_lock! { + // SAFETY: We call `init` in the module initializer, so it's initialized before first use. + pub(crate) unsafe(uninit) static CONTEXTS: Mutex<ContextList> = ContextList { + list: List::new(), + }; +} + +pub(crate) struct ContextList { + list: List<Context>, +} + +pub(crate) fn get_all_contexts() -> Result<KVec<Arc<Context>>> { + let lock = CONTEXTS.lock(); + + let count = lock.list.iter().count(); + + let mut ctxs = KVec::with_capacity(count, GFP_KERNEL)?; + for ctx in &lock.list { + ctxs.push(Arc::from(ctx), GFP_KERNEL)?; + } + Ok(ctxs) +} + +/// This struct keeps track of the processes using this context, and which process is the context +/// manager. +struct Manager { + node: Option<NodeRef>, + uid: Option<Kuid>, + all_procs: List<Process>, +} + +/// There is one context per binder file (/dev/binder, /dev/hwbinder, etc) +#[pin_data] +pub(crate) struct Context { + #[pin] + manager: Mutex<Manager>, + pub(crate) name: CString, + #[pin] + links: ListLinks, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for Context { untracked; } +} +kernel::list::impl_list_item! { + impl ListItem<0> for Context { + using ListLinks { self.links }; + } +} + +impl Context { + pub(crate) fn new(name: &CStr) -> Result<Arc<Self>> { + let name = CString::try_from(name)?; + let list_ctx = ListArc::pin_init::<Error>( + try_pin_init!(Context { + name, + links <- ListLinks::new(), + manager <- kernel::new_mutex!(Manager { + all_procs: List::new(), + node: None, + uid: None, + }, "Context::manager"), + }), + GFP_KERNEL, + )?; + + let ctx = list_ctx.clone_arc(); + CONTEXTS.lock().list.push_back(list_ctx); + + Ok(ctx) + } + + /// Called when the file for this context is unlinked. + /// + /// No-op if called twice. + pub(crate) fn deregister(&self) { + // SAFETY: We never add the context to any other linked list than this one, so it is either + // in this list, or not in any list. + unsafe { CONTEXTS.lock().list.remove(self) }; + } + + pub(crate) fn register_process(self: &Arc<Self>, proc: ListArc<Process>) { + if !Arc::ptr_eq(self, &proc.ctx) { + pr_err!("Context::register_process called on the wrong context."); + return; + } + self.manager.lock().all_procs.push_back(proc); + } + + pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Process) { + if !Arc::ptr_eq(self, &proc.ctx) { + pr_err!("Context::deregister_process called on the wrong context."); + return; + } + // SAFETY: We just checked that this is the right list. + unsafe { self.manager.lock().all_procs.remove(proc) }; + } + + pub(crate) fn set_manager_node(&self, node_ref: NodeRef) -> Result { + let mut manager = self.manager.lock(); + if manager.node.is_some() { + pr_warn!("BINDER_SET_CONTEXT_MGR already set"); + return Err(EBUSY); + } + security::binder_set_context_mgr(&node_ref.node.owner.cred)?; + + // If the context manager has been set before, ensure that we use the same euid. + let caller_uid = Kuid::current_euid(); + if let Some(ref uid) = manager.uid { + if *uid != caller_uid { + return Err(EPERM); + } + } + + manager.node = Some(node_ref); + manager.uid = Some(caller_uid); + Ok(()) + } + + pub(crate) fn unset_manager_node(&self) { + let node_ref = self.manager.lock().node.take(); + drop(node_ref); + } + + pub(crate) fn get_manager_node(&self, strong: bool) -> Result<NodeRef, BinderError> { + self.manager + .lock() + .node + .as_ref() + .ok_or_else(BinderError::new_dead)? + .clone(strong) + .map_err(BinderError::from) + } + + pub(crate) fn for_each_proc<F>(&self, mut func: F) + where + F: FnMut(&Process), + { + let lock = self.manager.lock(); + for proc in &lock.all_procs { + func(&proc); + } + } + + pub(crate) fn get_all_procs(&self) -> Result<KVec<Arc<Process>>> { + let lock = self.manager.lock(); + let count = lock.all_procs.iter().count(); + + let mut procs = KVec::with_capacity(count, GFP_KERNEL)?; + for proc in &lock.all_procs { + procs.push(Arc::from(proc), GFP_KERNEL)?; + } + Ok(procs) + } + + pub(crate) fn get_procs_with_pid(&self, pid: i32) -> Result<KVec<Arc<Process>>> { + let orig = self.get_all_procs()?; + let mut backing = KVec::with_capacity(orig.len(), GFP_KERNEL)?; + for proc in orig.into_iter().filter(|proc| proc.task.pid() == pid) { + backing.push(proc, GFP_KERNEL)?; + } + Ok(backing) + } +} diff --git a/drivers/android/binder/deferred_close.rs b/drivers/android/binder/deferred_close.rs new file mode 100644 index 000000000000..ac895c04d0cb --- /dev/null +++ b/drivers/android/binder/deferred_close.rs @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! Logic for closing files in a deferred manner. +//! +//! This file could make sense to have in `kernel::fs`, but it was rejected for being too +//! Binder-specific. + +use core::mem::MaybeUninit; +use kernel::{ + alloc::{AllocError, Flags}, + bindings, + prelude::*, +}; + +/// Helper used for closing file descriptors in a way that is safe even if the file is currently +/// held using `fdget`. +/// +/// Additional motivation can be found in commit 80cd795630d6 ("binder: fix use-after-free due to +/// ksys_close() during fdget()") and in the comments on `binder_do_fd_close`. +pub(crate) struct DeferredFdCloser { + inner: KBox<DeferredFdCloserInner>, +} + +/// SAFETY: This just holds an allocation with no real content, so there's no safety issue with +/// moving it across threads. +unsafe impl Send for DeferredFdCloser {} +/// SAFETY: This just holds an allocation with no real content, so there's no safety issue with +/// moving it across threads. +unsafe impl Sync for DeferredFdCloser {} + +/// # Invariants +/// +/// If the `file` pointer is non-null, then it points at a `struct file` and owns a refcount to +/// that file. +#[repr(C)] +struct DeferredFdCloserInner { + twork: MaybeUninit<bindings::callback_head>, + file: *mut bindings::file, +} + +impl DeferredFdCloser { + /// Create a new [`DeferredFdCloser`]. + pub(crate) fn new(flags: Flags) -> Result<Self, AllocError> { + Ok(Self { + // INVARIANT: The `file` pointer is null, so the type invariant does not apply. + inner: KBox::new( + DeferredFdCloserInner { + twork: MaybeUninit::uninit(), + file: core::ptr::null_mut(), + }, + flags, + )?, + }) + } + + /// Schedule a task work that closes the file descriptor when this task returns to userspace. + /// + /// Fails if this is called from a context where we cannot run work when returning to + /// userspace. (E.g., from a kthread.) + pub(crate) fn close_fd(self, fd: u32) -> Result<(), DeferredFdCloseError> { + use bindings::task_work_notify_mode_TWA_RESUME as TWA_RESUME; + + // In this method, we schedule the task work before closing the file. This is because + // scheduling a task work is fallible, and we need to know whether it will fail before we + // attempt to close the file. + + // Task works are not available on kthreads. + let current = kernel::current!(); + + // Check if this is a kthread. + // SAFETY: Reading `flags` from a task is always okay. + if unsafe { ((*current.as_ptr()).flags & bindings::PF_KTHREAD) != 0 } { + return Err(DeferredFdCloseError::TaskWorkUnavailable); + } + + // Transfer ownership of the box's allocation to a raw pointer. This disables the + // destructor, so we must manually convert it back to a KBox to drop it. + // + // Until we convert it back to a `KBox`, there are no aliasing requirements on this + // pointer. + let inner = KBox::into_raw(self.inner); + + // The `callback_head` field is first in the struct, so this cast correctly gives us a + // pointer to the field. + let callback_head = inner.cast::<bindings::callback_head>(); + // SAFETY: This pointer offset operation does not go out-of-bounds. + let file_field = unsafe { core::ptr::addr_of_mut!((*inner).file) }; + + let current = current.as_ptr(); + + // SAFETY: This function currently has exclusive access to the `DeferredFdCloserInner`, so + // it is okay for us to perform unsynchronized writes to its `callback_head` field. + unsafe { bindings::init_task_work(callback_head, Some(Self::do_close_fd)) }; + + // SAFETY: This inserts the `DeferredFdCloserInner` into the task workqueue for the current + // task. If this operation is successful, then this transfers exclusive ownership of the + // `callback_head` field to the C side until it calls `do_close_fd`, and we don't touch or + // invalidate the field during that time. + // + // When the C side calls `do_close_fd`, the safety requirements of that method are + // satisfied because when a task work is executed, the callback is given ownership of the + // pointer. + // + // The file pointer is currently null. If it is changed to be non-null before `do_close_fd` + // is called, then that change happens due to the write at the end of this function, and + // that write has a safety comment that explains why the refcount can be dropped when + // `do_close_fd` runs. + let res = unsafe { bindings::task_work_add(current, callback_head, TWA_RESUME) }; + + if res != 0 { + // SAFETY: Scheduling the task work failed, so we still have ownership of the box, so + // we may destroy it. + unsafe { drop(KBox::from_raw(inner)) }; + + return Err(DeferredFdCloseError::TaskWorkUnavailable); + } + + // This removes the fd from the fd table in `current`. The file is not fully closed until + // `filp_close` is called. We are given ownership of one refcount to the file. + // + // SAFETY: This is safe no matter what `fd` is. If the `fd` is valid (that is, if the + // pointer is non-null), then we call `filp_close` on the returned pointer as required by + // `file_close_fd`. + let file = unsafe { bindings::file_close_fd(fd) }; + if file.is_null() { + // We don't clean up the task work since that might be expensive if the task work queue + // is long. Just let it execute and let it clean up for itself. + return Err(DeferredFdCloseError::BadFd); + } + + // Acquire a second refcount to the file. + // + // SAFETY: The `file` pointer points at a file with a non-zero refcount. + unsafe { bindings::get_file(file) }; + + // This method closes the fd, consuming one of our two refcounts. There could be active + // light refcounts created from that fd, so we must ensure that the file has a positive + // refcount for the duration of those active light refcounts. We do that by holding on to + // the second refcount until the current task returns to userspace. + // + // SAFETY: The `file` pointer is valid. Passing `current->files` as the file table to close + // it in is correct, since we just got the `fd` from `file_close_fd` which also uses + // `current->files`. + // + // Note: fl_owner_t is currently a void pointer. + unsafe { bindings::filp_close(file, (*current).files as bindings::fl_owner_t) }; + + // We update the file pointer that the task work is supposed to fput. This transfers + // ownership of our last refcount. + // + // INVARIANT: This changes the `file` field of a `DeferredFdCloserInner` from null to + // non-null. This doesn't break the type invariant for `DeferredFdCloserInner` because we + // still own a refcount to the file, so we can pass ownership of that refcount to the + // `DeferredFdCloserInner`. + // + // When `do_close_fd` runs, it must be safe for it to `fput` the refcount. However, this is + // the case because all light refcounts that are associated with the fd we closed + // previously must be dropped when `do_close_fd`, since light refcounts must be dropped + // before returning to userspace. + // + // SAFETY: Task works are executed on the current thread right before we return to + // userspace, so this write is guaranteed to happen before `do_close_fd` is called, which + // means that a race is not possible here. + unsafe { *file_field = file }; + + Ok(()) + } + + /// # Safety + /// + /// The provided pointer must point at the `twork` field of a `DeferredFdCloserInner` stored in + /// a `KBox`, and the caller must pass exclusive ownership of that `KBox`. Furthermore, if the + /// file pointer is non-null, then it must be okay to release the refcount by calling `fput`. + unsafe extern "C" fn do_close_fd(inner: *mut bindings::callback_head) { + // SAFETY: The caller just passed us ownership of this box. + let inner = unsafe { KBox::from_raw(inner.cast::<DeferredFdCloserInner>()) }; + if !inner.file.is_null() { + // SAFETY: By the type invariants, we own a refcount to this file, and the caller + // guarantees that dropping the refcount now is okay. + unsafe { bindings::fput(inner.file) }; + } + // The allocation is freed when `inner` goes out of scope. + } +} + +/// Represents a failure to close an fd in a deferred manner. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum DeferredFdCloseError { + /// Closing the fd failed because we were unable to schedule a task work. + TaskWorkUnavailable, + /// Closing the fd failed because the fd does not exist. + BadFd, +} + +impl From<DeferredFdCloseError> for Error { + fn from(err: DeferredFdCloseError) -> Error { + match err { + DeferredFdCloseError::TaskWorkUnavailable => ESRCH, + DeferredFdCloseError::BadFd => EBADF, + } + } +} diff --git a/drivers/android/binder/defs.rs b/drivers/android/binder/defs.rs new file mode 100644 index 000000000000..33f51b4139c7 --- /dev/null +++ b/drivers/android/binder/defs.rs @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; +use kernel::{ + transmute::{AsBytes, FromBytes}, + uapi::{self, *}, +}; + +macro_rules! pub_no_prefix { + ($prefix:ident, $($newname:ident),+ $(,)?) => { + $(pub(crate) const $newname: u32 = kernel::macros::concat_idents!($prefix, $newname);)+ + }; +} + +pub_no_prefix!( + binder_driver_return_protocol_, + BR_TRANSACTION, + BR_TRANSACTION_SEC_CTX, + BR_REPLY, + BR_DEAD_REPLY, + BR_FAILED_REPLY, + BR_FROZEN_REPLY, + BR_NOOP, + BR_SPAWN_LOOPER, + BR_TRANSACTION_COMPLETE, + BR_TRANSACTION_PENDING_FROZEN, + BR_ONEWAY_SPAM_SUSPECT, + BR_OK, + BR_ERROR, + BR_INCREFS, + BR_ACQUIRE, + BR_RELEASE, + BR_DECREFS, + BR_DEAD_BINDER, + BR_CLEAR_DEATH_NOTIFICATION_DONE, + BR_FROZEN_BINDER, + BR_CLEAR_FREEZE_NOTIFICATION_DONE, +); + +pub_no_prefix!( + binder_driver_command_protocol_, + BC_TRANSACTION, + BC_TRANSACTION_SG, + BC_REPLY, + BC_REPLY_SG, + BC_FREE_BUFFER, + BC_ENTER_LOOPER, + BC_EXIT_LOOPER, + BC_REGISTER_LOOPER, + BC_INCREFS, + BC_ACQUIRE, + BC_RELEASE, + BC_DECREFS, + BC_INCREFS_DONE, + BC_ACQUIRE_DONE, + BC_REQUEST_DEATH_NOTIFICATION, + BC_CLEAR_DEATH_NOTIFICATION, + BC_DEAD_BINDER_DONE, + BC_REQUEST_FREEZE_NOTIFICATION, + BC_CLEAR_FREEZE_NOTIFICATION, + BC_FREEZE_NOTIFICATION_DONE, +); + +pub_no_prefix!( + flat_binder_object_flags_, + FLAT_BINDER_FLAG_ACCEPTS_FDS, + FLAT_BINDER_FLAG_TXN_SECURITY_CTX +); + +pub_no_prefix!( + transaction_flags_, + TF_ONE_WAY, + TF_ACCEPT_FDS, + TF_CLEAR_BUF, + TF_UPDATE_TXN +); + +pub(crate) use uapi::{ + BINDER_TYPE_BINDER, BINDER_TYPE_FD, BINDER_TYPE_FDA, BINDER_TYPE_HANDLE, BINDER_TYPE_PTR, + BINDER_TYPE_WEAK_BINDER, BINDER_TYPE_WEAK_HANDLE, +}; + +macro_rules! decl_wrapper { + ($newname:ident, $wrapped:ty) => { + // Define a wrapper around the C type. Use `MaybeUninit` to enforce that the value of + // padding bytes must be preserved. + #[derive(Copy, Clone)] + #[repr(transparent)] + pub(crate) struct $newname(MaybeUninit<$wrapped>); + + // SAFETY: This macro is only used with types where this is ok. + unsafe impl FromBytes for $newname {} + // SAFETY: This macro is only used with types where this is ok. + unsafe impl AsBytes for $newname {} + + impl Deref for $newname { + type Target = $wrapped; + fn deref(&self) -> &Self::Target { + // SAFETY: We use `MaybeUninit` only to preserve padding. The value must still + // always be valid. + unsafe { self.0.assume_init_ref() } + } + } + + impl DerefMut for $newname { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: We use `MaybeUninit` only to preserve padding. The value must still + // always be valid. + unsafe { self.0.assume_init_mut() } + } + } + + impl Default for $newname { + fn default() -> Self { + // Create a new value of this type where all bytes (including padding) are zeroed. + Self(MaybeUninit::zeroed()) + } + } + }; +} + +decl_wrapper!(BinderNodeDebugInfo, uapi::binder_node_debug_info); +decl_wrapper!(BinderNodeInfoForRef, uapi::binder_node_info_for_ref); +decl_wrapper!(FlatBinderObject, uapi::flat_binder_object); +decl_wrapper!(BinderFdObject, uapi::binder_fd_object); +decl_wrapper!(BinderFdArrayObject, uapi::binder_fd_array_object); +decl_wrapper!(BinderObjectHeader, uapi::binder_object_header); +decl_wrapper!(BinderBufferObject, uapi::binder_buffer_object); +decl_wrapper!(BinderTransactionData, uapi::binder_transaction_data); +decl_wrapper!( + BinderTransactionDataSecctx, + uapi::binder_transaction_data_secctx +); +decl_wrapper!(BinderTransactionDataSg, uapi::binder_transaction_data_sg); +decl_wrapper!(BinderWriteRead, uapi::binder_write_read); +decl_wrapper!(BinderVersion, uapi::binder_version); +decl_wrapper!(BinderFrozenStatusInfo, uapi::binder_frozen_status_info); +decl_wrapper!(BinderFreezeInfo, uapi::binder_freeze_info); +decl_wrapper!(BinderFrozenStateInfo, uapi::binder_frozen_state_info); +decl_wrapper!(BinderHandleCookie, uapi::binder_handle_cookie); +decl_wrapper!(ExtendedError, uapi::binder_extended_error); + +impl BinderVersion { + pub(crate) fn current() -> Self { + Self(MaybeUninit::new(uapi::binder_version { + protocol_version: BINDER_CURRENT_PROTOCOL_VERSION as _, + })) + } +} + +impl BinderTransactionData { + pub(crate) fn with_buffers_size(self, buffers_size: u64) -> BinderTransactionDataSg { + BinderTransactionDataSg(MaybeUninit::new(uapi::binder_transaction_data_sg { + transaction_data: *self, + buffers_size, + })) + } +} + +impl BinderTransactionDataSecctx { + /// View the inner data as wrapped in `BinderTransactionData`. + pub(crate) fn tr_data(&mut self) -> &mut BinderTransactionData { + // SAFETY: Transparent wrapper is safe to transmute. + unsafe { + &mut *(&mut self.transaction_data as *mut uapi::binder_transaction_data + as *mut BinderTransactionData) + } + } +} + +impl ExtendedError { + pub(crate) fn new(id: u32, command: u32, param: i32) -> Self { + Self(MaybeUninit::new(uapi::binder_extended_error { + id, + command, + param, + })) + } +} diff --git a/drivers/android/binder/error.rs b/drivers/android/binder/error.rs new file mode 100644 index 000000000000..9921827267d0 --- /dev/null +++ b/drivers/android/binder/error.rs @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::prelude::*; + +use crate::defs::*; + +pub(crate) type BinderResult<T = ()> = core::result::Result<T, BinderError>; + +/// 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, + source: Option<Error>, +} + +impl BinderError { + pub(crate) fn new_dead() -> Self { + Self { + reply: BR_DEAD_REPLY, + source: None, + } + } + + pub(crate) fn new_frozen() -> Self { + Self { + reply: BR_FROZEN_REPLY, + source: None, + } + } + + pub(crate) fn new_frozen_oneway() -> Self { + Self { + reply: BR_TRANSACTION_PENDING_FROZEN, + source: None, + } + } + + pub(crate) fn is_dead(&self) -> bool { + self.reply == BR_DEAD_REPLY + } + + pub(crate) fn as_errno(&self) -> kernel::ffi::c_int { + self.source.unwrap_or(EINVAL).to_errno() + } + + pub(crate) fn should_pr_warn(&self) -> bool { + self.source.is_some() + } +} + +/// 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 { + fn from(source: Error) -> Self { + Self { + reply: BR_FAILED_REPLY, + source: Some(source), + } + } +} + +impl From<kernel::fs::file::BadFdError> for BinderError { + fn from(source: kernel::fs::file::BadFdError) -> Self { + BinderError::from(Error::from(source)) + } +} + +impl From<kernel::alloc::AllocError> for BinderError { + fn from(_: kernel::alloc::AllocError) -> Self { + Self { + reply: BR_FAILED_REPLY, + source: Some(ENOMEM), + } + } +} + +impl core::fmt::Debug for BinderError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self.reply { + BR_FAILED_REPLY => match self.source.as_ref() { + Some(source) => f + .debug_struct("BR_FAILED_REPLY") + .field("source", source) + .finish(), + None => f.pad("BR_FAILED_REPLY"), + }, + BR_DEAD_REPLY => f.pad("BR_DEAD_REPLY"), + BR_FROZEN_REPLY => f.pad("BR_FROZEN_REPLY"), + BR_TRANSACTION_PENDING_FROZEN => f.pad("BR_TRANSACTION_PENDING_FROZEN"), + BR_TRANSACTION_COMPLETE => f.pad("BR_TRANSACTION_COMPLETE"), + _ => f + .debug_struct("BinderError") + .field("reply", &self.reply) + .finish(), + } + } +} diff --git a/drivers/android/binder/freeze.rs b/drivers/android/binder/freeze.rs new file mode 100644 index 000000000000..e68c3c8bc55a --- /dev/null +++ b/drivers/android/binder/freeze.rs @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{ + alloc::AllocError, + list::ListArc, + prelude::*, + rbtree::{self, RBTreeNodeReservation}, + seq_file::SeqFile, + seq_print, + sync::{Arc, UniqueArc}, + uaccess::UserSliceReader, +}; + +use crate::{ + defs::*, node::Node, process::Process, thread::Thread, BinderReturnWriter, DArc, DLArc, + DTRWrap, DeliverToRead, +}; + +#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] +pub(crate) struct FreezeCookie(u64); + +/// Represents a listener for changes to the frozen state of a process. +pub(crate) struct FreezeListener { + /// The node we are listening for. + pub(crate) node: DArc<Node>, + /// The cookie of this freeze listener. + cookie: FreezeCookie, + /// What value of `is_frozen` did we most recently tell userspace about? + last_is_frozen: Option<bool>, + /// We sent a `BR_FROZEN_BINDER` and we are waiting for `BC_FREEZE_NOTIFICATION_DONE` before + /// sending any other commands. + is_pending: bool, + /// Userspace sent `BC_CLEAR_FREEZE_NOTIFICATION` and we need to reply with + /// `BR_CLEAR_FREEZE_NOTIFICATION_DONE` as soon as possible. If `is_pending` is set, then we + /// must wait for it to be unset before we can reply. + is_clearing: bool, + /// Number of cleared duplicates that can't be deleted until userspace sends + /// `BC_FREEZE_NOTIFICATION_DONE`. + num_pending_duplicates: u64, + /// Number of cleared duplicates that can be deleted. + num_cleared_duplicates: u64, +} + +impl FreezeListener { + /// Is it okay to create a new listener with the same cookie as this one for the provided node? + /// + /// Under some scenarios, userspace may delete a freeze listener and immediately recreate it + /// with the same cookie. This results in duplicate listeners. To avoid issues with ambiguity, + /// we allow this only if the new listener is for the same node, and we also require that the + /// old listener has already been cleared. + fn allow_duplicate(&self, node: &DArc<Node>) -> bool { + Arc::ptr_eq(&self.node, node) && self.is_clearing + } +} + +type UninitFM = UniqueArc<core::mem::MaybeUninit<DTRWrap<FreezeMessage>>>; + +/// Represents a notification that the freeze state has changed. +pub(crate) struct FreezeMessage { + cookie: FreezeCookie, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for FreezeMessage { + untracked; + } +} + +impl FreezeMessage { + fn new(flags: kernel::alloc::Flags) -> Result<UninitFM, AllocError> { + UniqueArc::new_uninit(flags) + } + + fn init(ua: UninitFM, cookie: FreezeCookie) -> DLArc<FreezeMessage> { + match ua.pin_init_with(DTRWrap::new(FreezeMessage { cookie })) { + Ok(msg) => ListArc::from(msg), + Err(err) => match err {}, + } + } +} + +impl DeliverToRead for FreezeMessage { + fn do_work( + self: DArc<Self>, + thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + let _removed_listener; + let mut node_refs = thread.process.node_refs.lock(); + let Some(mut freeze_entry) = node_refs.freeze_listeners.find_mut(&self.cookie) else { + return Ok(true); + }; + let freeze = freeze_entry.get_mut(); + + if freeze.num_cleared_duplicates > 0 { + freeze.num_cleared_duplicates -= 1; + drop(node_refs); + writer.write_code(BR_CLEAR_FREEZE_NOTIFICATION_DONE)?; + writer.write_payload(&self.cookie.0)?; + return Ok(true); + } + + if freeze.is_pending { + return Ok(true); + } + if freeze.is_clearing { + _removed_listener = freeze_entry.remove_node(); + drop(node_refs); + writer.write_code(BR_CLEAR_FREEZE_NOTIFICATION_DONE)?; + writer.write_payload(&self.cookie.0)?; + Ok(true) + } else { + let is_frozen = freeze.node.owner.inner.lock().is_frozen; + if freeze.last_is_frozen == Some(is_frozen) { + return Ok(true); + } + + let mut state_info = BinderFrozenStateInfo::default(); + state_info.is_frozen = is_frozen as u32; + state_info.cookie = freeze.cookie.0; + freeze.is_pending = true; + freeze.last_is_frozen = Some(is_frozen); + drop(node_refs); + + writer.write_code(BR_FROZEN_BINDER)?; + writer.write_payload(&state_info)?; + // BR_FROZEN_BINDER notifications can cause transactions + Ok(false) + } + } + + fn cancel(self: DArc<Self>) {} + + fn should_sync_wakeup(&self) -> bool { + false + } + + #[inline(never)] + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + seq_print!(m, "{}has frozen binder\n", prefix); + Ok(()) + } +} + +impl FreezeListener { + pub(crate) fn on_process_exit(&self, proc: &Arc<Process>) { + if !self.is_clearing { + self.node.remove_freeze_listener(proc); + } + } +} + +impl Process { + 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); + + let msg = FreezeMessage::new(GFP_KERNEL)?; + let alloc = RBTreeNodeReservation::new(GFP_KERNEL)?; + + let mut node_refs_guard = self.node_refs.lock(); + let node_refs = &mut *node_refs_guard; + let Some(info) = node_refs.by_handle.get_mut(&handle) else { + pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION invalid ref {}\n", handle); + return Err(EINVAL); + }; + if info.freeze().is_some() { + pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION already set\n"); + return Err(EINVAL); + } + let node_ref = info.node_ref(); + let freeze_entry = node_refs.freeze_listeners.entry(cookie); + + if let rbtree::Entry::Occupied(ref dupe) = freeze_entry { + if !dupe.get().allow_duplicate(&node_ref.node) { + pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION duplicate cookie\n"); + return Err(EINVAL); + } + } + + // All failure paths must come before this call, and all modifications must come after this + // call. + node_ref.node.add_freeze_listener(self, GFP_KERNEL)?; + + match freeze_entry { + rbtree::Entry::Vacant(entry) => { + entry.insert( + FreezeListener { + cookie, + node: node_ref.node.clone(), + last_is_frozen: None, + is_pending: false, + is_clearing: false, + num_pending_duplicates: 0, + num_cleared_duplicates: 0, + }, + alloc, + ); + } + rbtree::Entry::Occupied(mut dupe) => { + let dupe = dupe.get_mut(); + if dupe.is_pending { + dupe.num_pending_duplicates += 1; + } else { + dupe.num_cleared_duplicates += 1; + } + dupe.last_is_frozen = None; + dupe.is_pending = false; + dupe.is_clearing = false; + } + } + + *info.freeze() = Some(cookie); + let msg = FreezeMessage::init(msg, cookie); + drop(node_refs_guard); + let _ = self.push_work(msg); + Ok(()) + } + + 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(); + let node_refs = &mut *node_refs_guard; + let Some(freeze) = node_refs.freeze_listeners.get_mut(&cookie) else { + pr_warn!("BC_FREEZE_NOTIFICATION_DONE {:016x} not found\n", cookie.0); + return Err(EINVAL); + }; + let mut clear_msg = None; + if freeze.num_pending_duplicates > 0 { + clear_msg = Some(FreezeMessage::init(alloc, cookie)); + freeze.num_pending_duplicates -= 1; + freeze.num_cleared_duplicates += 1; + } else { + if !freeze.is_pending { + pr_warn!( + "BC_FREEZE_NOTIFICATION_DONE {:016x} not pending\n", + cookie.0 + ); + return Err(EINVAL); + } + if freeze.is_clearing { + // Immediately send another FreezeMessage for BR_CLEAR_FREEZE_NOTIFICATION_DONE. + clear_msg = Some(FreezeMessage::init(alloc, cookie)); + } + freeze.is_pending = false; + } + drop(node_refs_guard); + if let Some(clear_msg) = clear_msg { + let _ = self.push_work(clear_msg); + } + Ok(()) + } + + 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); + + let alloc = FreezeMessage::new(GFP_KERNEL)?; + let mut node_refs_guard = self.node_refs.lock(); + let node_refs = &mut *node_refs_guard; + let Some(info) = node_refs.by_handle.get_mut(&handle) else { + pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION invalid ref {}\n", handle); + return Err(EINVAL); + }; + let Some(info_cookie) = info.freeze() else { + pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION freeze notification not active\n"); + return Err(EINVAL); + }; + if *info_cookie != cookie { + pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION freeze notification cookie mismatch\n"); + return Err(EINVAL); + } + let Some(listener) = node_refs.freeze_listeners.get_mut(&cookie) else { + pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION invalid cookie {}\n", handle); + return Err(EINVAL); + }; + listener.is_clearing = true; + listener.node.remove_freeze_listener(self); + *info.freeze() = None; + let mut msg = None; + if !listener.is_pending { + msg = Some(FreezeMessage::init(alloc, cookie)); + } + drop(node_refs_guard); + + if let Some(msg) = msg { + let _ = self.push_work(msg); + } + Ok(()) + } + + fn get_freeze_cookie(&self, node: &DArc<Node>) -> Option<FreezeCookie> { + let node_refs = &mut *self.node_refs.lock(); + let handle = node_refs.by_node.get(&node.global_id())?; + let node_ref = node_refs.by_handle.get_mut(handle)?; + *node_ref.freeze() + } + + /// Creates a vector of every freeze listener on this process. + /// + /// Returns pairs of the remote process listening for notifications and the local node it is + /// listening on. + #[expect(clippy::type_complexity)] + fn find_freeze_recipients(&self) -> Result<KVVec<(DArc<Node>, Arc<Process>)>, AllocError> { + // Defined before `inner` to drop after releasing spinlock if `push_within_capacity` fails. + let mut node_proc_pair; + + // We pre-allocate space for up to 8 recipients before we take the spinlock. However, if + // the allocation fails, use a vector with a capacity of zero instead of failing. After + // all, there might not be any freeze listeners, in which case this operation could still + // succeed. + let mut recipients = + KVVec::with_capacity(8, GFP_KERNEL).unwrap_or_else(|_err| KVVec::new()); + + let mut inner = self.lock_with_nodes(); + let mut curr = inner.nodes.cursor_front(); + while let Some(cursor) = curr { + let (key, node) = cursor.current(); + let key = *key; + let list = node.freeze_list(&inner.inner); + let len = list.len(); + + if recipients.spare_capacity_mut().len() < len { + drop(inner); + recipients.reserve(len, GFP_KERNEL)?; + inner = self.lock_with_nodes(); + // Find the node we were looking at and try again. If the set of nodes was changed, + // then just proceed to the next node. This is ok because we don't guarantee the + // inclusion of nodes that are added or removed in parallel with this operation. + curr = inner.nodes.cursor_lower_bound(&key); + continue; + } + + for proc in list { + node_proc_pair = (node.clone(), proc.clone()); + recipients + .push_within_capacity(node_proc_pair) + .map_err(|_| { + pr_err!( + "push_within_capacity failed even though we checked the capacity\n" + ); + AllocError + })?; + } + + curr = cursor.move_next(); + } + Ok(recipients) + } + + /// Prepare allocations for sending freeze messages. + pub(crate) fn prepare_freeze_messages(&self) -> Result<FreezeMessages, AllocError> { + let recipients = self.find_freeze_recipients()?; + let mut batch = KVVec::with_capacity(recipients.len(), GFP_KERNEL)?; + for (node, proc) in recipients { + let Some(cookie) = proc.get_freeze_cookie(&node) else { + // If the freeze listener was removed in the meantime, just discard the + // notification. + continue; + }; + let msg_alloc = FreezeMessage::new(GFP_KERNEL)?; + let msg = FreezeMessage::init(msg_alloc, cookie); + batch.push((proc, msg), GFP_KERNEL)?; + } + + Ok(FreezeMessages { batch }) + } +} + +pub(crate) struct FreezeMessages { + batch: KVVec<(Arc<Process>, DLArc<FreezeMessage>)>, +} + +impl FreezeMessages { + pub(crate) fn send_messages(self) { + for (proc, msg) in self.batch { + let _ = proc.push_work(msg); + } + } +} diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs new file mode 100644 index 000000000000..ade895ef791e --- /dev/null +++ b/drivers/android/binder/node.rs @@ -0,0 +1,1131 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{ + list::{AtomicTracker, List, ListArc, ListLinks, TryNewListArc}, + prelude::*, + seq_file::SeqFile, + seq_print, + sync::lock::{spinlock::SpinLockBackend, Guard}, + sync::{Arc, LockedBy, SpinLock}, +}; + +use crate::{ + defs::*, + error::BinderError, + process::{NodeRefInfo, Process, ProcessInner}, + thread::Thread, + transaction::Transaction, + BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead, +}; + +use core::mem; + +mod wrapper; +pub(crate) use self::wrapper::CritIncrWrapper; + +#[derive(Debug)] +pub(crate) struct CouldNotDeliverCriticalIncrement; + +/// Keeps track of how this node is scheduled. +/// +/// There are two ways to schedule a node to a work list. Just schedule the node itself, or +/// allocate a wrapper that references the node and schedule the wrapper. These wrappers exists to +/// make it possible to "move" a node from one list to another - when `do_work` is called directly +/// on the `Node`, then it's a no-op if there's also a pending wrapper. +/// +/// Wrappers are generally only needed for zero-to-one refcount increments, and there are two cases +/// of this: weak increments and strong increments. We call such increments "critical" because it +/// is critical that they are delivered to the thread doing the increment. Some examples: +/// +/// * One thread makes a zero-to-one strong increment, and another thread makes a zero-to-one weak +/// increment. Delivering the node to the thread doing the weak increment is wrong, since the +/// thread doing the strong increment may have ended a long time ago when the command is actually +/// processed by userspace. +/// +/// * We have a weak reference and are about to drop it on one thread. But then another thread does +/// a zero-to-one strong increment. If the strong increment gets sent to the thread that was +/// about to drop the weak reference, then the strong increment could be processed after the +/// other thread has already exited, which would be too late. +/// +/// Note that trying to create a `ListArc` to the node can succeed even if `has_normal_push` is +/// set. This is because another thread might just have popped the node from a todo list, but not +/// yet called `do_work`. However, if `has_normal_push` is false, then creating a `ListArc` should +/// always succeed. +/// +/// Like the other fields in `NodeInner`, the delivery state is protected by the process lock. +struct DeliveryState { + /// Is the `Node` currently scheduled? + has_pushed_node: bool, + + /// Is a wrapper currently scheduled? + /// + /// The wrapper is used only for strong zero2one increments. + has_pushed_wrapper: bool, + + /// Is the currently scheduled `Node` scheduled due to a weak zero2one increment? + /// + /// Weak zero2one operations are always scheduled using the `Node`. + has_weak_zero2one: bool, + + /// Is the currently scheduled wrapper/`Node` scheduled due to a strong zero2one increment? + /// + /// If `has_pushed_wrapper` is set, then the strong zero2one increment was scheduled using the + /// wrapper. Otherwise, `has_pushed_node` must be set and it was scheduled using the `Node`. + has_strong_zero2one: bool, +} + +impl DeliveryState { + fn should_normal_push(&self) -> bool { + !self.has_pushed_node && !self.has_pushed_wrapper + } + + fn did_normal_push(&mut self) { + assert!(self.should_normal_push()); + self.has_pushed_node = true; + } + + fn should_push_weak_zero2one(&self) -> bool { + !self.has_weak_zero2one && !self.has_strong_zero2one + } + + fn can_push_weak_zero2one_normally(&self) -> bool { + !self.has_pushed_node + } + + fn did_push_weak_zero2one(&mut self) { + assert!(self.should_push_weak_zero2one()); + assert!(self.can_push_weak_zero2one_normally()); + self.has_pushed_node = true; + self.has_weak_zero2one = true; + } + + fn should_push_strong_zero2one(&self) -> bool { + !self.has_strong_zero2one + } + + fn can_push_strong_zero2one_normally(&self) -> bool { + !self.has_pushed_node + } + + fn did_push_strong_zero2one(&mut self) { + assert!(self.should_push_strong_zero2one()); + assert!(self.can_push_strong_zero2one_normally()); + self.has_pushed_node = true; + self.has_strong_zero2one = true; + } + + fn did_push_strong_zero2one_wrapper(&mut self) { + assert!(self.should_push_strong_zero2one()); + assert!(!self.can_push_strong_zero2one_normally()); + self.has_pushed_wrapper = true; + self.has_strong_zero2one = true; + } +} + +struct CountState { + /// The reference count. + count: usize, + /// Whether the process that owns this node thinks that we hold a refcount on it. (Note that + /// even if count is greater than one, we only increment it once in the owning process.) + has_count: bool, +} + +impl CountState { + fn new() -> Self { + Self { + count: 0, + has_count: false, + } + } +} + +struct NodeInner { + /// Strong refcounts held on this node by `NodeRef` objects. + strong: CountState, + /// Weak refcounts held on this node by `NodeRef` objects. + weak: CountState, + delivery_state: DeliveryState, + /// The binder driver guarantees that oneway transactions sent to the same node are serialized, + /// that is, userspace will not be given the next one until it has finished processing the + /// previous oneway transaction. This is done to avoid the case where two oneway transactions + /// arrive in opposite order from the order in which they were sent. (E.g., they could be + /// delivered to two different threads, which could appear as-if they were sent in opposite + /// order.) + /// + /// To fix that, we store pending oneway transactions in a separate list in the node, and don't + /// deliver the next oneway transaction until userspace signals that it has finished processing + /// the previous oneway transaction by calling the `BC_FREE_BUFFER` ioctl. + oneway_todo: List<DTRWrap<Transaction>>, + /// Keeps track of whether this node has a pending oneway transaction. + /// + /// When this is true, incoming oneway transactions are stored in `oneway_todo`, instead of + /// being delivered directly to the process. + has_oneway_transaction: bool, + /// List of processes to deliver a notification to when this node is destroyed (usually due to + /// the process dying). + death_list: List<DTRWrap<NodeDeath>, 1>, + /// List of processes to deliver freeze notifications to. + freeze_list: KVVec<Arc<Process>>, + /// The number of active BR_INCREFS or BR_ACQUIRE operations. (should be maximum two) + /// + /// If this is non-zero, then we postpone any BR_RELEASE or BR_DECREFS notifications until the + /// active operations have ended. This avoids the situation an increment and decrement get + /// reordered from userspace's perspective. + active_inc_refs: u8, + /// List of `NodeRefInfo` objects that reference this node. + refs: List<NodeRefInfo, { NodeRefInfo::LIST_NODE }>, +} + +#[pin_data] +pub(crate) struct Node { + pub(crate) debug_id: usize, + ptr: u64, + pub(crate) cookie: u64, + pub(crate) flags: u32, + pub(crate) owner: Arc<Process>, + inner: LockedBy<NodeInner, ProcessInner>, + #[pin] + links_track: AtomicTracker, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for Node { + tracked_by links_track: AtomicTracker; + } +} + +// Make `oneway_todo` work. +kernel::list::impl_list_item! { + impl ListItem<0> for DTRWrap<Transaction> { + using ListLinks { self.links.inner }; + } +} + +impl Node { + pub(crate) fn new( + ptr: u64, + cookie: u64, + flags: u32, + owner: Arc<Process>, + ) -> impl PinInit<Self> { + pin_init!(Self { + inner: LockedBy::new( + &owner.inner, + NodeInner { + strong: CountState::new(), + weak: CountState::new(), + delivery_state: DeliveryState { + has_pushed_node: false, + has_pushed_wrapper: false, + has_weak_zero2one: false, + has_strong_zero2one: false, + }, + death_list: List::new(), + oneway_todo: List::new(), + freeze_list: KVVec::new(), + has_oneway_transaction: false, + active_inc_refs: 0, + refs: List::new(), + }, + ), + debug_id: super::next_debug_id(), + ptr, + cookie, + flags, + owner, + links_track <- AtomicTracker::new(), + }) + } + + pub(crate) fn has_oneway_transaction(&self, owner_inner: &mut ProcessInner) -> bool { + let inner = self.inner.access_mut(owner_inner); + inner.has_oneway_transaction + } + + #[inline(never)] + 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, + " node {}: u{:016x} c{:016x} hs {} hw {} cs {} cw {}", + self.debug_id, + self.ptr, + self.cookie, + inner.strong.has_count, + inner.weak.has_count, + inner.strong.count, + inner.weak.count, + ); + if !inner.refs.is_empty() { + seq_print!(m, " proc"); + for node_ref in &inner.refs { + seq_print!(m, " {}", node_ref.process.task.pid()); + } + } + seq_print!(m, "\n"); + for t in &inner.oneway_todo { + t.debug_print_inner(m, " pending async transaction "); + } + Ok(()) + } + + /// Insert the `NodeRef` into this `refs` list. + /// + /// # Safety + /// + /// It must be the case that `info.node_ref.node` is this node. + pub(crate) unsafe fn insert_node_info( + &self, + info: ListArc<NodeRefInfo, { NodeRefInfo::LIST_NODE }>, + ) { + self.inner + .access_mut(&mut self.owner.inner.lock()) + .refs + .push_front(info); + } + + /// Insert the `NodeRef` into this `refs` list. + /// + /// # Safety + /// + /// It must be the case that `info.node_ref.node` is this node. + pub(crate) unsafe fn remove_node_info( + &self, + info: &NodeRefInfo, + ) -> Option<ListArc<NodeRefInfo, { NodeRefInfo::LIST_NODE }>> { + // SAFETY: We always insert `NodeRefInfo` objects into the `refs` list of the node that it + // references in `info.node_ref.node`. That is this node, so `info` cannot possibly be in + // the `refs` list of another node. + unsafe { + self.inner + .access_mut(&mut self.owner.inner.lock()) + .refs + .remove(info) + } + } + + /// An id that is unique across all binder nodes on the system. Used as the key in the + /// `by_node` map. + pub(crate) fn global_id(&self) -> usize { + self as *const Node as usize + } + + pub(crate) fn get_id(&self) -> (u64, u64) { + (self.ptr, self.cookie) + } + + pub(crate) fn add_death( + &self, + death: ListArc<DTRWrap<NodeDeath>, 1>, + guard: &mut Guard<'_, ProcessInner, SpinLockBackend>, + ) { + self.inner.access_mut(guard).death_list.push_back(death); + } + + pub(crate) fn inc_ref_done_locked( + self: &DArc<Node>, + _strong: bool, + owner_inner: &mut ProcessInner, + ) -> Option<DLArc<Node>> { + let inner = self.inner.access_mut(owner_inner); + if inner.active_inc_refs == 0 { + pr_err!("inc_ref_done called when no active inc_refs"); + return None; + } + + inner.active_inc_refs -= 1; + if inner.active_inc_refs == 0 { + // Having active inc_refs can inhibit dropping of ref-counts. Calculate whether we + // would send a refcount decrement, and if so, tell the caller to schedule us. + let strong = inner.strong.count > 0; + let has_strong = inner.strong.has_count; + let weak = strong || inner.weak.count > 0; + let has_weak = inner.weak.has_count; + + let should_drop_weak = !weak && has_weak; + let should_drop_strong = !strong && has_strong; + + // If we want to drop the ref-count again, tell the caller to schedule a work node for + // that. + let need_push = should_drop_weak || should_drop_strong; + + if need_push && inner.delivery_state.should_normal_push() { + let list_arc = ListArc::try_from_arc(self.clone()).ok().unwrap(); + inner.delivery_state.did_normal_push(); + Some(list_arc) + } else { + None + } + } else { + None + } + } + + pub(crate) fn update_refcount_locked( + self: &DArc<Node>, + inc: bool, + strong: bool, + count: usize, + owner_inner: &mut ProcessInner, + ) -> Option<DLArc<Node>> { + let is_dead = owner_inner.is_dead; + let inner = self.inner.access_mut(owner_inner); + + // Get a reference to the state we'll update. + let state = if strong { + &mut inner.strong + } else { + &mut inner.weak + }; + + // Update the count and determine whether we need to push work. + let need_push = if inc { + state.count += count; + // TODO: This method shouldn't be used for zero-to-one increments. + !is_dead && !state.has_count + } else { + if state.count < count { + pr_err!("Failure: refcount underflow!"); + return None; + } + state.count -= count; + !is_dead && state.count == 0 && state.has_count + }; + + if need_push && inner.delivery_state.should_normal_push() { + let list_arc = ListArc::try_from_arc(self.clone()).ok().unwrap(); + inner.delivery_state.did_normal_push(); + Some(list_arc) + } else { + None + } + } + + pub(crate) fn incr_refcount_allow_zero2one( + self: &DArc<Self>, + strong: bool, + owner_inner: &mut ProcessInner, + ) -> Result<Option<DLArc<Node>>, CouldNotDeliverCriticalIncrement> { + let is_dead = owner_inner.is_dead; + let inner = self.inner.access_mut(owner_inner); + + // Get a reference to the state we'll update. + let state = if strong { + &mut inner.strong + } else { + &mut inner.weak + }; + + // Update the count and determine whether we need to push work. + state.count += 1; + if is_dead || state.has_count { + return Ok(None); + } + + // Userspace needs to be notified of this. + if !strong && inner.delivery_state.should_push_weak_zero2one() { + assert!(inner.delivery_state.can_push_weak_zero2one_normally()); + let list_arc = ListArc::try_from_arc(self.clone()).ok().unwrap(); + inner.delivery_state.did_push_weak_zero2one(); + Ok(Some(list_arc)) + } else if strong && inner.delivery_state.should_push_strong_zero2one() { + if inner.delivery_state.can_push_strong_zero2one_normally() { + let list_arc = ListArc::try_from_arc(self.clone()).ok().unwrap(); + inner.delivery_state.did_push_strong_zero2one(); + Ok(Some(list_arc)) + } else { + state.count -= 1; + Err(CouldNotDeliverCriticalIncrement) + } + } else { + // Work is already pushed, and we don't need to push again. + Ok(None) + } + } + + pub(crate) fn incr_refcount_allow_zero2one_with_wrapper( + self: &DArc<Self>, + strong: bool, + wrapper: CritIncrWrapper, + owner_inner: &mut ProcessInner, + ) -> Option<DLArc<dyn DeliverToRead>> { + match self.incr_refcount_allow_zero2one(strong, owner_inner) { + Ok(Some(node)) => Some(node as _), + Ok(None) => None, + Err(CouldNotDeliverCriticalIncrement) => { + assert!(strong); + let inner = self.inner.access_mut(owner_inner); + inner.strong.count += 1; + inner.delivery_state.did_push_strong_zero2one_wrapper(); + Some(wrapper.init(self.clone())) + } + } + } + + pub(crate) fn update_refcount(self: &DArc<Self>, inc: bool, count: usize, strong: bool) { + self.owner + .inner + .lock() + .update_node_refcount(self, inc, strong, count, None); + } + + pub(crate) fn populate_counts( + &self, + out: &mut BinderNodeInfoForRef, + guard: &Guard<'_, ProcessInner, SpinLockBackend>, + ) { + let inner = self.inner.access(guard); + out.strong_count = inner.strong.count as _; + out.weak_count = inner.weak.count as _; + } + + pub(crate) fn populate_debug_info( + &self, + out: &mut BinderNodeDebugInfo, + guard: &Guard<'_, ProcessInner, SpinLockBackend>, + ) { + out.ptr = self.ptr as _; + out.cookie = self.cookie as _; + let inner = self.inner.access(guard); + if inner.strong.has_count { + out.has_strong_ref = 1; + } + if inner.weak.has_count { + out.has_weak_ref = 1; + } + } + + pub(crate) fn force_has_count(&self, guard: &mut Guard<'_, ProcessInner, SpinLockBackend>) { + let inner = self.inner.access_mut(guard); + inner.strong.has_count = true; + inner.weak.has_count = true; + } + + fn write(&self, writer: &mut BinderReturnWriter<'_>, code: u32) -> Result { + writer.write_code(code)?; + writer.write_payload(&self.ptr)?; + writer.write_payload(&self.cookie)?; + Ok(()) + } + + pub(crate) fn submit_oneway( + &self, + transaction: DLArc<Transaction>, + guard: &mut Guard<'_, ProcessInner, SpinLockBackend>, + ) -> Result<(), (BinderError, DLArc<dyn DeliverToRead>)> { + if guard.is_dead { + return Err((BinderError::new_dead(), transaction)); + } + + let inner = self.inner.access_mut(guard); + if inner.has_oneway_transaction { + inner.oneway_todo.push_back(transaction); + } else { + inner.has_oneway_transaction = true; + guard.push_work(transaction)?; + } + Ok(()) + } + + pub(crate) fn release(&self) { + let mut guard = self.owner.inner.lock(); + while let Some(work) = self.inner.access_mut(&mut guard).oneway_todo.pop_front() { + drop(guard); + work.into_arc().cancel(); + guard = self.owner.inner.lock(); + } + + let death_list = core::mem::take(&mut self.inner.access_mut(&mut guard).death_list); + drop(guard); + for death in death_list { + death.into_arc().set_dead(); + } + } + + pub(crate) fn pending_oneway_finished(&self) { + let mut guard = self.owner.inner.lock(); + if guard.is_dead { + // Cleanup will happen in `Process::deferred_release`. + return; + } + + let inner = self.inner.access_mut(&mut guard); + + let transaction = inner.oneway_todo.pop_front(); + inner.has_oneway_transaction = transaction.is_some(); + if let Some(transaction) = transaction { + match guard.push_work(transaction) { + Ok(()) => {} + Err((_err, work)) => { + // Process is dead. + // This shouldn't happen due to the `is_dead` check, but if it does, just drop + // the transaction and return. + drop(guard); + drop(work); + } + } + } + } + + /// Finds an outdated transaction that the given transaction can replace. + /// + /// If one is found, it is removed from the list and returned. + pub(crate) fn take_outdated_transaction( + &self, + new: &Transaction, + guard: &mut Guard<'_, ProcessInner, SpinLockBackend>, + ) -> Option<DLArc<Transaction>> { + let inner = self.inner.access_mut(guard); + let mut cursor = inner.oneway_todo.cursor_front(); + while let Some(next) = cursor.peek_next() { + if new.can_replace(&next) { + return Some(next.remove()); + } + cursor.move_next(); + } + None + } + + /// This is split into a separate function since it's called by both `Node::do_work` and + /// `NodeWrapper::do_work`. + fn do_work_locked( + &self, + writer: &mut BinderReturnWriter<'_>, + mut guard: Guard<'_, ProcessInner, SpinLockBackend>, + ) -> Result<bool> { + let inner = self.inner.access_mut(&mut guard); + let strong = inner.strong.count > 0; + let has_strong = inner.strong.has_count; + let weak = strong || inner.weak.count > 0; + let has_weak = inner.weak.has_count; + + if weak && !has_weak { + inner.weak.has_count = true; + inner.active_inc_refs += 1; + } + + if strong && !has_strong { + inner.strong.has_count = true; + inner.active_inc_refs += 1; + } + + let no_active_inc_refs = inner.active_inc_refs == 0; + let should_drop_weak = no_active_inc_refs && (!weak && has_weak); + let should_drop_strong = no_active_inc_refs && (!strong && has_strong); + if should_drop_weak { + inner.weak.has_count = false; + } + if should_drop_strong { + inner.strong.has_count = false; + } + if no_active_inc_refs && !weak { + // Remove the node if there are no references to it. + guard.remove_node(self.ptr); + } + drop(guard); + + if weak && !has_weak { + self.write(writer, BR_INCREFS)?; + } + if strong && !has_strong { + self.write(writer, BR_ACQUIRE)?; + } + if should_drop_strong { + self.write(writer, BR_RELEASE)?; + } + if should_drop_weak { + self.write(writer, BR_DECREFS)?; + } + + Ok(true) + } + + pub(crate) fn add_freeze_listener( + &self, + process: &Arc<Process>, + flags: kernel::alloc::Flags, + ) -> Result { + let mut vec_alloc = KVVec::<Arc<Process>>::new(); + loop { + let mut guard = self.owner.inner.lock(); + // Do not check for `guard.dead`. The `dead` flag that matters here is the owner of the + // listener, no the target. + let inner = self.inner.access_mut(&mut guard); + let len = inner.freeze_list.len(); + if len >= inner.freeze_list.capacity() { + if len >= vec_alloc.capacity() { + drop(guard); + vec_alloc = KVVec::with_capacity((1 + len).next_power_of_two(), flags)?; + continue; + } + mem::swap(&mut inner.freeze_list, &mut vec_alloc); + for elem in vec_alloc.drain_all() { + inner.freeze_list.push_within_capacity(elem)?; + } + } + inner.freeze_list.push_within_capacity(process.clone())?; + return Ok(()); + } + } + + pub(crate) fn remove_freeze_listener(&self, p: &Arc<Process>) { + let _unused_capacity; + let mut guard = self.owner.inner.lock(); + let inner = self.inner.access_mut(&mut guard); + let len = inner.freeze_list.len(); + inner.freeze_list.retain(|proc| !Arc::ptr_eq(proc, p)); + if len == inner.freeze_list.len() { + pr_warn!( + "Could not remove freeze listener for {}\n", + p.pid_in_current_ns() + ); + } + if inner.freeze_list.is_empty() { + _unused_capacity = mem::replace(&mut inner.freeze_list, KVVec::new()); + } + } + + pub(crate) fn freeze_list<'a>(&'a self, guard: &'a ProcessInner) -> &'a [Arc<Process>] { + &self.inner.access(guard).freeze_list + } +} + +impl DeliverToRead for Node { + fn do_work( + self: DArc<Self>, + _thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + let mut owner_inner = self.owner.inner.lock(); + let inner = self.inner.access_mut(&mut owner_inner); + + assert!(inner.delivery_state.has_pushed_node); + if inner.delivery_state.has_pushed_wrapper { + // If the wrapper is scheduled, then we are either a normal push or weak zero2one + // increment, and the wrapper is a strong zero2one increment, so the wrapper always + // takes precedence over us. + assert!(inner.delivery_state.has_strong_zero2one); + inner.delivery_state.has_pushed_node = false; + inner.delivery_state.has_weak_zero2one = false; + return Ok(true); + } + + inner.delivery_state.has_pushed_node = false; + inner.delivery_state.has_weak_zero2one = false; + inner.delivery_state.has_strong_zero2one = false; + + self.do_work_locked(writer, owner_inner) + } + + fn cancel(self: DArc<Self>) {} + + fn should_sync_wakeup(&self) -> bool { + false + } + + #[inline(never)] + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + seq_print!( + m, + "{}node work {}: u{:016x} c{:016x}\n", + prefix, + self.debug_id, + self.ptr, + self.cookie, + ); + Ok(()) + } +} + +/// Represents something that holds one or more ref-counts to a `Node`. +/// +/// Whenever process A holds a refcount to a node owned by a different process B, then process A +/// will store a `NodeRef` that refers to the `Node` in process B. When process A releases the +/// refcount, we destroy the NodeRef, which decrements the ref-count in process A. +/// +/// This type is also used for some other cases. For example, a transaction allocation holds a +/// refcount on the target node, and this is implemented by storing a `NodeRef` in the allocation +/// so that the destructor of the allocation will drop a refcount of the `Node`. +pub(crate) struct NodeRef { + pub(crate) node: DArc<Node>, + /// How many times does this NodeRef hold a refcount on the Node? + strong_node_count: usize, + weak_node_count: usize, + /// How many times does userspace hold a refcount on this NodeRef? + strong_count: usize, + weak_count: usize, +} + +impl NodeRef { + pub(crate) fn new(node: DArc<Node>, strong_count: usize, weak_count: usize) -> Self { + Self { + node, + strong_node_count: strong_count, + weak_node_count: weak_count, + strong_count, + weak_count, + } + } + + pub(crate) fn absorb(&mut self, mut other: Self) { + assert!( + Arc::ptr_eq(&self.node, &other.node), + "absorb called with differing nodes" + ); + self.strong_node_count += other.strong_node_count; + self.weak_node_count += other.weak_node_count; + self.strong_count += other.strong_count; + self.weak_count += other.weak_count; + other.strong_count = 0; + other.weak_count = 0; + other.strong_node_count = 0; + other.weak_node_count = 0; + + if self.strong_node_count >= 2 || self.weak_node_count >= 2 { + let mut guard = self.node.owner.inner.lock(); + let inner = self.node.inner.access_mut(&mut guard); + + if self.strong_node_count >= 2 { + inner.strong.count -= self.strong_node_count - 1; + self.strong_node_count = 1; + assert_ne!(inner.strong.count, 0); + } + if self.weak_node_count >= 2 { + inner.weak.count -= self.weak_node_count - 1; + self.weak_node_count = 1; + assert_ne!(inner.weak.count, 0); + } + } + } + + pub(crate) fn get_count(&self) -> (usize, usize) { + (self.strong_count, self.weak_count) + } + + pub(crate) fn clone(&self, strong: bool) -> Result<NodeRef> { + if strong && self.strong_count == 0 { + return Err(EINVAL); + } + Ok(self + .node + .owner + .inner + .lock() + .new_node_ref(self.node.clone(), strong, None)) + } + + /// Updates (increments or decrements) the number of references held against the node. If the + /// count being updated transitions from 0 to 1 or from 1 to 0, the node is notified by having + /// its `update_refcount` function called. + /// + /// Returns whether `self` should be removed (when both counts are zero). + pub(crate) fn update(&mut self, inc: bool, strong: bool) -> bool { + if strong && self.strong_count == 0 { + return false; + } + let (count, node_count, other_count) = if strong { + ( + &mut self.strong_count, + &mut self.strong_node_count, + self.weak_count, + ) + } else { + ( + &mut self.weak_count, + &mut self.weak_node_count, + self.strong_count, + ) + }; + if inc { + if *count == 0 { + *node_count = 1; + self.node.update_refcount(true, 1, strong); + } + *count += 1; + } else { + if *count == 0 { + pr_warn!( + "pid {} performed invalid decrement on ref\n", + kernel::current!().pid() + ); + return false; + } + *count -= 1; + if *count == 0 { + self.node.update_refcount(false, *node_count, strong); + *node_count = 0; + return other_count == 0; + } + } + false + } +} + +impl Drop for NodeRef { + // This destructor is called conditionally from `Allocation::drop`. That branch is often + // mispredicted. Inlining this method call reduces the cost of those branch mispredictions. + #[inline(always)] + fn drop(&mut self) { + if self.strong_node_count > 0 { + self.node + .update_refcount(false, self.strong_node_count, true); + } + if self.weak_node_count > 0 { + self.node + .update_refcount(false, self.weak_node_count, false); + } + } +} + +struct NodeDeathInner { + dead: bool, + cleared: bool, + notification_done: bool, + /// Indicates whether the normal flow was interrupted by removing the handle. In this case, we + /// need behave as if the death notification didn't exist (i.e., we don't deliver anything to + /// the user. + aborted: bool, +} + +/// Used to deliver notifications when a process dies. +/// +/// A process can request to be notified when a process dies using `BC_REQUEST_DEATH_NOTIFICATION`. +/// This will make the driver send a `BR_DEAD_BINDER` to userspace when the process dies (or +/// immediately if it is already dead). Userspace is supposed to respond with `BC_DEAD_BINDER_DONE` +/// once it has processed the notification. +/// +/// Userspace can unregister from death notifications using the `BC_CLEAR_DEATH_NOTIFICATION` +/// command. In this case, the kernel will respond with `BR_CLEAR_DEATH_NOTIFICATION_DONE` once the +/// notification has been removed. Note that if the remote process dies before the kernel has +/// responded with `BR_CLEAR_DEATH_NOTIFICATION_DONE`, then the kernel will still send a +/// `BR_DEAD_BINDER`, which userspace must be able to process. In this case, the kernel will wait +/// for the `BC_DEAD_BINDER_DONE` command before it sends `BR_CLEAR_DEATH_NOTIFICATION_DONE`. +/// +/// Note that even if the kernel sends a `BR_DEAD_BINDER`, this does not remove the death +/// notification. Userspace must still remove it manually using `BC_CLEAR_DEATH_NOTIFICATION`. +/// +/// If a process uses `BC_RELEASE` to destroy its last refcount on a node that has an active death +/// registration, then the death registration is immediately deleted (we implement this using the +/// `aborted` field). However, userspace is not supposed to delete a `NodeRef` without first +/// deregistering death notifications, so this codepath is not executed under normal circumstances. +#[pin_data] +pub(crate) struct NodeDeath { + node: DArc<Node>, + process: Arc<Process>, + pub(crate) cookie: u64, + #[pin] + links_track: AtomicTracker<0>, + /// Used by the owner `Node` to store a list of registered death notifications. + /// + /// # Invariants + /// + /// Only ever used with the `death_list` list of `self.node`. + #[pin] + death_links: ListLinks<1>, + /// Used by the process to keep track of the death notifications for which we have sent a + /// `BR_DEAD_BINDER` but not yet received a `BC_DEAD_BINDER_DONE`. + /// + /// # Invariants + /// + /// Only ever used with the `delivered_deaths` list of `self.process`. + #[pin] + delivered_links: ListLinks<2>, + #[pin] + delivered_links_track: AtomicTracker<2>, + #[pin] + inner: SpinLock<NodeDeathInner>, +} + +impl NodeDeath { + /// Constructs a new node death notification object. + pub(crate) fn new( + node: DArc<Node>, + process: Arc<Process>, + cookie: u64, + ) -> impl PinInit<DTRWrap<Self>> { + DTRWrap::new(pin_init!( + Self { + node, + process, + cookie, + links_track <- AtomicTracker::new(), + death_links <- ListLinks::new(), + delivered_links <- ListLinks::new(), + delivered_links_track <- AtomicTracker::new(), + inner <- kernel::new_spinlock!(NodeDeathInner { + dead: false, + cleared: false, + notification_done: false, + aborted: false, + }, "NodeDeath::inner"), + } + )) + } + + /// Sets the cleared flag to `true`. + /// + /// It removes `self` from the node's death notification list if needed. + /// + /// Returns whether it needs to be queued. + pub(crate) fn set_cleared(self: &DArc<Self>, abort: bool) -> bool { + let (needs_removal, needs_queueing) = { + // Update state and determine if we need to queue a work item. We only need to do it + // when the node is not dead or if the user already completed the death notification. + let mut inner = self.inner.lock(); + if abort { + inner.aborted = true; + } + if inner.cleared { + // Already cleared. + return false; + } + inner.cleared = true; + (!inner.dead, !inner.dead || inner.notification_done) + }; + + // Remove death notification from node. + if needs_removal { + let mut owner_inner = self.node.owner.inner.lock(); + let node_inner = self.node.inner.access_mut(&mut owner_inner); + // SAFETY: A `NodeDeath` is never inserted into the death list of any node other than + // its owner, so it is either in this death list or in no death list. + unsafe { node_inner.death_list.remove(self) }; + } + needs_queueing + } + + /// Sets the 'notification done' flag to `true`. + pub(crate) fn set_notification_done(self: DArc<Self>, thread: &Thread) { + let needs_queueing = { + let mut inner = self.inner.lock(); + inner.notification_done = true; + inner.cleared + }; + if needs_queueing { + if let Some(death) = ListArc::try_from_arc_or_drop(self) { + let _ = thread.push_work_if_looper(death); + } + } + } + + /// Sets the 'dead' flag to `true` and queues work item if needed. + pub(crate) fn set_dead(self: DArc<Self>) { + let needs_queueing = { + let mut inner = self.inner.lock(); + if inner.cleared { + false + } else { + inner.dead = true; + true + } + }; + if needs_queueing { + // Push the death notification to the target process. There is nothing else to do if + // it's already dead. + if let Some(death) = ListArc::try_from_arc_or_drop(self) { + let process = death.process.clone(); + let _ = process.push_work(death); + } + } + } +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for NodeDeath { + tracked_by links_track: AtomicTracker; + } +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<1> for DTRWrap<NodeDeath> { untracked; } +} +kernel::list::impl_list_item! { + impl ListItem<1> for DTRWrap<NodeDeath> { + using ListLinks { self.wrapped.death_links }; + } +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<2> for DTRWrap<NodeDeath> { + tracked_by wrapped: NodeDeath; + } +} +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<2> for NodeDeath { + tracked_by delivered_links_track: AtomicTracker<2>; + } +} +kernel::list::impl_list_item! { + impl ListItem<2> for DTRWrap<NodeDeath> { + using ListLinks { self.wrapped.delivered_links }; + } +} + +impl DeliverToRead for NodeDeath { + fn do_work( + self: DArc<Self>, + _thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + let done = { + let inner = self.inner.lock(); + if inner.aborted { + return Ok(true); + } + inner.cleared && (!inner.dead || inner.notification_done) + }; + + let cookie = self.cookie; + let cmd = if done { + BR_CLEAR_DEATH_NOTIFICATION_DONE + } else { + let process = self.process.clone(); + let mut process_inner = process.inner.lock(); + let inner = self.inner.lock(); + if inner.aborted { + return Ok(true); + } + // We're still holding the inner lock, so it cannot be aborted while we insert it into + // the delivered list. + process_inner.death_delivered(self.clone()); + BR_DEAD_BINDER + }; + + writer.write_code(cmd)?; + writer.write_payload(&cookie)?; + // DEAD_BINDER notifications can cause transactions, so stop processing work items when we + // get to a death notification. + Ok(cmd != BR_DEAD_BINDER) + } + + fn cancel(self: DArc<Self>) {} + + fn should_sync_wakeup(&self) -> bool { + false + } + + #[inline(never)] + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + let inner = self.inner.lock(); + + let dead_binder = inner.dead && !inner.notification_done; + + if dead_binder { + if inner.cleared { + seq_print!(m, "{}has cleared dead binder\n", prefix); + } else { + seq_print!(m, "{}has dead binder\n", prefix); + } + } else { + seq_print!(m, "{}has cleared death notification\n", prefix); + } + + Ok(()) + } +} diff --git a/drivers/android/binder/node/wrapper.rs b/drivers/android/binder/node/wrapper.rs new file mode 100644 index 000000000000..43294c050502 --- /dev/null +++ b/drivers/android/binder/node/wrapper.rs @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{list::ListArc, prelude::*, seq_file::SeqFile, seq_print, sync::UniqueArc}; + +use crate::{node::Node, thread::Thread, BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead}; + +use core::mem::MaybeUninit; + +pub(crate) struct CritIncrWrapper { + inner: UniqueArc<MaybeUninit<DTRWrap<NodeWrapper>>>, +} + +impl CritIncrWrapper { + pub(crate) fn new() -> Result<Self> { + Ok(CritIncrWrapper { + inner: UniqueArc::new_uninit(GFP_KERNEL)?, + }) + } + + 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 _, + Err(err) => match err {}, + } + } +} + +struct NodeWrapper { + node: DArc<Node>, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for NodeWrapper { + untracked; + } +} + +impl DeliverToRead for NodeWrapper { + fn do_work( + self: DArc<Self>, + _thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + let node = &self.node; + let mut owner_inner = node.owner.inner.lock(); + let inner = node.inner.access_mut(&mut owner_inner); + + let ds = &mut inner.delivery_state; + + assert!(ds.has_pushed_wrapper); + assert!(ds.has_strong_zero2one); + ds.has_pushed_wrapper = false; + ds.has_strong_zero2one = false; + + node.do_work_locked(writer, owner_inner) + } + + fn cancel(self: DArc<Self>) {} + + fn should_sync_wakeup(&self) -> bool { + false + } + + #[inline(never)] + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + seq_print!( + m, + "{}node work {}: u{:016x} c{:016x}\n", + prefix, + self.node.debug_id, + self.node.ptr, + self.node.cookie, + ); + Ok(()) + } +} diff --git a/drivers/android/binder/page_range.rs b/drivers/android/binder/page_range.rs new file mode 100644 index 000000000000..9379038f61f5 --- /dev/null +++ b/drivers/android/binder/page_range.rs @@ -0,0 +1,734 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! This module has utilities for managing a page range where unused pages may be reclaimed by a +//! vma shrinker. + +// To avoid deadlocks, locks are taken in the order: +// +// 1. mmap lock +// 2. spinlock +// 3. lru spinlock +// +// The shrinker will use trylock methods because it locks them in a different order. + +use core::{ + marker::PhantomPinned, + mem::{size_of, size_of_val, MaybeUninit}, + ptr, +}; + +use kernel::{ + bindings, + error::Result, + ffi::{c_ulong, c_void}, + mm::{virt, Mm, MmWithUser}, + new_mutex, new_spinlock, + page::{Page, PAGE_SHIFT, PAGE_SIZE}, + prelude::*, + str::CStr, + sync::{aref::ARef, Mutex, SpinLock}, + task::Pid, + transmute::FromBytes, + types::Opaque, + uaccess::UserSliceReader, +}; + +/// Represents a shrinker that can be registered with the kernel. +/// +/// Each shrinker can be used by many `ShrinkablePageRange` objects. +#[repr(C)] +pub(crate) struct Shrinker { + inner: Opaque<*mut bindings::shrinker>, + list_lru: Opaque<bindings::list_lru>, +} + +// SAFETY: The shrinker and list_lru are thread safe. +unsafe impl Send for Shrinker {} +// SAFETY: The shrinker and list_lru are thread safe. +unsafe impl Sync for Shrinker {} + +impl Shrinker { + /// Create a new shrinker. + /// + /// # Safety + /// + /// Before using this shrinker with a `ShrinkablePageRange`, the `register` method must have + /// been called exactly once, and it must not have returned an error. + pub(crate) const unsafe fn new() -> Self { + Self { + inner: Opaque::uninit(), + list_lru: Opaque::uninit(), + } + } + + /// Register this shrinker with the kernel. + 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()); + self.list_lru.get().write_bytes(0, 1); + } + + // SAFETY: The field is not yet used, so we can initialize it. + let ret = unsafe { bindings::__list_lru_init(self.list_lru.get(), false, ptr::null_mut()) }; + if ret != 0 { + return Err(Error::from_errno(ret)); + } + + // SAFETY: The `name` points at a valid c string. + let shrinker = unsafe { bindings::shrinker_alloc(0, name.as_char_ptr()) }; + if shrinker.is_null() { + // SAFETY: We initialized it, so its okay to destroy it. + unsafe { bindings::list_lru_destroy(self.list_lru.get()) }; + return Err(Error::from_errno(ret)); + } + + // SAFETY: We're about to register the shrinker, and these are the fields we need to + // initialize. (All other fields are already zeroed.) + unsafe { + (&raw mut (*shrinker).count_objects).write(Some(rust_shrink_count)); + (&raw mut (*shrinker).scan_objects).write(Some(rust_shrink_scan)); + (&raw mut (*shrinker).private_data).write(self.list_lru.get().cast()); + } + + // SAFETY: The new shrinker has been fully initialized, so we can register it. + unsafe { bindings::shrinker_register(shrinker) }; + + // SAFETY: This initializes the pointer to the shrinker so that we can use it. + unsafe { self.inner.get().write(shrinker) }; + + Ok(()) + } +} + +/// A container that manages a page range in a vma. +/// +/// The pages can be thought of as an array of booleans of whether the pages are usable. The +/// methods `use_range` and `stop_using_range` set all booleans in a range to true or false +/// respectively. Initially, no pages are allocated. When a page is not used, it is not freed +/// immediately. Instead, it is made available to the memory shrinker to free it if the device is +/// under memory pressure. +/// +/// It's okay for `use_range` and `stop_using_range` to race with each other, although there's no +/// way to know whether an index ends up with true or false if a call to `use_range` races with +/// another call to `stop_using_range` on a given index. +/// +/// It's also okay for the two methods to race with themselves, e.g. if two threads call +/// `use_range` on the same index, then that's fine and neither call will return until the page is +/// allocated and mapped. +/// +/// The methods that read or write to a range require that the page is marked as in use. So it is +/// _not_ okay to call `stop_using_range` on a page that is in use by the methods that read or +/// write to the page. +#[pin_data(PinnedDrop)] +pub(crate) struct ShrinkablePageRange { + /// Shrinker object registered with the kernel. + shrinker: &'static Shrinker, + /// Pid using this page range. Only used as debugging information. + pid: Pid, + /// The mm for the relevant process. + mm: ARef<Mm>, + /// Used to synchronize calls to `vm_insert_page` and `zap_page_range_single`. + #[pin] + mm_lock: Mutex<()>, + /// Spinlock protecting changes to pages. + #[pin] + lock: SpinLock<Inner>, + + /// Must not move, since page info has pointers back. + #[pin] + _pin: PhantomPinned, +} + +struct Inner { + /// Array of pages. + /// + /// Since this is also accessed by the shrinker, we can't use a `Box`, which asserts exclusive + /// ownership. To deal with that, we manage it using raw pointers. + pages: *mut PageInfo, + /// Length of the `pages` array. + size: usize, + /// The address of the vma to insert the pages into. + vma_addr: usize, +} + +// SAFETY: proper locking is in place for `Inner` +unsafe impl Send for Inner {} + +type StableMmGuard = + kernel::sync::lock::Guard<'static, (), kernel::sync::lock::mutex::MutexBackend>; + +/// An array element that describes the current state of a page. +/// +/// There are three states: +/// +/// * Free. The page is None. The `lru` element is not queued. +/// * Available. The page is Some. The `lru` element is queued to the shrinker's lru. +/// * Used. The page is Some. The `lru` element is not queued. +/// +/// When an element is available, the shrinker is able to free the page. +#[repr(C)] +struct PageInfo { + lru: bindings::list_head, + page: Option<Page>, + range: *const ShrinkablePageRange, +} + +impl PageInfo { + /// # Safety + /// + /// The caller ensures that writing to `me.page` is ok, and that the page is not currently set. + unsafe fn set_page(me: *mut PageInfo, page: Page) { + // SAFETY: This pointer offset is in bounds. + let ptr = unsafe { &raw mut (*me).page }; + + // SAFETY: The pointer is valid for writing, so also valid for reading. + if unsafe { (*ptr).is_some() } { + pr_err!("set_page called when there is already a page"); + // SAFETY: We will initialize the page again below. + unsafe { ptr::drop_in_place(ptr) }; + } + + // SAFETY: The pointer is valid for writing. + unsafe { ptr::write(ptr, Some(page)) }; + } + + /// # Safety + /// + /// The caller ensures that reading from `me.page` is ok for the duration of 'a. + unsafe fn get_page<'a>(me: *const PageInfo) -> Option<&'a Page> { + // SAFETY: This pointer offset is in bounds. + let ptr = unsafe { &raw const (*me).page }; + + // SAFETY: The pointer is valid for reading. + unsafe { (*ptr).as_ref() } + } + + /// # Safety + /// + /// The caller ensures that writing to `me.page` is ok for the duration of 'a. + unsafe fn take_page(me: *mut PageInfo) -> Option<Page> { + // SAFETY: This pointer offset is in bounds. + let ptr = unsafe { &raw mut (*me).page }; + + // SAFETY: The pointer is valid for reading. + unsafe { (*ptr).take() } + } + + /// Add this page to the lru list, if not already in the list. + /// + /// # Safety + /// + /// The pointer must be valid, and it must be the right shrinker and nid. + unsafe fn list_lru_add(me: *mut PageInfo, nid: i32, shrinker: &'static Shrinker) { + // SAFETY: This pointer offset is in bounds. + let lru_ptr = unsafe { &raw mut (*me).lru }; + // SAFETY: The lru pointer is valid, and we're not using it with any other lru list. + unsafe { bindings::list_lru_add(shrinker.list_lru.get(), lru_ptr, nid, ptr::null_mut()) }; + } + + /// Remove this page from the lru list, if it is in the list. + /// + /// # Safety + /// + /// The pointer must be valid, and it must be the right shrinker and nid. + unsafe fn list_lru_del(me: *mut PageInfo, nid: i32, shrinker: &'static Shrinker) { + // SAFETY: This pointer offset is in bounds. + let lru_ptr = unsafe { &raw mut (*me).lru }; + // SAFETY: The lru pointer is valid, and we're not using it with any other lru list. + unsafe { bindings::list_lru_del(shrinker.list_lru.get(), lru_ptr, nid, ptr::null_mut()) }; + } +} + +impl ShrinkablePageRange { + /// Create a new `ShrinkablePageRange` using the given shrinker. + pub(crate) fn new(shrinker: &'static Shrinker) -> impl PinInit<Self, Error> { + try_pin_init!(Self { + shrinker, + pid: kernel::current!().pid(), + mm: ARef::from(&**kernel::current!().mm().ok_or(ESRCH)?), + mm_lock <- new_mutex!((), "ShrinkablePageRange::mm"), + lock <- new_spinlock!(Inner { + pages: ptr::null_mut(), + size: 0, + vma_addr: 0, + }, "ShrinkablePageRange"), + _pin: PhantomPinned, + }) + } + + pub(crate) fn stable_trylock_mm(&self) -> Option<StableMmGuard> { + // SAFETY: This extends the duration of the reference. Since this call happens before + // `mm_lock` is taken in the destructor of `ShrinkablePageRange`, the destructor will block + // until the returned guard is dropped. This ensures that the guard is valid until dropped. + let mm_lock = unsafe { &*ptr::from_ref(&self.mm_lock) }; + + mm_lock.try_lock() + } + + /// Register a vma with this page range. Returns the size of the region. + pub(crate) fn register_with_vma(&self, vma: &virt::VmaNew) -> Result<usize> { + let num_bytes = usize::min(vma.end() - vma.start(), bindings::SZ_4M as usize); + let num_pages = num_bytes >> PAGE_SHIFT; + + if !ptr::eq::<Mm>(&*self.mm, &**vma.mm()) { + pr_debug!("Failed to register with vma: invalid vma->vm_mm"); + return Err(EINVAL); + } + if num_pages == 0 { + pr_debug!("Failed to register with vma: size zero"); + return Err(EINVAL); + } + + let mut pages = KVVec::<PageInfo>::with_capacity(num_pages, GFP_KERNEL)?; + + // SAFETY: This just initializes the pages array. + unsafe { + let self_ptr = self as *const ShrinkablePageRange; + for i in 0..num_pages { + let info = pages.as_mut_ptr().add(i); + (&raw mut (*info).range).write(self_ptr); + (&raw mut (*info).page).write(None); + let lru = &raw mut (*info).lru; + (&raw mut (*lru).next).write(lru); + (&raw mut (*lru).prev).write(lru); + } + } + + let mut inner = self.lock.lock(); + if inner.size > 0 { + pr_debug!("Failed to register with vma: already registered"); + drop(inner); + return Err(EBUSY); + } + + inner.pages = pages.into_raw_parts().0; + inner.size = num_pages; + inner.vma_addr = vma.start(); + + Ok(num_pages) + } + + /// 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<()> { + if start >= end { + return Ok(()); + } + let mut inner = self.lock.lock(); + assert!(end <= inner.size); + + for i in start..end { + // SAFETY: This pointer offset is in bounds. + let page_info = unsafe { inner.pages.add(i) }; + + // SAFETY: The pointer is valid, and we hold the lock so reading from the page is okay. + if let Some(page) = unsafe { PageInfo::get_page(page_info) } { + // Since we're going to use the page, we should remove it from the lru list so that + // the shrinker will not free it. + // + // SAFETY: The pointer is valid, and this is the right shrinker. + // + // The shrinker can't free the page between the check and this call to + // `list_lru_del` because we hold the lock. + unsafe { PageInfo::list_lru_del(page_info, page.nid(), self.shrinker) }; + } else { + // We have to allocate a new page. Use the slow path. + drop(inner); + // SAFETY: `i < end <= inner.size` so `i` is in bounds. + match unsafe { self.use_page_slow(i) } { + Ok(()) => {} + Err(err) => { + pr_warn!("Error in use_page_slow: {:?}", err); + return Err(err); + } + } + inner = self.lock.lock(); + } + } + Ok(()) + } + + /// Mark the given page as in use, slow path. + /// + /// Must not be called from an atomic context. + /// + /// # Safety + /// + /// Assumes that `i` is in bounds. + #[cold] + 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(); + let inner = self.lock.lock(); + + // SAFETY: This pointer offset is in bounds. + let page_info = unsafe { inner.pages.add(i) }; + + // SAFETY: The pointer is valid, and we hold the lock so reading from the page is okay. + if let Some(page) = unsafe { PageInfo::get_page(page_info) } { + // The page was already there, or someone else added the page while we didn't hold the + // spinlock. + // + // SAFETY: The pointer is valid, and this is the right shrinker. + // + // The shrinker can't free the page between the check and this call to + // `list_lru_del` because we hold the lock. + unsafe { PageInfo::list_lru_del(page_info, page.nid(), self.shrinker) }; + return Ok(()); + } + + let vma_addr = inner.vma_addr; + // Release the spinlock while we insert the page into the vma. + drop(inner); + + // No overflow since we stay in bounds of the vma. + let user_page_addr = vma_addr + (i << PAGE_SHIFT); + + // We use `mmput_async` when dropping the `mm` because `use_page_slow` is usually used from + // a remote process. If the call to `mmput` races with the process shutting down, then the + // caller of `use_page_slow` becomes responsible for cleaning up the `mm`, which doesn't + // happen until it returns to userspace. However, the caller might instead go to sleep and + // wait for the owner of the `mm` to wake it up, which doesn't happen because it's in the + // middle of a shutdown process that won't complete until the `mm` is dropped. This can + // amount to a deadlock. + // + // Using `mmput_async` avoids this, because then the `mm` cleanup is instead queued to a + // workqueue. + MmWithUser::into_mmput_async(self.mm.mmget_not_zero().ok_or(ESRCH)?) + .mmap_read_lock() + .vma_lookup(vma_addr) + .ok_or(ESRCH)? + .as_mixedmap_vma() + .ok_or(ESRCH)? + .vm_insert_page(user_page_addr, &new_page) + .inspect_err(|err| { + pr_warn!( + "Failed to vm_insert_page({}): vma_addr:{} i:{} err:{:?}", + user_page_addr, + vma_addr, + i, + err + ) + })?; + + let inner = self.lock.lock(); + + // SAFETY: The `page_info` pointer is valid and currently does not have a page. The page + // can be written to since we hold the lock. + // + // We released and reacquired the spinlock since we checked that the page is null, but we + // always hold the mm_lock mutex when setting the page to a non-null value, so it's not + // possible for someone else to have changed it since our check. + unsafe { PageInfo::set_page(page_info, new_page) }; + + drop(inner); + drop(mm_mutex); + + Ok(()) + } + + /// If the given page is in use, then mark it as available so that the shrinker can free it. + /// + /// May be called from an atomic context. + pub(crate) fn stop_using_range(&self, start: usize, end: usize) { + if start >= end { + return; + } + let inner = self.lock.lock(); + assert!(end <= inner.size); + + for i in (start..end).rev() { + // SAFETY: The pointer is in bounds. + let page_info = unsafe { inner.pages.add(i) }; + + // SAFETY: Okay for reading since we have the lock. + if let Some(page) = unsafe { PageInfo::get_page(page_info) } { + // SAFETY: The pointer is valid, and it's the right shrinker. + unsafe { PageInfo::list_lru_add(page_info, page.nid(), self.shrinker) }; + } + } + } + + /// Helper for reading or writing to a range of bytes that may overlap with several pages. + /// + /// # Safety + /// + /// All pages touched by this operation must be in use for the duration of this call. + unsafe fn iterate<T>(&self, mut offset: usize, mut size: usize, mut cb: T) -> Result + where + T: FnMut(&Page, usize, usize) -> Result, + { + if size == 0 { + return Ok(()); + } + + let (pages, num_pages) = { + let inner = self.lock.lock(); + (inner.pages, inner.size) + }; + let num_bytes = num_pages << PAGE_SHIFT; + + // Check that the request is within the buffer. + if offset.checked_add(size).ok_or(EFAULT)? > num_bytes { + return Err(EFAULT); + } + + let mut page_index = offset >> PAGE_SHIFT; + offset &= PAGE_SIZE - 1; + while size > 0 { + let available = usize::min(size, PAGE_SIZE - offset); + // SAFETY: The pointer is in bounds. + let page_info = unsafe { pages.add(page_index) }; + // SAFETY: The caller guarantees that this page is in the "in use" state for the + // duration of this call to `iterate`, so nobody will change the page. + let page = unsafe { PageInfo::get_page(page_info) }; + if page.is_none() { + pr_warn!("Page is null!"); + } + let page = page.ok_or(EFAULT)?; + cb(page, offset, available)?; + size -= available; + page_index += 1; + offset = 0; + } + Ok(()) + } + + /// Copy from userspace into this page range. + /// + /// # Safety + /// + /// All pages touched by this operation must be in use for the duration of this call. + pub(crate) unsafe fn copy_from_user_slice( + &self, + reader: &mut UserSliceReader, + offset: usize, + size: usize, + ) -> Result { + // SAFETY: `self.iterate` has the same safety requirements as `copy_from_user_slice`. + unsafe { + self.iterate(offset, size, |page, offset, to_copy| { + page.copy_from_user_slice_raw(reader, offset, to_copy) + }) + } + } + + /// Copy from this page range into kernel space. + /// + /// # Safety + /// + /// All pages touched by this operation must be in use for the duration of this call. + pub(crate) unsafe fn read<T: FromBytes>(&self, offset: usize) -> Result<T> { + let mut out = MaybeUninit::<T>::uninit(); + let mut out_offset = 0; + // SAFETY: `self.iterate` has the same safety requirements as `read`. + unsafe { + self.iterate(offset, size_of::<T>(), |page, offset, to_copy| { + // SAFETY: The sum of `offset` and `to_copy` is bounded by the size of T. + let obj_ptr = (out.as_mut_ptr() as *mut u8).add(out_offset); + // SAFETY: The pointer points is in-bounds of the `out` variable, so it is valid. + page.read_raw(obj_ptr, offset, to_copy)?; + out_offset += to_copy; + Ok(()) + })?; + } + // SAFETY: We just initialised the data. + Ok(unsafe { out.assume_init() }) + } + + /// Copy from kernel space into this page range. + /// + /// # Safety + /// + /// All pages touched by this operation must be in use for the duration of this call. + pub(crate) unsafe fn write<T: ?Sized>(&self, offset: usize, obj: &T) -> Result { + let mut obj_offset = 0; + // SAFETY: `self.iterate` has the same safety requirements as `write`. + unsafe { + self.iterate(offset, size_of_val(obj), |page, offset, to_copy| { + // SAFETY: The sum of `offset` and `to_copy` is bounded by the size of T. + let obj_ptr = (obj as *const T as *const u8).add(obj_offset); + // SAFETY: We have a reference to the object, so the pointer is valid. + page.write_raw(obj_ptr, offset, to_copy)?; + obj_offset += to_copy; + Ok(()) + }) + } + } + + /// Write zeroes to the given range. + /// + /// # Safety + /// + /// All pages touched by this operation must be in use for the duration of this call. + pub(crate) unsafe fn fill_zero(&self, offset: usize, size: usize) -> Result { + // SAFETY: `self.iterate` has the same safety requirements as `copy_into`. + unsafe { + self.iterate(offset, size, |page, offset, len| { + page.fill_zero_raw(offset, len) + }) + } + } +} + +#[pinned_drop] +impl PinnedDrop for ShrinkablePageRange { + fn drop(self: Pin<&mut Self>) { + let (pages, size) = { + let lock = self.lock.lock(); + (lock.pages, lock.size) + }; + + if size == 0 { + return; + } + + // Note: This call is also necessary for the safety of `stable_trylock_mm`. + let mm_lock = self.mm_lock.lock(); + + // This is the destructor, so unlike the other methods, we only need to worry about races + // with the shrinker here. Since we hold the `mm_lock`, we also can't race with the + // shrinker, and after this loop, the shrinker will not access any of our pages since we + // removed them from the lru list. + for i in 0..size { + // SAFETY: Loop is in-bounds of the size. + let p_ptr = unsafe { pages.add(i) }; + // SAFETY: No other readers, so we can read. + if let Some(p) = unsafe { PageInfo::get_page(p_ptr) } { + // SAFETY: The pointer is valid and it's the right shrinker. + unsafe { PageInfo::list_lru_del(p_ptr, p.nid(), self.shrinker) }; + } + } + + drop(mm_lock); + + // SAFETY: `pages` was allocated as an `KVVec<PageInfo>` with capacity `size`. Furthermore, + // all `size` elements are initialized. Also, the array is no longer shared with the + // shrinker due to the above loop. + drop(unsafe { KVVec::from_raw_parts(pages, size, size) }); + } +} + +/// # Safety +/// Called by the shrinker. +#[no_mangle] +unsafe extern "C" fn rust_shrink_count( + shrink: *mut bindings::shrinker, + _sc: *mut bindings::shrink_control, +) -> c_ulong { + // SAFETY: We can access our own private data. + let list_lru = unsafe { (*shrink).private_data.cast::<bindings::list_lru>() }; + // SAFETY: Accessing the lru list is okay. Just an FFI call. + unsafe { bindings::list_lru_count(list_lru) } +} + +/// # Safety +/// Called by the shrinker. +#[no_mangle] +unsafe extern "C" fn rust_shrink_scan( + shrink: *mut bindings::shrinker, + sc: *mut bindings::shrink_control, +) -> c_ulong { + // SAFETY: We can access our own private data. + let list_lru = unsafe { (*shrink).private_data.cast::<bindings::list_lru>() }; + // SAFETY: Caller guarantees that it is safe to read this field. + let nr_to_scan = unsafe { (*sc).nr_to_scan }; + // SAFETY: Accessing the lru list is okay. Just an FFI call. + unsafe { + bindings::list_lru_walk( + list_lru, + Some(bindings::rust_shrink_free_page_wrap), + ptr::null_mut(), + nr_to_scan, + ) + } +} + +const LRU_SKIP: bindings::lru_status = bindings::lru_status_LRU_SKIP; +const LRU_REMOVED_ENTRY: bindings::lru_status = bindings::lru_status_LRU_REMOVED_RETRY; + +/// # Safety +/// Called by the shrinker. +#[no_mangle] +unsafe extern "C" fn rust_shrink_free_page( + item: *mut bindings::list_head, + lru: *mut bindings::list_lru_one, + _cb_arg: *mut c_void, +) -> bindings::lru_status { + // Fields that should survive after unlocking the lru lock. + let page; + let page_index; + let mm; + let mmap_read; + let mm_mutex; + let vma_addr; + + { + // CAST: The `list_head` field is first in `PageInfo`. + let info = item as *mut PageInfo; + // SAFETY: The `range` field of `PageInfo` is immutable. + let range = unsafe { &*((*info).range) }; + + mm = match range.mm.mmget_not_zero() { + Some(mm) => MmWithUser::into_mmput_async(mm), + None => return LRU_SKIP, + }; + + mm_mutex = match range.stable_trylock_mm() { + Some(guard) => guard, + None => return LRU_SKIP, + }; + + mmap_read = match mm.mmap_read_trylock() { + Some(guard) => guard, + None => return LRU_SKIP, + }; + + // We can't lock it normally here, since we hold the lru lock. + let inner = match range.lock.try_lock() { + Some(inner) => inner, + None => return LRU_SKIP, + }; + + // SAFETY: The item is in this lru list, so it's okay to remove it. + unsafe { bindings::list_lru_isolate(lru, item) }; + + // SAFETY: Both pointers are in bounds of the same allocation. + page_index = unsafe { info.offset_from(inner.pages) } as usize; + + // SAFETY: We hold the spinlock, so we can take the page. + // + // This sets the page pointer to zero before we unmap it from the vma. However, we call + // `zap_page_range` before we release the mmap lock, so `use_page_slow` will not be able to + // insert a new page until after our call to `zap_page_range`. + page = unsafe { PageInfo::take_page(info) }; + vma_addr = inner.vma_addr; + + // From this point on, we don't access this PageInfo or ShrinkablePageRange again, because + // they can be freed at any point after we unlock `lru_lock`. This is with the exception of + // `mm_mutex` which is kept alive by holding the lock. + } + + // SAFETY: The lru lock is locked when this method is called. + unsafe { bindings::spin_unlock(&raw mut (*lru).lock) }; + + if let Some(vma) = mmap_read.vma_lookup(vma_addr) { + let user_page_addr = vma_addr + (page_index << PAGE_SHIFT); + vma.zap_page_range_single(user_page_addr, PAGE_SIZE); + } + + drop(mmap_read); + drop(mm_mutex); + drop(mm); + drop(page); + + // SAFETY: We just unlocked the lru lock, but it should be locked when we return. + unsafe { bindings::spin_lock(&raw mut (*lru).lock) }; + + LRU_REMOVED_ENTRY +} diff --git a/drivers/android/binder/page_range_helper.c b/drivers/android/binder/page_range_helper.c new file mode 100644 index 000000000000..496887723ee0 --- /dev/null +++ b/drivers/android/binder/page_range_helper.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* C helper for page_range.rs to work around a CFI violation. + * + * Bindgen currently pretends that `enum lru_status` is the same as an integer. + * This assumption is fine ABI-wise, but once you add CFI to the mix, it + * triggers a CFI violation because `enum lru_status` gets a different CFI tag. + * + * This file contains a workaround until bindgen can be fixed. + * + * Copyright (C) 2025 Google LLC. + */ +#include "page_range_helper.h" + +unsigned int rust_shrink_free_page(struct list_head *item, + struct list_lru_one *list, + void *cb_arg); + +enum lru_status +rust_shrink_free_page_wrap(struct list_head *item, struct list_lru_one *list, + void *cb_arg) +{ + return rust_shrink_free_page(item, list, cb_arg); +} diff --git a/drivers/android/binder/page_range_helper.h b/drivers/android/binder/page_range_helper.h new file mode 100644 index 000000000000..18dd2dd117b2 --- /dev/null +++ b/drivers/android/binder/page_range_helper.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2025 Google, Inc. + */ + +#ifndef _LINUX_PAGE_RANGE_HELPER_H +#define _LINUX_PAGE_RANGE_HELPER_H + +#include <linux/list_lru.h> + +enum lru_status +rust_shrink_free_page_wrap(struct list_head *item, struct list_lru_one *list, + void *cb_arg); + +#endif /* _LINUX_PAGE_RANGE_HELPER_H */ diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs new file mode 100644 index 000000000000..f13a747e784c --- /dev/null +++ b/drivers/android/binder/process.rs @@ -0,0 +1,1696 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! This module defines the `Process` type, which represents a process using a particular binder +//! context. +//! +//! The `Process` object keeps track of all of the resources that this process owns in the binder +//! context. +//! +//! There is one `Process` object for each binder fd that a process has opened, so processes using +//! several binder contexts have several `Process` objects. This ensures that the contexts are +//! fully separated. + +use core::mem::take; + +use kernel::{ + bindings, + cred::Credential, + error::Error, + fs::file::{self, File}, + list::{List, ListArc, ListArcField, ListLinks}, + mm, + prelude::*, + rbtree::{self, RBTree, RBTreeNode, RBTreeNodeReservation}, + seq_file::SeqFile, + seq_print, + sync::poll::PollTable, + sync::{ + lock::{spinlock::SpinLockBackend, Guard}, + Arc, ArcBorrow, CondVar, CondVarTimeoutResult, Mutex, SpinLock, UniqueArc, + }, + task::Task, + types::ARef, + uaccess::{UserSlice, UserSliceReader}, + uapi, + workqueue::{self, Work}, +}; + +use crate::{ + allocation::{Allocation, AllocationInfo, NewAllocation}, + context::Context, + defs::*, + error::{BinderError, BinderResult}, + node::{CouldNotDeliverCriticalIncrement, CritIncrWrapper, Node, NodeDeath, NodeRef}, + page_range::ShrinkablePageRange, + range_alloc::{RangeAllocator, ReserveNew, ReserveNewArgs}, + stats::BinderStats, + thread::{PushWorkRes, Thread}, + BinderfsProcFile, DArc, DLArc, DTRWrap, DeliverToRead, +}; + +#[path = "freeze.rs"] +mod freeze; +use self::freeze::{FreezeCookie, FreezeListener}; + +struct Mapping { + address: usize, + alloc: RangeAllocator<AllocationInfo>, +} + +impl Mapping { + fn new(address: usize, size: usize) -> Self { + Self { + address, + alloc: RangeAllocator::new(size), + } + } +} + +// bitflags for defer_work. +const PROC_DEFER_FLUSH: u8 = 1; +const PROC_DEFER_RELEASE: u8 = 2; + +/// The fields of `Process` protected by the spinlock. +pub(crate) struct ProcessInner { + is_manager: bool, + pub(crate) is_dead: bool, + threads: RBTree<i32, Arc<Thread>>, + /// INVARIANT: Threads pushed to this list must be owned by this process. + ready_threads: List<Thread>, + nodes: RBTree<u64, DArc<Node>>, + mapping: Option<Mapping>, + work: List<DTRWrap<dyn DeliverToRead>>, + delivered_deaths: List<DTRWrap<NodeDeath>, 2>, + + /// The number of requested threads that haven't registered yet. + requested_thread_count: u32, + /// The maximum number of threads used by the process thread pool. + max_threads: u32, + /// The number of threads the started and registered with the thread pool. + started_thread_count: u32, + + /// Bitmap of deferred work to do. + defer_work: u8, + + /// Number of transactions to be transmitted before processes in freeze_wait + /// are woken up. + outstanding_txns: u32, + /// Process is frozen and unable to service binder transactions. + pub(crate) is_frozen: bool, + /// Process received sync transactions since last frozen. + pub(crate) sync_recv: bool, + /// Process received async transactions since last frozen. + pub(crate) async_recv: bool, + pub(crate) binderfs_file: Option<BinderfsProcFile>, + /// Check for oneway spam + oneway_spam_detection_enabled: bool, +} + +impl ProcessInner { + fn new() -> Self { + Self { + is_manager: false, + is_dead: false, + threads: RBTree::new(), + ready_threads: List::new(), + mapping: None, + nodes: RBTree::new(), + work: List::new(), + delivered_deaths: List::new(), + requested_thread_count: 0, + max_threads: 0, + started_thread_count: 0, + defer_work: 0, + outstanding_txns: 0, + is_frozen: false, + sync_recv: false, + async_recv: false, + binderfs_file: None, + oneway_spam_detection_enabled: false, + } + } + + /// Schedule the work item for execution on this process. + /// + /// If any threads are ready for work, then the work item is given directly to that thread and + /// it is woken up. Otherwise, it is pushed to the process work list. + /// + /// This call can fail only if the process is dead. In this case, the work item is returned to + /// the caller so that the caller can drop it after releasing the inner process lock. This is + /// necessary since the destructor of `Transaction` will take locks that can't necessarily be + /// taken while holding the inner process lock. + pub(crate) fn push_work( + &mut self, + work: DLArc<dyn DeliverToRead>, + ) -> Result<(), (BinderError, DLArc<dyn DeliverToRead>)> { + // Try to find a ready thread to which to push the work. + if let Some(thread) = self.ready_threads.pop_front() { + // Push to thread while holding state lock. This prevents the thread from giving up + // (for example, because of a signal) when we're about to deliver work. + match thread.push_work(work) { + PushWorkRes::Ok => Ok(()), + PushWorkRes::FailedDead(work) => Err((BinderError::new_dead(), work)), + } + } else if self.is_dead { + Err((BinderError::new_dead(), work)) + } else { + let sync = work.should_sync_wakeup(); + + // 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. + self.work.push_back(work); + + // Wake up polling threads, if any. + for thread in self.threads.values() { + thread.notify_if_poll_ready(sync); + } + + Ok(()) + } + } + + pub(crate) fn remove_node(&mut self, ptr: u64) { + self.nodes.remove(&ptr); + } + + /// Updates the reference count on the given node. + pub(crate) fn update_node_refcount( + &mut self, + node: &DArc<Node>, + inc: bool, + strong: bool, + count: usize, + othread: Option<&Thread>, + ) { + let push = node.update_refcount_locked(inc, strong, count, self); + + // If we decided that we need to push work, push either to the process or to a thread if + // one is specified. + if let Some(node) = push { + if let Some(thread) = othread { + thread.push_work_deferred(node); + } else { + let _ = self.push_work(node); + // Nothing to do: `push_work` may fail if the process is dead, but that's ok as in + // that case, it doesn't care about the notification. + } + } + } + + pub(crate) fn new_node_ref( + &mut self, + node: DArc<Node>, + strong: bool, + thread: Option<&Thread>, + ) -> NodeRef { + self.update_node_refcount(&node, true, strong, 1, thread); + let strong_count = if strong { 1 } else { 0 }; + NodeRef::new(node, strong_count, 1 - strong_count) + } + + pub(crate) fn new_node_ref_with_thread( + &mut self, + node: DArc<Node>, + strong: bool, + thread: &Thread, + wrapper: Option<CritIncrWrapper>, + ) -> Result<NodeRef, CouldNotDeliverCriticalIncrement> { + let push = match wrapper { + None => node + .incr_refcount_allow_zero2one(strong, self)? + .map(|node| node as _), + Some(wrapper) => node.incr_refcount_allow_zero2one_with_wrapper(strong, wrapper, self), + }; + if let Some(node) = push { + thread.push_work_deferred(node); + } + let strong_count = if strong { 1 } else { 0 }; + Ok(NodeRef::new(node, strong_count, 1 - strong_count)) + } + + /// Returns an existing node with the given pointer and cookie, if one exists. + /// + /// Returns an error if a node with the given pointer but a different cookie exists. + fn get_existing_node(&self, ptr: u64, cookie: u64) -> Result<Option<DArc<Node>>> { + match self.nodes.get(&ptr) { + None => Ok(None), + Some(node) => { + let (_, node_cookie) = node.get_id(); + if node_cookie == cookie { + Ok(Some(node.clone())) + } else { + Err(EINVAL) + } + } + } + } + + fn register_thread(&mut self) -> bool { + if self.requested_thread_count == 0 { + return false; + } + + self.requested_thread_count -= 1; + self.started_thread_count += 1; + true + } + + /// Finds a delivered death notification with the given cookie, removes it from the thread's + /// delivered list, and returns it. + fn pull_delivered_death(&mut self, cookie: u64) -> Option<DArc<NodeDeath>> { + let mut cursor = self.delivered_deaths.cursor_front(); + while let Some(next) = cursor.peek_next() { + if next.cookie == cookie { + return Some(next.remove().into_arc()); + } + cursor.move_next(); + } + None + } + + pub(crate) fn death_delivered(&mut self, death: DArc<NodeDeath>) { + if let Some(death) = ListArc::try_from_arc_or_drop(death) { + self.delivered_deaths.push_back(death); + } else { + pr_warn!("Notification added to `delivered_deaths` twice."); + } + } + + pub(crate) fn add_outstanding_txn(&mut self) { + self.outstanding_txns += 1; + } + + fn txns_pending_locked(&self) -> bool { + if self.outstanding_txns > 0 { + return true; + } + for thread in self.threads.values() { + if thread.has_current_transaction() { + return true; + } + } + false + } +} + +/// Used to keep track of a node that this process has a handle to. +#[pin_data] +pub(crate) struct NodeRefInfo { + debug_id: usize, + /// The refcount that this process owns to the node. + node_ref: ListArcField<NodeRef, { Self::LIST_PROC }>, + death: ListArcField<Option<DArc<NodeDeath>>, { Self::LIST_PROC }>, + /// Cookie of the active freeze listener for this node. + freeze: ListArcField<Option<FreezeCookie>, { Self::LIST_PROC }>, + /// Used to store this `NodeRefInfo` in the node's `refs` list. + #[pin] + links: ListLinks<{ Self::LIST_NODE }>, + /// The handle for this `NodeRefInfo`. + handle: u32, + /// The process that has a handle to the node. + pub(crate) process: Arc<Process>, +} + +impl NodeRefInfo { + /// The id used for the `Node::refs` list. + pub(crate) const LIST_NODE: u64 = 0x2da16350fb724a10; + /// The id used for the `ListArc` in `ProcessNodeRefs`. + const LIST_PROC: u64 = 0xd703a5263dcc8650; + + fn new(node_ref: NodeRef, handle: u32, process: Arc<Process>) -> impl PinInit<Self> { + pin_init!(Self { + debug_id: super::next_debug_id(), + node_ref: ListArcField::new(node_ref), + death: ListArcField::new(None), + freeze: ListArcField::new(None), + links <- ListLinks::new(), + handle, + process, + }) + } + + kernel::list::define_list_arc_field_getter! { + pub(crate) fn death(&mut self<{Self::LIST_PROC}>) -> &mut Option<DArc<NodeDeath>> { death } + pub(crate) fn freeze(&mut self<{Self::LIST_PROC}>) -> &mut Option<FreezeCookie> { freeze } + pub(crate) fn node_ref(&mut self<{Self::LIST_PROC}>) -> &mut NodeRef { node_ref } + pub(crate) fn node_ref2(&self<{Self::LIST_PROC}>) -> &NodeRef { node_ref } + } +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<{Self::LIST_NODE}> for NodeRefInfo { untracked; } + impl ListArcSafe<{Self::LIST_PROC}> for NodeRefInfo { untracked; } +} +kernel::list::impl_list_item! { + impl ListItem<{Self::LIST_NODE}> for NodeRefInfo { + using ListLinks { self.links }; + } +} + +/// Keeps track of references this process has to nodes owned by other processes. +/// +/// TODO: Currently, the rbtree requires two allocations per node reference, and two tree +/// traversals to look up a node by `Node::global_id`. Once the rbtree is more powerful, these +/// extra costs should be eliminated. +struct ProcessNodeRefs { + /// Used to look up nodes using the 32-bit id that this process knows it by. + by_handle: RBTree<u32, ListArc<NodeRefInfo, { NodeRefInfo::LIST_PROC }>>, + /// Used to look up nodes without knowing their local 32-bit id. The usize is the address of + /// the underlying `Node` struct as returned by `Node::global_id`. + by_node: RBTree<usize, u32>, + /// Used to look up a `FreezeListener` by cookie. + /// + /// There might be multiple freeze listeners for the same node, but at most one of them is + /// active. + freeze_listeners: RBTree<FreezeCookie, FreezeListener>, +} + +impl ProcessNodeRefs { + fn new() -> Self { + Self { + by_handle: RBTree::new(), + by_node: RBTree::new(), + freeze_listeners: RBTree::new(), + } + } +} + +/// A process using binder. +/// +/// Strictly speaking, there can be multiple of these per process. There is one for each binder fd +/// that a process has opened, so processes using several binder contexts have several `Process` +/// objects. This ensures that the contexts are fully separated. +#[pin_data] +pub(crate) struct Process { + pub(crate) ctx: Arc<Context>, + + // The task leader (process). + pub(crate) task: ARef<Task>, + + // Credential associated with file when `Process` is created. + pub(crate) cred: ARef<Credential>, + + #[pin] + pub(crate) inner: SpinLock<ProcessInner>, + + #[pin] + pub(crate) pages: ShrinkablePageRange, + + // Waitqueue of processes waiting for all outstanding transactions to be + // processed. + #[pin] + freeze_wait: CondVar, + + // Node references are in a different lock to avoid recursive acquisition when + // incrementing/decrementing a node in another process. + #[pin] + node_refs: Mutex<ProcessNodeRefs>, + + // Work node for deferred work item. + #[pin] + defer_work: Work<Process>, + + // Links for process list in Context. + #[pin] + links: ListLinks, + + pub(crate) stats: BinderStats, +} + +kernel::impl_has_work! { + impl HasWork<Process> for Process { self.defer_work } +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for Process { untracked; } +} +kernel::list::impl_list_item! { + impl ListItem<0> for Process { + using ListLinks { self.links }; + } +} + +impl workqueue::WorkItem for Process { + type Pointer = Arc<Process>; + + fn run(me: Arc<Self>) { + let defer; + { + let mut inner = me.inner.lock(); + defer = inner.defer_work; + inner.defer_work = 0; + } + + if defer & PROC_DEFER_FLUSH != 0 { + me.deferred_flush(); + } + if defer & PROC_DEFER_RELEASE != 0 { + me.deferred_release(); + } + } +} + +impl Process { + fn new(ctx: Arc<Context>, cred: ARef<Credential>) -> Result<Arc<Self>> { + let current = kernel::current!(); + let list_process = ListArc::pin_init::<Error>( + try_pin_init!(Process { + ctx, + cred, + inner <- kernel::new_spinlock!(ProcessInner::new(), "Process::inner"), + pages <- ShrinkablePageRange::new(&super::BINDER_SHRINKER), + node_refs <- kernel::new_mutex!(ProcessNodeRefs::new(), "Process::node_refs"), + freeze_wait <- kernel::new_condvar!("Process::freeze_wait"), + task: current.group_leader().into(), + defer_work <- kernel::new_work!("Process::defer_work"), + links <- ListLinks::new(), + stats: BinderStats::new(), + }), + GFP_KERNEL, + )?; + + let process = list_process.clone_arc(); + process.ctx.register_process(list_process); + + Ok(process) + } + + pub(crate) fn pid_in_current_ns(&self) -> kernel::task::Pid { + self.task.tgid_nr_ns(None) + } + + #[inline(never)] + 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); + + let inner = self.inner.lock(); + seq_print!(m, " threads: {}\n", inner.threads.iter().count()); + seq_print!( + m, + " requested threads: {}+{}/{}\n", + inner.requested_thread_count, + inner.started_thread_count, + inner.max_threads, + ); + if let Some(mapping) = &inner.mapping { + seq_print!( + m, + " free oneway space: {}\n", + mapping.alloc.free_oneway_space() + ); + seq_print!(m, " buffers: {}\n", mapping.alloc.count_buffers()); + } + seq_print!( + m, + " outstanding transactions: {}\n", + inner.outstanding_txns + ); + seq_print!(m, " nodes: {}\n", inner.nodes.iter().count()); + drop(inner); + + { + let mut refs = self.node_refs.lock(); + let (mut count, mut weak, mut strong) = (0, 0, 0); + for r in refs.by_handle.values_mut() { + let node_ref = r.node_ref(); + let (nstrong, nweak) = node_ref.get_count(); + count += 1; + weak += nweak; + strong += nstrong; + } + seq_print!(m, " refs: {count} s {strong} w {weak}\n"); + } + + self.stats.debug_print(" ", m); + + Ok(()) + } + + #[inline(never)] + 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); + + let mut all_threads = KVec::new(); + let mut all_nodes = KVec::new(); + loop { + let inner = self.inner.lock(); + let num_threads = inner.threads.iter().count(); + let num_nodes = inner.nodes.iter().count(); + + if all_threads.capacity() < num_threads || all_nodes.capacity() < num_nodes { + drop(inner); + all_threads.reserve(num_threads, GFP_KERNEL)?; + all_nodes.reserve(num_nodes, GFP_KERNEL)?; + continue; + } + + for thread in inner.threads.values() { + assert!(all_threads.len() < all_threads.capacity()); + let _ = all_threads.push(thread.clone(), GFP_ATOMIC); + } + + for node in inner.nodes.values() { + assert!(all_nodes.len() < all_nodes.capacity()); + let _ = all_nodes.push(node.clone(), GFP_ATOMIC); + } + + break; + } + + for thread in all_threads { + thread.debug_print(m, print_all)?; + } + + let mut inner = self.inner.lock(); + for node in all_nodes { + if print_all || node.has_oneway_transaction(&mut inner) { + node.full_debug_print(m, &mut inner)?; + } + } + drop(inner); + + if print_all { + let mut refs = self.node_refs.lock(); + for r in refs.by_handle.values_mut() { + let node_ref = r.node_ref(); + let dead = node_ref.node.owner.inner.lock().is_dead; + let (strong, weak) = node_ref.get_count(); + let debug_id = node_ref.node.debug_id; + + seq_print!( + m, + " ref {}: desc {} {}node {debug_id} s {strong} w {weak}", + r.debug_id, + r.handle, + if dead { "dead " } else { "" }, + ); + } + } + + let inner = self.inner.lock(); + for work in &inner.work { + work.debug_print(m, " ", " pending transaction ")?; + } + for _death in &inner.delivered_deaths { + seq_print!(m, " has delivered dead binder\n"); + } + if let Some(mapping) = &inner.mapping { + mapping.alloc.debug_print(m)?; + } + drop(inner); + + Ok(()) + } + + /// Attempts to fetch a work item from the process queue. + pub(crate) fn get_work(&self) -> Option<DLArc<dyn DeliverToRead>> { + self.inner.lock().work.pop_front() + } + + /// Attempts to fetch a work item from the process queue. If none is available, it registers the + /// given thread as ready to receive work directly. + /// + /// This must only be called when the thread is not participating in a transaction chain; when + /// it is, work will always be delivered directly to the thread (and not through the process + /// queue). + pub(crate) fn get_work_or_register<'a>( + &'a self, + thread: &'a Arc<Thread>, + ) -> GetWorkOrRegister<'a> { + let mut inner = self.inner.lock(); + // Try to get work from the process queue. + if let Some(work) = inner.work.pop_front() { + return GetWorkOrRegister::Work(work); + } + + // Register the thread as ready. + GetWorkOrRegister::Register(Registration::new(thread, &mut inner)) + } + + fn get_current_thread(self: ArcBorrow<'_, Self>) -> Result<Arc<Thread>> { + let id = { + let current = kernel::current!(); + if !core::ptr::eq(current.group_leader(), &*self.task) { + pr_err!("get_current_thread was called from the wrong process."); + return Err(EINVAL); + } + current.pid() + }; + + { + let inner = self.inner.lock(); + if let Some(thread) = inner.threads.get(&id) { + return Ok(thread.clone()); + } + } + + // Allocate a new `Thread` without holding any locks. + let reservation = RBTreeNodeReservation::new(GFP_KERNEL)?; + let ta: Arc<Thread> = Thread::new(id, self.into())?; + + let mut inner = self.inner.lock(); + match inner.threads.entry(id) { + rbtree::Entry::Vacant(entry) => { + entry.insert(ta.clone(), reservation); + Ok(ta) + } + rbtree::Entry::Occupied(_entry) => { + pr_err!("Cannot create two threads with the same id."); + Err(EINVAL) + } + } + } + + pub(crate) fn push_work(&self, work: DLArc<dyn DeliverToRead>) -> BinderResult { + // If push_work fails, drop the work item outside the lock. + let res = self.inner.lock().push_work(work); + match res { + Ok(()) => Ok(()), + Err((err, work)) => { + drop(work); + Err(err) + } + } + } + + fn set_as_manager( + self: ArcBorrow<'_, Self>, + info: Option<FlatBinderObject>, + thread: &Thread, + ) -> Result { + let (ptr, cookie, flags) = if let Some(obj) = info { + ( + // SAFETY: The object type for this ioctl is implicitly `BINDER_TYPE_BINDER`, so it + // is safe to access the `binder` field. + unsafe { obj.__bindgen_anon_1.binder }, + obj.cookie, + obj.flags, + ) + } else { + (0, 0, 0) + }; + let node_ref = self.get_node(ptr, cookie, flags as _, true, thread)?; + let node = node_ref.node.clone(); + self.ctx.set_manager_node(node_ref)?; + self.inner.lock().is_manager = true; + + // Force the state of the node to prevent the delivery of acquire/increfs. + let mut owner_inner = node.owner.inner.lock(); + node.force_has_count(&mut owner_inner); + Ok(()) + } + + fn get_node_inner( + self: ArcBorrow<'_, Self>, + ptr: u64, + cookie: u64, + flags: u32, + strong: bool, + thread: &Thread, + wrapper: Option<CritIncrWrapper>, + ) -> Result<Result<NodeRef, CouldNotDeliverCriticalIncrement>> { + // Try to find an existing node. + { + let mut inner = self.inner.lock(); + if let Some(node) = inner.get_existing_node(ptr, cookie)? { + return Ok(inner.new_node_ref_with_thread(node, strong, thread, wrapper)); + } + } + + // Allocate the node before reacquiring the lock. + let node = DTRWrap::arc_pin_init(Node::new(ptr, cookie, flags, self.into()))?.into_arc(); + let rbnode = RBTreeNode::new(ptr, node.clone(), GFP_KERNEL)?; + let mut inner = self.inner.lock(); + if let Some(node) = inner.get_existing_node(ptr, cookie)? { + return Ok(inner.new_node_ref_with_thread(node, strong, thread, wrapper)); + } + + inner.nodes.insert(rbnode); + // This can only fail if someone has already pushed the node to a list, but we just created + // it and still hold the lock, so it can't fail right now. + let node_ref = inner + .new_node_ref_with_thread(node, strong, thread, wrapper) + .unwrap(); + + Ok(Ok(node_ref)) + } + + pub(crate) fn get_node( + self: ArcBorrow<'_, Self>, + ptr: u64, + cookie: u64, + flags: u32, + strong: bool, + thread: &Thread, + ) -> Result<NodeRef> { + let mut wrapper = None; + for _ in 0..2 { + match self.get_node_inner(ptr, cookie, flags, strong, thread, wrapper) { + Err(err) => return Err(err), + Ok(Ok(node_ref)) => return Ok(node_ref), + Ok(Err(CouldNotDeliverCriticalIncrement)) => { + wrapper = Some(CritIncrWrapper::new()?); + } + } + } + // We only get a `CouldNotDeliverCriticalIncrement` error if `wrapper` is `None`, so the + // loop should run at most twice. + unreachable!() + } + + pub(crate) fn insert_or_update_handle( + self: ArcBorrow<'_, Process>, + node_ref: NodeRef, + is_mananger: bool, + ) -> Result<u32> { + { + let mut refs = self.node_refs.lock(); + + // Do a lookup before inserting. + if let Some(handle_ref) = refs.by_node.get(&node_ref.node.global_id()) { + let handle = *handle_ref; + let info = refs.by_handle.get_mut(&handle).unwrap(); + info.node_ref().absorb(node_ref); + return Ok(handle); + } + } + + // Reserve memory for tree nodes. + let reserve1 = RBTreeNodeReservation::new(GFP_KERNEL)?; + let reserve2 = RBTreeNodeReservation::new(GFP_KERNEL)?; + let info = UniqueArc::new_uninit(GFP_KERNEL)?; + + let mut refs = self.node_refs.lock(); + + // Do a lookup again as node may have been inserted before the lock was reacquired. + if let Some(handle_ref) = refs.by_node.get(&node_ref.node.global_id()) { + let handle = *handle_ref; + let info = refs.by_handle.get_mut(&handle).unwrap(); + info.node_ref().absorb(node_ref); + return Ok(handle); + } + + // Find id. + let mut target: u32 = if is_mananger { 0 } else { 1 }; + for handle in refs.by_handle.keys() { + if *handle > target { + break; + } + if *handle == target { + target = target.checked_add(1).ok_or(ENOMEM)?; + } + } + + let gid = node_ref.node.global_id(); + let (info_proc, info_node) = { + let info_init = NodeRefInfo::new(node_ref, target, self.into()); + match info.pin_init_with(info_init) { + Ok(info) => ListArc::pair_from_pin_unique(info), + // error is infallible + Err(err) => match err {}, + } + }; + + // Ensure the process is still alive while we insert a new reference. + // + // This releases the lock before inserting the nodes, but since `is_dead` is set as the + // first thing in `deferred_release`, process cleanup will not miss the items inserted into + // `refs` below. + if self.inner.lock().is_dead { + return Err(ESRCH); + } + + // SAFETY: `info_proc` and `info_node` reference the same node, so we are inserting + // `info_node` into the right node's `refs` list. + unsafe { info_proc.node_ref2().node.insert_node_info(info_node) }; + + refs.by_node.insert(reserve1.into_node(gid, target)); + refs.by_handle.insert(reserve2.into_node(target, info_proc)); + Ok(target) + } + + pub(crate) fn get_transaction_node(&self, handle: u32) -> BinderResult<NodeRef> { + // When handle is zero, try to get the context manager. + if handle == 0 { + Ok(self.ctx.get_manager_node(true)?) + } else { + Ok(self.get_node_from_handle(handle, true)?) + } + } + + pub(crate) fn get_node_from_handle(&self, handle: u32, strong: bool) -> Result<NodeRef> { + self.node_refs + .lock() + .by_handle + .get_mut(&handle) + .ok_or(ENOENT)? + .node_ref() + .clone(strong) + } + + pub(crate) fn remove_from_delivered_deaths(&self, death: &DArc<NodeDeath>) { + let mut inner = self.inner.lock(); + // SAFETY: By the invariant on the `delivered_links` field, this is the right linked list. + let removed = unsafe { inner.delivered_deaths.remove(death) }; + drop(inner); + drop(removed); + } + + pub(crate) fn update_ref( + self: ArcBorrow<'_, Process>, + handle: u32, + inc: bool, + strong: bool, + ) -> Result { + if inc && handle == 0 { + if let Ok(node_ref) = self.ctx.get_manager_node(strong) { + if core::ptr::eq(&*self, &*node_ref.node.owner) { + return Err(EINVAL); + } + let _ = self.insert_or_update_handle(node_ref, true); + return Ok(()); + } + } + + // To preserve original binder behaviour, we only fail requests where the manager tries to + // increment references on itself. + let mut refs = self.node_refs.lock(); + if let Some(info) = refs.by_handle.get_mut(&handle) { + if info.node_ref().update(inc, strong) { + // Clean up death if there is one attached to this node reference. + if let Some(death) = info.death().take() { + death.set_cleared(true); + self.remove_from_delivered_deaths(&death); + } + + // Remove reference from process tables, and from the node's `refs` list. + + // SAFETY: We are removing the `NodeRefInfo` from the right node. + unsafe { info.node_ref2().node.remove_node_info(info) }; + + let id = info.node_ref().node.global_id(); + refs.by_handle.remove(&handle); + refs.by_node.remove(&id); + } + } else { + // All refs are cleared in process exit, so this warning is expected in that case. + if !self.inner.lock().is_dead { + pr_warn!("{}: no such ref {handle}\n", self.pid_in_current_ns()); + } + } + Ok(()) + } + + /// Decrements the refcount of the given node, if one exists. + pub(crate) fn update_node(&self, ptr: u64, cookie: u64, strong: bool) { + let mut inner = self.inner.lock(); + if let Ok(Some(node)) = inner.get_existing_node(ptr, cookie) { + inner.update_node_refcount(&node, false, strong, 1, None); + } + } + + pub(crate) fn inc_ref_done(&self, reader: &mut UserSliceReader, strong: bool) -> Result { + let ptr = reader.read::<u64>()?; + let cookie = reader.read::<u64>()?; + let mut inner = self.inner.lock(); + if let Ok(Some(node)) = inner.get_existing_node(ptr, cookie) { + if let Some(node) = node.inc_ref_done_locked(strong, &mut inner) { + // This only fails if the process is dead. + let _ = inner.push_work(node); + } + } + Ok(()) + } + + pub(crate) fn buffer_alloc( + self: &Arc<Self>, + debug_id: usize, + size: usize, + is_oneway: bool, + from_pid: i32, + ) -> BinderResult<NewAllocation> { + use kernel::page::PAGE_SIZE; + + let mut reserve_new_args = ReserveNewArgs { + debug_id, + size, + is_oneway, + pid: from_pid, + ..ReserveNewArgs::default() + }; + + let (new_alloc, addr) = loop { + let mut inner = self.inner.lock(); + 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, + }; + drop(inner); + // We need to allocate memory and then call `reserve_new` again. + reserve_new_args = alloc_request.make_alloc()?; + }; + + let res = Allocation::new( + self.clone(), + debug_id, + new_alloc.offset, + size, + addr + new_alloc.offset, + new_alloc.oneway_spam_detected, + ); + + // This allocation will be marked as in use until the `Allocation` is used to free it. + // + // This method can't be called while holding a lock, so we release the lock first. It's + // okay for several threads to use the method on the same index at the same time. In that + // case, one of the calls will allocate the given page (if missing), and the other call + // will wait for the other call to finish allocating the page. + // + // We will not call `stop_using_range` in parallel with this on the same page, because the + // allocation can only be removed via the destructor of the `Allocation` object that we + // currently own. + match self.pages.use_range( + new_alloc.offset / PAGE_SIZE, + (new_alloc.offset + size).div_ceil(PAGE_SIZE), + ) { + Ok(()) => {} + Err(err) => { + pr_warn!("use_range failure {:?}", err); + return Err(err.into()); + } + } + + Ok(NewAllocation(res)) + } + + pub(crate) fn buffer_get(self: &Arc<Self>, ptr: usize) -> Option<Allocation> { + let mut inner = self.inner.lock(); + let mapping = inner.mapping.as_mut()?; + let offset = ptr.checked_sub(mapping.address)?; + let (size, debug_id, odata) = mapping.alloc.reserve_existing(offset).ok()?; + let mut alloc = Allocation::new(self.clone(), debug_id, offset, size, ptr, false); + if let Some(data) = odata { + alloc.set_info(data); + } + Some(alloc) + } + + pub(crate) fn buffer_raw_free(&self, ptr: usize) { + let mut inner = self.inner.lock(); + if let Some(ref mut mapping) = &mut inner.mapping { + let offset = match ptr.checked_sub(mapping.address) { + Some(offset) => offset, + None => return, + }; + + let freed_range = match mapping.alloc.reservation_abort(offset) { + Ok(freed_range) => freed_range, + Err(_) => { + pr_warn!( + "Pointer {:x} failed to free, base = {:x}\n", + ptr, + mapping.address + ); + return; + } + }; + + // No more allocations in this range. Mark them as not in use. + // + // Must be done before we release the lock so that `use_range` is not used on these + // indices until `stop_using_range` returns. + self.pages + .stop_using_range(freed_range.start_page_idx, freed_range.end_page_idx); + } + } + + pub(crate) fn buffer_make_freeable(&self, offset: usize, mut data: Option<AllocationInfo>) { + let mut inner = self.inner.lock(); + if let Some(ref mut mapping) = &mut inner.mapping { + if mapping.alloc.reservation_commit(offset, &mut data).is_err() { + pr_warn!("Offset {} failed to be marked freeable\n", offset); + } + } + } + + fn create_mapping(&self, vma: &mm::virt::VmaNew) -> Result { + use kernel::page::PAGE_SIZE; + let size = usize::min(vma.end() - vma.start(), bindings::SZ_4M as usize); + let mapping = Mapping::new(vma.start(), size); + let page_count = self.pages.register_with_vma(vma)?; + if page_count * PAGE_SIZE != size { + return Err(EINVAL); + } + + // Save range allocator for later. + self.inner.lock().mapping = Some(mapping); + + Ok(()) + } + + fn version(&self, data: UserSlice) -> Result { + data.writer().write(&BinderVersion::current()) + } + + pub(crate) fn register_thread(&self) -> bool { + self.inner.lock().register_thread() + } + + fn remove_thread(&self, thread: Arc<Thread>) { + self.inner.lock().threads.remove(&thread.id); + thread.release(); + } + + fn set_max_threads(&self, max: u32) { + self.inner.lock().max_threads = max; + } + + fn set_oneway_spam_detection_enabled(&self, enabled: u32) { + self.inner.lock().oneway_spam_detection_enabled = enabled != 0; + } + + pub(crate) fn is_oneway_spam_detection_enabled(&self) -> bool { + self.inner.lock().oneway_spam_detection_enabled + } + + fn get_node_debug_info(&self, data: UserSlice) -> Result { + let (mut reader, mut writer) = data.reader_writer(); + + // Read the starting point. + let ptr = reader.read::<BinderNodeDebugInfo>()?.ptr; + let mut out = BinderNodeDebugInfo::default(); + + { + let inner = self.inner.lock(); + for (node_ptr, node) in &inner.nodes { + if *node_ptr > ptr { + node.populate_debug_info(&mut out, &inner); + break; + } + } + } + + writer.write(&out) + } + + fn get_node_info_from_ref(&self, data: UserSlice) -> Result { + let (mut reader, mut writer) = data.reader_writer(); + let mut out = reader.read::<BinderNodeInfoForRef>()?; + + if out.strong_count != 0 + || out.weak_count != 0 + || out.reserved1 != 0 + || out.reserved2 != 0 + || out.reserved3 != 0 + { + return Err(EINVAL); + } + + // Only the context manager is allowed to use this ioctl. + if !self.inner.lock().is_manager { + return Err(EPERM); + } + + { + let mut node_refs = self.node_refs.lock(); + let node_info = node_refs.by_handle.get_mut(&out.handle).ok_or(ENOENT)?; + let node_ref = node_info.node_ref(); + let owner_inner = node_ref.node.owner.inner.lock(); + node_ref.node.populate_counts(&mut out, &owner_inner); + } + + // Write the result back. + writer.write(&out) + } + + pub(crate) fn needs_thread(&self) -> bool { + let mut inner = self.inner.lock(); + let ret = inner.requested_thread_count == 0 + && inner.ready_threads.is_empty() + && inner.started_thread_count < inner.max_threads; + if ret { + inner.requested_thread_count += 1 + } + ret + } + + pub(crate) fn request_death( + self: &Arc<Self>, + reader: &mut UserSliceReader, + thread: &Thread, + ) -> Result { + let handle: u32 = reader.read()?; + let cookie: u64 = reader.read()?; + + // Queue BR_ERROR if we can't allocate memory for the death notification. + let death = UniqueArc::new_uninit(GFP_KERNEL).inspect_err(|_| { + thread.push_return_work(BR_ERROR); + })?; + let mut refs = self.node_refs.lock(); + let Some(info) = refs.by_handle.get_mut(&handle) else { + pr_warn!("BC_REQUEST_DEATH_NOTIFICATION invalid ref {handle}\n"); + return Ok(()); + }; + + // Nothing to do if there is already a death notification request for this handle. + if info.death().is_some() { + pr_warn!("BC_REQUEST_DEATH_NOTIFICATION death notification already set\n"); + 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 {}, + } + }; + + // Register the death notification. + { + let owner = info.node_ref2().node.owner.clone(); + let mut owner_inner = owner.inner.lock(); + if owner_inner.is_dead { + let death = Arc::from(death); + *info.death() = Some(death.clone()); + drop(owner_inner); + death.set_dead(); + } else { + let death = ListArc::from(death); + *info.death() = Some(death.clone_arc()); + info.node_ref().node.add_death(death, &mut owner_inner); + } + } + Ok(()) + } + + pub(crate) fn clear_death(&self, reader: &mut UserSliceReader, thread: &Thread) -> Result { + let handle: u32 = reader.read()?; + let cookie: u64 = reader.read()?; + + let mut refs = self.node_refs.lock(); + let Some(info) = refs.by_handle.get_mut(&handle) else { + pr_warn!("BC_CLEAR_DEATH_NOTIFICATION invalid ref {handle}\n"); + return Ok(()); + }; + + let Some(death) = info.death().take() else { + pr_warn!("BC_CLEAR_DEATH_NOTIFICATION death notification not active\n"); + return Ok(()); + }; + if death.cookie != cookie { + *info.death() = Some(death); + pr_warn!("BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch\n"); + return Ok(()); + } + + // Update state and determine if we need to queue a work item. We only need to do it when + // the node is not dead or if the user already completed the death notification. + if death.set_cleared(false) { + if let Some(death) = ListArc::try_from_arc_or_drop(death) { + let _ = thread.push_work_if_looper(death); + } + } + + Ok(()) + } + + pub(crate) fn dead_binder_done(&self, cookie: u64, thread: &Thread) { + if let Some(death) = self.inner.lock().pull_delivered_death(cookie) { + death.set_notification_done(thread); + } + } + + /// Locks the spinlock and move the `nodes` rbtree out. + /// + /// This allows you to iterate through `nodes` while also allowing you to give other parts of + /// the codebase exclusive access to `ProcessInner`. + pub(crate) fn lock_with_nodes(&self) -> WithNodes<'_> { + let mut inner = self.inner.lock(); + WithNodes { + nodes: take(&mut inner.nodes), + inner, + } + } + + fn deferred_flush(&self) { + let inner = self.inner.lock(); + for thread in inner.threads.values() { + thread.exit_looper(); + } + } + + fn deferred_release(self: Arc<Self>) { + let is_manager = { + let mut inner = self.inner.lock(); + inner.is_dead = true; + inner.is_frozen = false; + inner.sync_recv = false; + inner.async_recv = false; + inner.is_manager + }; + + if is_manager { + self.ctx.unset_manager_node(); + } + + self.ctx.deregister_process(&self); + + let binderfs_file = self.inner.lock().binderfs_file.take(); + drop(binderfs_file); + + // Release threads. + let threads = { + let mut inner = self.inner.lock(); + let threads = take(&mut inner.threads); + let ready = take(&mut inner.ready_threads); + drop(inner); + drop(ready); + + for thread in threads.values() { + thread.release(); + } + threads + }; + + // Release nodes. + { + while let Some(node) = { + let mut lock = self.inner.lock(); + lock.nodes.cursor_front().map(|c| c.remove_current().1) + } { + node.to_key_value().1.release(); + } + } + + // Clean up death listeners and remove nodes from external node info lists. + for info in self.node_refs.lock().by_handle.values_mut() { + // SAFETY: We are removing the `NodeRefInfo` from the right node. + unsafe { info.node_ref2().node.remove_node_info(info) }; + + // Remove all death notifications from the nodes (that belong to a different process). + let death = if let Some(existing) = info.death().take() { + existing + } else { + continue; + }; + death.set_cleared(false); + } + + // Clean up freeze listeners. + let freeze_listeners = take(&mut self.node_refs.lock().freeze_listeners); + for listener in freeze_listeners.values() { + listener.on_process_exit(&self); + } + drop(freeze_listeners); + + // Release refs on foreign nodes. + { + let mut refs = self.node_refs.lock(); + let by_handle = take(&mut refs.by_handle); + let by_node = take(&mut refs.by_node); + drop(refs); + drop(by_node); + drop(by_handle); + } + + // Cancel all pending work items. + while let Some(work) = self.get_work() { + work.into_arc().cancel(); + } + + let delivered_deaths = take(&mut self.inner.lock().delivered_deaths); + drop(delivered_deaths); + + // Free any resources kept alive by allocated buffers. + let omapping = self.inner.lock().mapping.take(); + if let Some(mut mapping) = omapping { + let address = mapping.address; + mapping + .alloc + .take_for_each(|offset, size, debug_id, odata| { + let ptr = offset + address; + pr_warn!( + "{}: removing orphan mapping {offset}:{size}\n", + self.pid_in_current_ns() + ); + let mut alloc = + Allocation::new(self.clone(), debug_id, offset, size, ptr, false); + if let Some(data) = odata { + alloc.set_info(data); + } + drop(alloc) + }); + } + + // calls to synchronize_rcu() in thread drop will happen here + drop(threads); + } + + pub(crate) fn drop_outstanding_txn(&self) { + let wake = { + let mut inner = self.inner.lock(); + if inner.outstanding_txns == 0 { + pr_err!("outstanding_txns underflow"); + return; + } + inner.outstanding_txns -= 1; + inner.is_frozen && inner.outstanding_txns == 0 + }; + + if wake { + self.freeze_wait.notify_all(); + } + } + + pub(crate) fn ioctl_freeze(&self, info: &BinderFreezeInfo) -> Result { + if info.enable == 0 { + let msgs = self.prepare_freeze_messages()?; + let mut inner = self.inner.lock(); + inner.sync_recv = false; + inner.async_recv = false; + inner.is_frozen = false; + drop(inner); + msgs.send_messages(); + return Ok(()); + } + + let mut inner = self.inner.lock(); + inner.sync_recv = false; + inner.async_recv = false; + inner.is_frozen = true; + + if info.timeout_ms > 0 { + let mut jiffies = kernel::time::msecs_to_jiffies(info.timeout_ms); + while jiffies > 0 { + if inner.outstanding_txns == 0 { + break; + } + + match self + .freeze_wait + .wait_interruptible_timeout(&mut inner, jiffies) + { + CondVarTimeoutResult::Signal { .. } => { + inner.is_frozen = false; + return Err(ERESTARTSYS); + } + CondVarTimeoutResult::Woken { jiffies: remaining } => { + jiffies = remaining; + } + CondVarTimeoutResult::Timeout => { + jiffies = 0; + } + } + } + } + + if inner.txns_pending_locked() { + inner.is_frozen = false; + Err(EAGAIN) + } else { + drop(inner); + match self.prepare_freeze_messages() { + Ok(batch) => { + batch.send_messages(); + Ok(()) + } + Err(kernel::alloc::AllocError) => { + self.inner.lock().is_frozen = false; + Err(ENOMEM) + } + } + } + } +} + +fn get_frozen_status(data: UserSlice) -> Result { + let (mut reader, mut writer) = data.reader_writer(); + + let mut info = reader.read::<BinderFrozenStatusInfo>()?; + info.sync_recv = 0; + info.async_recv = 0; + let mut found = false; + + for ctx in crate::context::get_all_contexts()? { + ctx.for_each_proc(|proc| { + if proc.task.pid() == info.pid as _ { + found = true; + let inner = proc.inner.lock(); + let txns_pending = inner.txns_pending_locked(); + info.async_recv |= inner.async_recv as u32; + info.sync_recv |= inner.sync_recv as u32; + info.sync_recv |= (txns_pending as u32) << 1; + } + }); + } + + if found { + writer.write(&info)?; + Ok(()) + } else { + Err(EINVAL) + } +} + +fn ioctl_freeze(reader: &mut UserSliceReader) -> Result { + let info = reader.read::<BinderFreezeInfo>()?; + + // Very unlikely for there to be more than 3, since a process normally uses at most binder and + // hwbinder. + let mut procs = KVec::with_capacity(3, GFP_KERNEL)?; + + let ctxs = crate::context::get_all_contexts()?; + for ctx in ctxs { + for proc in ctx.get_procs_with_pid(info.pid as i32)? { + procs.push(proc, GFP_KERNEL)?; + } + } + + for proc in procs { + proc.ioctl_freeze(&info)?; + } + Ok(()) +} + +/// The ioctl handler. +impl Process { + /// Ioctls that are write-only from the perspective of userspace. + /// + /// The kernel will only read from the pointer that userspace provided to us. + fn ioctl_write_only( + this: ArcBorrow<'_, Process>, + _file: &File, + cmd: u32, + reader: &mut UserSliceReader, + ) -> Result { + let thread = this.get_current_thread()?; + match cmd { + uapi::BINDER_SET_MAX_THREADS => this.set_max_threads(reader.read()?), + uapi::BINDER_THREAD_EXIT => this.remove_thread(thread), + uapi::BINDER_SET_CONTEXT_MGR => this.set_as_manager(None, &thread)?, + uapi::BINDER_SET_CONTEXT_MGR_EXT => { + this.set_as_manager(Some(reader.read()?), &thread)? + } + uapi::BINDER_ENABLE_ONEWAY_SPAM_DETECTION => { + this.set_oneway_spam_detection_enabled(reader.read()?) + } + uapi::BINDER_FREEZE => ioctl_freeze(reader)?, + _ => return Err(EINVAL), + } + Ok(()) + } + + /// Ioctls that are read/write from the perspective of userspace. + /// + /// The kernel will both read from and write to the pointer that userspace provided to us. + fn ioctl_write_read( + this: ArcBorrow<'_, Process>, + file: &File, + cmd: u32, + data: UserSlice, + ) -> Result { + let thread = this.get_current_thread()?; + let blocking = (file.flags() & file::flags::O_NONBLOCK) == 0; + match cmd { + uapi::BINDER_WRITE_READ => thread.write_read(data, blocking)?, + uapi::BINDER_GET_NODE_DEBUG_INFO => this.get_node_debug_info(data)?, + uapi::BINDER_GET_NODE_INFO_FOR_REF => this.get_node_info_from_ref(data)?, + uapi::BINDER_VERSION => this.version(data)?, + uapi::BINDER_GET_FROZEN_INFO => get_frozen_status(data)?, + uapi::BINDER_GET_EXTENDED_ERROR => thread.get_extended_error(data)?, + _ => return Err(EINVAL), + } + Ok(()) + } +} + +/// The file operations supported by `Process`. +impl Process { + pub(crate) fn open(ctx: ArcBorrow<'_, Context>, file: &File) -> Result<Arc<Process>> { + Self::new(ctx.into(), ARef::from(file.cred())) + } + + pub(crate) fn release(this: Arc<Process>, _file: &File) { + let binderfs_file; + let should_schedule; + { + let mut inner = this.inner.lock(); + should_schedule = inner.defer_work == 0; + inner.defer_work |= PROC_DEFER_RELEASE; + binderfs_file = inner.binderfs_file.take(); + } + + if should_schedule { + // Ignore failures to schedule to the workqueue. Those just mean that we're already + // scheduled for execution. + let _ = workqueue::system().enqueue(this); + } + + drop(binderfs_file); + } + + pub(crate) fn flush(this: ArcBorrow<'_, Process>) -> Result { + let should_schedule; + { + let mut inner = this.inner.lock(); + should_schedule = inner.defer_work == 0; + inner.defer_work |= PROC_DEFER_FLUSH; + } + + if should_schedule { + // Ignore failures to schedule to the workqueue. Those just mean that we're already + // scheduled for execution. + let _ = workqueue::system().enqueue(Arc::from(this)); + } + Ok(()) + } + + pub(crate) fn ioctl(this: ArcBorrow<'_, Process>, file: &File, cmd: u32, arg: usize) -> Result { + use kernel::ioctl::{_IOC_DIR, _IOC_SIZE}; + use kernel::uapi::{_IOC_READ, _IOC_WRITE}; + + crate::trace::trace_ioctl(cmd, arg); + + let user_slice = UserSlice::new(UserPtr::from_addr(arg), _IOC_SIZE(cmd)); + + const _IOC_READ_WRITE: u32 = _IOC_READ | _IOC_WRITE; + + match _IOC_DIR(cmd) { + _IOC_WRITE => Self::ioctl_write_only(this, file, cmd, &mut user_slice.reader()), + _IOC_READ_WRITE => Self::ioctl_write_read(this, file, cmd, user_slice), + _ => Err(EINVAL), + } + } + + pub(crate) fn compat_ioctl( + this: ArcBorrow<'_, Process>, + file: &File, + cmd: u32, + arg: usize, + ) -> Result { + Self::ioctl(this, file, cmd, arg) + } + + pub(crate) fn mmap( + this: ArcBorrow<'_, Process>, + _file: &File, + vma: &mm::virt::VmaNew, + ) -> Result { + // We don't allow mmap to be used in a different process. + if !core::ptr::eq(kernel::current!().group_leader(), &*this.task) { + return Err(EINVAL); + } + if vma.start() == 0 { + return Err(EINVAL); + } + + vma.try_clear_maywrite().map_err(|_| EPERM)?; + vma.set_dontcopy(); + vma.set_mixedmap(); + + // TODO: Set ops. We need to learn when the user unmaps so that we can stop using it. + this.create_mapping(vma) + } + + pub(crate) fn poll( + this: ArcBorrow<'_, Process>, + file: &File, + table: PollTable<'_>, + ) -> Result<u32> { + let thread = this.get_current_thread()?; + let (from_proc, mut mask) = thread.poll(file, table); + if mask == 0 && from_proc && !this.inner.lock().work.is_empty() { + mask |= bindings::POLLIN; + } + Ok(mask) + } +} + +/// Represents that a thread has registered with the `ready_threads` list of its process. +/// +/// The destructor of this type will unregister the thread from the list of ready threads. +pub(crate) struct Registration<'a> { + thread: &'a Arc<Thread>, +} + +impl<'a> Registration<'a> { + fn new(thread: &'a Arc<Thread>, guard: &mut Guard<'_, ProcessInner, SpinLockBackend>) -> Self { + assert!(core::ptr::eq(&thread.process.inner, guard.lock_ref())); + // INVARIANT: We are pushing this thread to the right `ready_threads` list. + if let Ok(list_arc) = ListArc::try_from_arc(thread.clone()) { + guard.ready_threads.push_front(list_arc); + } else { + // It is an error to hit this branch, and it should not be reachable. We try to do + // something reasonable when the failure path happens. Most likely, the thread in + // question will sleep forever. + pr_err!("Same thread registered with `ready_threads` twice."); + } + Self { thread } + } +} + +impl Drop for Registration<'_> { + fn drop(&mut self) { + let mut inner = self.thread.process.inner.lock(); + // SAFETY: The thread has the invariant that we never push it to any other linked list than + // the `ready_threads` list of its parent process. Therefore, the thread is either in that + // list, or in no list. + unsafe { inner.ready_threads.remove(self.thread) }; + } +} + +pub(crate) struct WithNodes<'a> { + pub(crate) inner: Guard<'a, ProcessInner, SpinLockBackend>, + pub(crate) nodes: RBTree<u64, DArc<Node>>, +} + +impl Drop for WithNodes<'_> { + fn drop(&mut self) { + core::mem::swap(&mut self.nodes, &mut self.inner.nodes); + if self.nodes.iter().next().is_some() { + pr_err!("nodes array was modified while using lock_with_nodes\n"); + } + } +} + +pub(crate) enum GetWorkOrRegister<'a> { + Work(DLArc<dyn DeliverToRead>), + Register(Registration<'a>), +} diff --git a/drivers/android/binder/range_alloc/array.rs b/drivers/android/binder/range_alloc/array.rs new file mode 100644 index 000000000000..07e1dec2ce63 --- /dev/null +++ b/drivers/android/binder/range_alloc/array.rs @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{ + page::{PAGE_MASK, PAGE_SIZE}, + prelude::*, + seq_file::SeqFile, + seq_print, + task::Pid, +}; + +use crate::range_alloc::{DescriptorState, FreedRange, Range}; + +/// Keeps track of allocations in a process' mmap. +/// +/// Each process has an mmap where the data for incoming transactions will be placed. This struct +/// keeps track of allocations made in the mmap. For each allocation, we store a descriptor that +/// has metadata related to the allocation. We also keep track of available free space. +pub(super) struct ArrayRangeAllocator<T> { + /// This stores all ranges that are allocated. Unlike the tree based allocator, we do *not* + /// store the free ranges. + /// + /// Sorted by offset. + pub(super) ranges: KVec<Range<T>>, + size: usize, + free_oneway_space: usize, +} + +struct FindEmptyRes { + /// Which index in `ranges` should we insert the new range at? + /// + /// Inserting the new range at this index keeps `ranges` sorted. + insert_at_idx: usize, + /// Which offset should we insert the new range at? + insert_at_offset: usize, +} + +impl<T> ArrayRangeAllocator<T> { + pub(crate) fn new(size: usize, alloc: EmptyArrayAlloc<T>) -> Self { + Self { + ranges: alloc.ranges, + size, + free_oneway_space: size / 2, + } + } + + pub(crate) fn free_oneway_space(&self) -> usize { + self.free_oneway_space + } + + pub(crate) fn count_buffers(&self) -> usize { + self.ranges.len() + } + + pub(crate) fn total_size(&self) -> usize { + self.size + } + + pub(crate) fn is_full(&self) -> bool { + self.ranges.len() == self.ranges.capacity() + } + + pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> { + for range in &self.ranges { + seq_print!( + m, + " buffer {}: {} size {} pid {} oneway {}", + 0, + range.offset, + range.size, + range.state.pid(), + range.state.is_oneway(), + ); + if let DescriptorState::Reserved(_) = range.state { + seq_print!(m, " reserved\n"); + } else { + seq_print!(m, " allocated\n"); + } + } + Ok(()) + } + + /// Find somewhere to put a new range. + /// + /// Unlike the tree implementation, we do not bother to find the smallest gap. The idea is that + /// fragmentation isn't a big issue when we don't have many ranges. + /// + /// Returns the index that the new range should have in `self.ranges` after insertion. + fn find_empty_range(&self, size: usize) -> Option<FindEmptyRes> { + let after_last_range = self.ranges.last().map(Range::endpoint).unwrap_or(0); + + if size <= self.total_size() - after_last_range { + // We can put the range at the end, so just do that. + Some(FindEmptyRes { + insert_at_idx: self.ranges.len(), + insert_at_offset: after_last_range, + }) + } else { + let mut end_of_prev = 0; + for (i, range) in self.ranges.iter().enumerate() { + // Does it fit before the i'th range? + if size <= range.offset - end_of_prev { + return Some(FindEmptyRes { + insert_at_idx: i, + insert_at_offset: end_of_prev, + }); + } + end_of_prev = range.endpoint(); + } + None + } + } + + pub(crate) fn reserve_new( + &mut self, + debug_id: usize, + size: usize, + is_oneway: bool, + pid: Pid, + ) -> Result<usize> { + // Compute new value of free_oneway_space, which is set only on success. + let new_oneway_space = if is_oneway { + match self.free_oneway_space.checked_sub(size) { + Some(new_oneway_space) => new_oneway_space, + None => return Err(ENOSPC), + } + } else { + self.free_oneway_space + }; + + let FindEmptyRes { + insert_at_idx, + insert_at_offset, + } = self.find_empty_range(size).ok_or(ENOSPC)?; + self.free_oneway_space = new_oneway_space; + + let new_range = Range { + offset: insert_at_offset, + size, + state: DescriptorState::new(is_oneway, debug_id, pid), + }; + // Insert the value at the given index to keep the array sorted. + self.ranges + .insert_within_capacity(insert_at_idx, new_range) + .ok() + .unwrap(); + + Ok(insert_at_offset) + } + + pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result<FreedRange> { + // This could use a binary search, but linear scans are usually faster for small arrays. + let i = self + .ranges + .iter() + .position(|range| range.offset == offset) + .ok_or(EINVAL)?; + let range = &self.ranges[i]; + + if let DescriptorState::Allocated(_) = range.state { + return Err(EPERM); + } + + let size = range.size; + let offset = range.offset; + + if range.state.is_oneway() { + self.free_oneway_space += size; + } + + // This computes the range of pages that are no longer used by *any* allocated range. The + // caller will mark them as unused, which means that they can be freed if the system comes + // under memory pressure. + let mut freed_range = FreedRange::interior_pages(offset, size); + #[expect(clippy::collapsible_if)] // reads better like this + if offset % PAGE_SIZE != 0 { + if i == 0 || self.ranges[i - 1].endpoint() <= (offset & PAGE_MASK) { + freed_range.start_page_idx -= 1; + } + } + if range.endpoint() % PAGE_SIZE != 0 { + let page_after = (range.endpoint() & PAGE_MASK) + PAGE_SIZE; + if i + 1 == self.ranges.len() || page_after <= self.ranges[i + 1].offset { + freed_range.end_page_idx += 1; + } + } + + self.ranges.remove(i)?; + Ok(freed_range) + } + + pub(crate) fn reservation_commit(&mut self, offset: usize, data: &mut Option<T>) -> Result { + // This could use a binary search, but linear scans are usually faster for small arrays. + let range = self + .ranges + .iter_mut() + .find(|range| range.offset == offset) + .ok_or(ENOENT)?; + + let DescriptorState::Reserved(reservation) = &range.state else { + return Err(ENOENT); + }; + + range.state = DescriptorState::Allocated(reservation.clone().allocate(data.take())); + Ok(()) + } + + pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option<T>)> { + // This could use a binary search, but linear scans are usually faster for small arrays. + let range = self + .ranges + .iter_mut() + .find(|range| range.offset == offset) + .ok_or(ENOENT)?; + + let DescriptorState::Allocated(allocation) = &mut range.state else { + return Err(ENOENT); + }; + + let data = allocation.take(); + let debug_id = allocation.reservation.debug_id; + range.state = DescriptorState::Reserved(allocation.reservation.clone()); + Ok((range.size, debug_id, data)) + } + + pub(crate) fn take_for_each<F: Fn(usize, usize, usize, Option<T>)>(&mut self, callback: F) { + for range in self.ranges.iter_mut() { + if let DescriptorState::Allocated(allocation) = &mut range.state { + callback( + range.offset, + range.size, + allocation.reservation.debug_id, + allocation.data.take(), + ); + } + } + } +} + +pub(crate) struct EmptyArrayAlloc<T> { + ranges: KVec<Range<T>>, +} + +impl<T> EmptyArrayAlloc<T> { + pub(crate) fn try_new(capacity: usize) -> Result<Self> { + Ok(Self { + ranges: KVec::with_capacity(capacity, GFP_KERNEL)?, + }) + } +} diff --git a/drivers/android/binder/range_alloc/mod.rs b/drivers/android/binder/range_alloc/mod.rs new file mode 100644 index 000000000000..2301e2bc1a1f --- /dev/null +++ b/drivers/android/binder/range_alloc/mod.rs @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{page::PAGE_SIZE, prelude::*, seq_file::SeqFile, task::Pid}; + +mod tree; +use self::tree::{FromArrayAllocs, ReserveNewTreeAlloc, TreeRangeAllocator}; + +mod array; +use self::array::{ArrayRangeAllocator, EmptyArrayAlloc}; + +enum DescriptorState<T> { + Reserved(Reservation), + Allocated(Allocation<T>), +} + +impl<T> DescriptorState<T> { + fn new(is_oneway: bool, debug_id: usize, pid: Pid) -> Self { + DescriptorState::Reserved(Reservation { + debug_id, + is_oneway, + pid, + }) + } + + fn pid(&self) -> Pid { + match self { + DescriptorState::Reserved(inner) => inner.pid, + DescriptorState::Allocated(inner) => inner.reservation.pid, + } + } + + fn is_oneway(&self) -> bool { + match self { + DescriptorState::Reserved(inner) => inner.is_oneway, + DescriptorState::Allocated(inner) => inner.reservation.is_oneway, + } + } +} + +#[derive(Clone)] +struct Reservation { + debug_id: usize, + is_oneway: bool, + pid: Pid, +} + +impl Reservation { + fn allocate<T>(self, data: Option<T>) -> Allocation<T> { + Allocation { + data, + reservation: self, + } + } +} + +struct Allocation<T> { + reservation: Reservation, + data: Option<T>, +} + +impl<T> Allocation<T> { + fn deallocate(self) -> (Reservation, Option<T>) { + (self.reservation, self.data) + } + + fn debug_id(&self) -> usize { + self.reservation.debug_id + } + + fn take(&mut self) -> Option<T> { + self.data.take() + } +} + +/// The array implementation must switch to the tree if it wants to go beyond this number of +/// ranges. +const TREE_THRESHOLD: usize = 8; + +/// Represents a range of pages that have just become completely free. +#[derive(Copy, Clone)] +pub(crate) struct FreedRange { + pub(crate) start_page_idx: usize, + pub(crate) end_page_idx: usize, +} + +impl FreedRange { + fn interior_pages(offset: usize, size: usize) -> FreedRange { + FreedRange { + // Divide round up + start_page_idx: offset.div_ceil(PAGE_SIZE), + // Divide round down + end_page_idx: (offset + size) / PAGE_SIZE, + } + } +} + +struct Range<T> { + offset: usize, + size: usize, + state: DescriptorState<T>, +} + +impl<T> Range<T> { + fn endpoint(&self) -> usize { + self.offset + self.size + } +} + +pub(crate) struct RangeAllocator<T> { + inner: Impl<T>, +} + +enum Impl<T> { + Empty(usize), + Array(ArrayRangeAllocator<T>), + Tree(TreeRangeAllocator<T>), +} + +impl<T> RangeAllocator<T> { + pub(crate) fn new(size: usize) -> Self { + Self { + inner: Impl::Empty(size), + } + } + + pub(crate) fn free_oneway_space(&self) -> usize { + match &self.inner { + Impl::Empty(size) => size / 2, + Impl::Array(array) => array.free_oneway_space(), + Impl::Tree(tree) => tree.free_oneway_space(), + } + } + + pub(crate) fn count_buffers(&self) -> usize { + match &self.inner { + Impl::Empty(_size) => 0, + Impl::Array(array) => array.count_buffers(), + Impl::Tree(tree) => tree.count_buffers(), + } + } + + pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> { + match &self.inner { + Impl::Empty(_size) => Ok(()), + Impl::Array(array) => array.debug_print(m), + Impl::Tree(tree) => tree.debug_print(m), + } + } + + /// Try to reserve a new buffer, using the provided allocation if necessary. + pub(crate) fn reserve_new(&mut self, mut args: ReserveNewArgs<T>) -> Result<ReserveNew<T>> { + match &mut self.inner { + Impl::Empty(size) => { + let empty_array = match args.empty_array_alloc.take() { + Some(empty_array) => ArrayRangeAllocator::new(*size, empty_array), + None => { + return Ok(ReserveNew::NeedAlloc(ReserveNewNeedAlloc { + args, + need_empty_array_alloc: true, + need_new_tree_alloc: false, + need_tree_alloc: false, + })) + } + }; + + self.inner = Impl::Array(empty_array); + self.reserve_new(args) + } + Impl::Array(array) if array.is_full() => { + let allocs = match args.new_tree_alloc { + Some(ref mut allocs) => allocs, + None => { + return Ok(ReserveNew::NeedAlloc(ReserveNewNeedAlloc { + args, + need_empty_array_alloc: false, + need_new_tree_alloc: true, + need_tree_alloc: true, + })) + } + }; + + let new_tree = + TreeRangeAllocator::from_array(array.total_size(), &mut array.ranges, allocs); + + self.inner = Impl::Tree(new_tree); + self.reserve_new(args) + } + Impl::Array(array) => { + let offset = + array.reserve_new(args.debug_id, args.size, args.is_oneway, args.pid)?; + Ok(ReserveNew::Success(ReserveNewSuccess { + offset, + oneway_spam_detected: false, + _empty_array_alloc: args.empty_array_alloc, + _new_tree_alloc: args.new_tree_alloc, + _tree_alloc: args.tree_alloc, + })) + } + Impl::Tree(tree) => { + let alloc = match args.tree_alloc { + Some(alloc) => alloc, + None => { + return Ok(ReserveNew::NeedAlloc(ReserveNewNeedAlloc { + args, + need_empty_array_alloc: false, + need_new_tree_alloc: false, + need_tree_alloc: true, + })); + } + }; + let (offset, oneway_spam_detected) = + tree.reserve_new(args.debug_id, args.size, args.is_oneway, args.pid, alloc)?; + Ok(ReserveNew::Success(ReserveNewSuccess { + offset, + oneway_spam_detected, + _empty_array_alloc: args.empty_array_alloc, + _new_tree_alloc: args.new_tree_alloc, + _tree_alloc: None, + })) + } + } + } + + /// Deletes the allocations at `offset`. + pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result<FreedRange> { + match &mut self.inner { + Impl::Empty(_size) => Err(EINVAL), + Impl::Array(array) => array.reservation_abort(offset), + Impl::Tree(tree) => { + let freed_range = tree.reservation_abort(offset)?; + if tree.is_empty() { + self.inner = Impl::Empty(tree.total_size()); + } + Ok(freed_range) + } + } + } + + /// Called when an allocation is no longer in use by the kernel. + /// + /// The value in `data` will be stored, if any. A mutable reference is used to avoid dropping + /// the `T` when an error is returned. + pub(crate) fn reservation_commit(&mut self, offset: usize, data: &mut Option<T>) -> Result { + match &mut self.inner { + Impl::Empty(_size) => Err(EINVAL), + Impl::Array(array) => array.reservation_commit(offset, data), + Impl::Tree(tree) => tree.reservation_commit(offset, data), + } + } + + /// Called when the kernel starts using an allocation. + /// + /// Returns the size of the existing entry and the data associated with it. + pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option<T>)> { + match &mut self.inner { + Impl::Empty(_size) => Err(EINVAL), + Impl::Array(array) => array.reserve_existing(offset), + Impl::Tree(tree) => tree.reserve_existing(offset), + } + } + + /// Call the provided callback at every allocated region. + /// + /// This destroys the range allocator. Used only during shutdown. + pub(crate) fn take_for_each<F: Fn(usize, usize, usize, Option<T>)>(&mut self, callback: F) { + match &mut self.inner { + Impl::Empty(_size) => {} + Impl::Array(array) => array.take_for_each(callback), + Impl::Tree(tree) => tree.take_for_each(callback), + } + } +} + +/// The arguments for `reserve_new`. +#[derive(Default)] +pub(crate) struct ReserveNewArgs<T> { + pub(crate) size: usize, + pub(crate) is_oneway: bool, + pub(crate) debug_id: usize, + pub(crate) pid: Pid, + pub(crate) empty_array_alloc: Option<EmptyArrayAlloc<T>>, + pub(crate) new_tree_alloc: Option<FromArrayAllocs<T>>, + pub(crate) tree_alloc: Option<ReserveNewTreeAlloc<T>>, +} + +/// The return type of `ReserveNew`. +pub(crate) enum ReserveNew<T> { + Success(ReserveNewSuccess<T>), + NeedAlloc(ReserveNewNeedAlloc<T>), +} + +/// Returned by `reserve_new` when the reservation was successul. +pub(crate) struct ReserveNewSuccess<T> { + pub(crate) offset: usize, + pub(crate) oneway_spam_detected: bool, + + // If the user supplied an allocation that we did not end up using, then we return it here. + // The caller will kfree it outside of the lock. + _empty_array_alloc: Option<EmptyArrayAlloc<T>>, + _new_tree_alloc: Option<FromArrayAllocs<T>>, + _tree_alloc: Option<ReserveNewTreeAlloc<T>>, +} + +/// Returned by `reserve_new` to request the caller to make an allocation before calling the method +/// again. +pub(crate) struct ReserveNewNeedAlloc<T> { + args: ReserveNewArgs<T>, + need_empty_array_alloc: bool, + need_new_tree_alloc: bool, + need_tree_alloc: bool, +} + +impl<T> ReserveNewNeedAlloc<T> { + /// Make the necessary allocations for another call to `reserve_new`. + pub(crate) fn make_alloc(mut self) -> Result<ReserveNewArgs<T>> { + if self.need_empty_array_alloc && self.args.empty_array_alloc.is_none() { + self.args.empty_array_alloc = Some(EmptyArrayAlloc::try_new(TREE_THRESHOLD)?); + } + if self.need_new_tree_alloc && self.args.new_tree_alloc.is_none() { + self.args.new_tree_alloc = Some(FromArrayAllocs::try_new(TREE_THRESHOLD)?); + } + if self.need_tree_alloc && self.args.tree_alloc.is_none() { + self.args.tree_alloc = Some(ReserveNewTreeAlloc::try_new()?); + } + Ok(self.args) + } +} diff --git a/drivers/android/binder/range_alloc/tree.rs b/drivers/android/binder/range_alloc/tree.rs new file mode 100644 index 000000000000..7b1a248fcb02 --- /dev/null +++ b/drivers/android/binder/range_alloc/tree.rs @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{ + page::PAGE_SIZE, + prelude::*, + rbtree::{RBTree, RBTreeNode, RBTreeNodeReservation}, + seq_file::SeqFile, + seq_print, + task::Pid, +}; + +use crate::range_alloc::{DescriptorState, FreedRange, Range}; + +/// Keeps track of allocations in a process' mmap. +/// +/// Each process has an mmap where the data for incoming transactions will be placed. This struct +/// keeps track of allocations made in the mmap. For each allocation, we store a descriptor that +/// has metadata related to the allocation. We also keep track of available free space. +pub(super) struct TreeRangeAllocator<T> { + /// This collection contains descriptors for *both* ranges containing an allocation, *and* free + /// ranges between allocations. The free ranges get merged, so there are never two free ranges + /// next to each other. + tree: RBTree<usize, Descriptor<T>>, + /// Contains an entry for every free range in `self.tree`. This tree sorts the ranges by size, + /// letting us look up the smallest range whose size is at least some lower bound. + free_tree: RBTree<FreeKey, ()>, + size: usize, + free_oneway_space: usize, +} + +impl<T> TreeRangeAllocator<T> { + pub(crate) fn from_array( + size: usize, + ranges: &mut KVec<Range<T>>, + alloc: &mut FromArrayAllocs<T>, + ) -> Self { + let mut tree = TreeRangeAllocator { + tree: RBTree::new(), + free_tree: RBTree::new(), + size, + free_oneway_space: size / 2, + }; + + let mut free_offset = 0; + for range in ranges.drain_all() { + let free_size = range.offset - free_offset; + if free_size > 0 { + let free_node = alloc.free_tree.pop().unwrap(); + tree.free_tree + .insert(free_node.into_node((free_size, free_offset), ())); + let tree_node = alloc.tree.pop().unwrap(); + tree.tree.insert( + tree_node.into_node(free_offset, Descriptor::new(free_offset, free_size)), + ); + } + free_offset = range.endpoint(); + + if range.state.is_oneway() { + tree.free_oneway_space = tree.free_oneway_space.saturating_sub(range.size); + } + + let free_res = alloc.free_tree.pop().unwrap(); + let tree_node = alloc.tree.pop().unwrap(); + let mut desc = Descriptor::new(range.offset, range.size); + desc.state = Some((range.state, free_res)); + tree.tree.insert(tree_node.into_node(range.offset, desc)); + } + + // After the last range, we may need a free range. + if free_offset < size { + let free_size = size - free_offset; + let free_node = alloc.free_tree.pop().unwrap(); + tree.free_tree + .insert(free_node.into_node((free_size, free_offset), ())); + let tree_node = alloc.tree.pop().unwrap(); + tree.tree + .insert(tree_node.into_node(free_offset, Descriptor::new(free_offset, free_size))); + } + + tree + } + + pub(crate) fn is_empty(&self) -> bool { + let mut tree_iter = self.tree.values(); + // There's always at least one range, because index zero is either the start of a free or + // allocated range. + let first_value = tree_iter.next().unwrap(); + if tree_iter.next().is_some() { + // There are never two free ranges next to each other, so if there is more than one + // descriptor, then at least one of them must hold an allocated range. + return false; + } + // There is only one descriptor. Return true if it is for a free range. + first_value.state.is_none() + } + + pub(crate) fn total_size(&self) -> usize { + self.size + } + + pub(crate) fn free_oneway_space(&self) -> usize { + self.free_oneway_space + } + + pub(crate) fn count_buffers(&self) -> usize { + self.tree + .values() + .filter(|desc| desc.state.is_some()) + .count() + } + + pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> { + for desc in self.tree.values() { + let state = match &desc.state { + Some(state) => &state.0, + None => continue, + }; + seq_print!( + m, + " buffer: {} size {} pid {}", + desc.offset, + desc.size, + state.pid(), + ); + if state.is_oneway() { + seq_print!(m, " oneway"); + } + match state { + DescriptorState::Reserved(_res) => { + seq_print!(m, " reserved\n"); + } + DescriptorState::Allocated(_alloc) => { + seq_print!(m, " allocated\n"); + } + } + } + Ok(()) + } + + fn find_best_match(&mut self, size: usize) -> Option<&mut Descriptor<T>> { + let free_cursor = self.free_tree.cursor_lower_bound(&(size, 0))?; + let ((_, offset), ()) = free_cursor.current(); + self.tree.get_mut(offset) + } + + /// Try to reserve a new buffer, using the provided allocation if necessary. + pub(crate) fn reserve_new( + &mut self, + debug_id: usize, + size: usize, + is_oneway: bool, + pid: Pid, + alloc: ReserveNewTreeAlloc<T>, + ) -> Result<(usize, bool)> { + // Compute new value of free_oneway_space, which is set only on success. + let new_oneway_space = if is_oneway { + match self.free_oneway_space.checked_sub(size) { + Some(new_oneway_space) => new_oneway_space, + None => return Err(ENOSPC), + } + } else { + self.free_oneway_space + }; + + // Start detecting spammers once we have less than 20% + // of async space left (which is less than 10% of total + // buffer size). + // + // (This will short-circut, so `low_oneway_space` is + // only called when necessary.) + let oneway_spam_detected = + is_oneway && new_oneway_space < self.size / 10 && self.low_oneway_space(pid); + + let (found_size, found_off, tree_node, free_tree_node) = match self.find_best_match(size) { + None => { + pr_warn!("ENOSPC from range_alloc.reserve_new - size: {}", size); + return Err(ENOSPC); + } + Some(desc) => { + let found_size = desc.size; + let found_offset = desc.offset; + + // In case we need to break up the descriptor + let new_desc = Descriptor::new(found_offset + size, found_size - size); + let (tree_node, free_tree_node, desc_node_res) = alloc.initialize(new_desc); + + desc.state = Some(( + DescriptorState::new(is_oneway, debug_id, pid), + desc_node_res, + )); + desc.size = size; + + (found_size, found_offset, tree_node, free_tree_node) + } + }; + self.free_oneway_space = new_oneway_space; + self.free_tree.remove(&(found_size, found_off)); + + if found_size != size { + self.tree.insert(tree_node); + self.free_tree.insert(free_tree_node); + } + + Ok((found_off, oneway_spam_detected)) + } + + pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result<FreedRange> { + let mut cursor = self.tree.cursor_lower_bound(&offset).ok_or_else(|| { + pr_warn!( + "EINVAL from range_alloc.reservation_abort - offset: {}", + offset + ); + EINVAL + })?; + + let (_, desc) = cursor.current_mut(); + + if desc.offset != offset { + pr_warn!( + "EINVAL from range_alloc.reservation_abort - offset: {}", + offset + ); + return Err(EINVAL); + } + + let (reservation, free_node_res) = desc.try_change_state(|state| match state { + Some((DescriptorState::Reserved(reservation), free_node_res)) => { + (None, Ok((reservation, free_node_res))) + } + None => { + pr_warn!( + "EINVAL from range_alloc.reservation_abort - offset: {}", + offset + ); + (None, Err(EINVAL)) + } + allocated => { + pr_warn!( + "EPERM from range_alloc.reservation_abort - offset: {}", + offset + ); + (allocated, Err(EPERM)) + } + })?; + + let mut size = desc.size; + let mut offset = desc.offset; + let free_oneway_space_add = if reservation.is_oneway { size } else { 0 }; + + self.free_oneway_space += free_oneway_space_add; + + let mut freed_range = FreedRange::interior_pages(offset, size); + // Compute how large the next free region needs to be to include one more page in + // the newly freed range. + let add_next_page_needed = match (offset + size) % PAGE_SIZE { + 0 => usize::MAX, + unalign => PAGE_SIZE - unalign, + }; + // Compute how large the previous free region needs to be to include one more page + // in the newly freed range. + let add_prev_page_needed = match offset % PAGE_SIZE { + 0 => usize::MAX, + unalign => unalign, + }; + + // Merge next into current if next is free + let remove_next = match cursor.peek_next() { + Some((_, next)) if next.state.is_none() => { + if next.size >= add_next_page_needed { + freed_range.end_page_idx += 1; + } + self.free_tree.remove(&(next.size, next.offset)); + size += next.size; + true + } + _ => false, + }; + + if remove_next { + let (_, desc) = cursor.current_mut(); + desc.size = size; + cursor.remove_next(); + } + + // Merge current into prev if prev is free + match cursor.peek_prev_mut() { + Some((_, prev)) if prev.state.is_none() => { + if prev.size >= add_prev_page_needed { + freed_range.start_page_idx -= 1; + } + // merge previous with current, remove current + self.free_tree.remove(&(prev.size, prev.offset)); + offset = prev.offset; + size += prev.size; + prev.size = size; + cursor.remove_current(); + } + _ => {} + }; + + self.free_tree + .insert(free_node_res.into_node((size, offset), ())); + + Ok(freed_range) + } + + pub(crate) fn reservation_commit(&mut self, offset: usize, data: &mut Option<T>) -> Result { + let desc = self.tree.get_mut(&offset).ok_or(ENOENT)?; + + desc.try_change_state(|state| match state { + Some((DescriptorState::Reserved(reservation), free_node_res)) => ( + Some(( + DescriptorState::Allocated(reservation.allocate(data.take())), + free_node_res, + )), + Ok(()), + ), + other => (other, Err(ENOENT)), + }) + } + + /// Takes an entry at the given offset from [`DescriptorState::Allocated`] to + /// [`DescriptorState::Reserved`]. + /// + /// Returns the size of the existing entry and the data associated with it. + pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option<T>)> { + let desc = self.tree.get_mut(&offset).ok_or_else(|| { + pr_warn!( + "ENOENT from range_alloc.reserve_existing - offset: {}", + offset + ); + ENOENT + })?; + + let (debug_id, data) = desc.try_change_state(|state| match state { + Some((DescriptorState::Allocated(allocation), free_node_res)) => { + let (reservation, data) = allocation.deallocate(); + let debug_id = reservation.debug_id; + ( + Some((DescriptorState::Reserved(reservation), free_node_res)), + Ok((debug_id, data)), + ) + } + other => { + pr_warn!( + "ENOENT from range_alloc.reserve_existing - offset: {}", + offset + ); + (other, Err(ENOENT)) + } + })?; + + Ok((desc.size, debug_id, data)) + } + + /// Call the provided callback at every allocated region. + /// + /// This destroys the range allocator. Used only during shutdown. + pub(crate) fn take_for_each<F: Fn(usize, usize, usize, Option<T>)>(&mut self, callback: F) { + for (_, desc) in self.tree.iter_mut() { + if let Some((DescriptorState::Allocated(allocation), _)) = &mut desc.state { + callback( + desc.offset, + desc.size, + allocation.debug_id(), + allocation.take(), + ); + } + } + } + + /// 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 transaction, + /// and at some point we'll catch them in the act. This is more efficient + /// than keeping a map per pid. + fn low_oneway_space(&self, calling_pid: Pid) -> bool { + let mut total_alloc_size = 0; + let mut num_buffers = 0; + for (_, desc) in self.tree.iter() { + if let Some((state, _)) = &desc.state { + if state.is_oneway() && state.pid() == calling_pid { + total_alloc_size += desc.size; + num_buffers += 1; + } + } + } + + // 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. + num_buffers > 50 || total_alloc_size > self.size / 4 + } +} + +type TreeDescriptorState<T> = (DescriptorState<T>, FreeNodeRes); +struct Descriptor<T> { + size: usize, + offset: usize, + state: Option<TreeDescriptorState<T>>, +} + +impl<T> Descriptor<T> { + fn new(offset: usize, size: usize) -> Self { + Self { + size, + offset, + state: None, + } + } + + fn try_change_state<F, Data>(&mut self, f: F) -> Result<Data> + where + F: FnOnce(Option<TreeDescriptorState<T>>) -> (Option<TreeDescriptorState<T>>, Result<Data>), + { + let (new_state, result) = f(self.state.take()); + self.state = new_state; + result + } +} + +// (Descriptor.size, Descriptor.offset) +type FreeKey = (usize, usize); +type FreeNodeRes = RBTreeNodeReservation<FreeKey, ()>; + +/// An allocation for use by `reserve_new`. +pub(crate) struct ReserveNewTreeAlloc<T> { + tree_node_res: RBTreeNodeReservation<usize, Descriptor<T>>, + free_tree_node_res: FreeNodeRes, + desc_node_res: FreeNodeRes, +} + +impl<T> ReserveNewTreeAlloc<T> { + pub(crate) fn try_new() -> Result<Self> { + let tree_node_res = RBTreeNodeReservation::new(GFP_KERNEL)?; + let free_tree_node_res = RBTreeNodeReservation::new(GFP_KERNEL)?; + let desc_node_res = RBTreeNodeReservation::new(GFP_KERNEL)?; + Ok(Self { + tree_node_res, + free_tree_node_res, + desc_node_res, + }) + } + + fn initialize( + self, + desc: Descriptor<T>, + ) -> ( + RBTreeNode<usize, Descriptor<T>>, + RBTreeNode<FreeKey, ()>, + FreeNodeRes, + ) { + let size = desc.size; + let offset = desc.offset; + ( + self.tree_node_res.into_node(offset, desc), + self.free_tree_node_res.into_node((size, offset), ()), + self.desc_node_res, + ) + } +} + +/// An allocation for creating a tree from an `ArrayRangeAllocator`. +pub(crate) struct FromArrayAllocs<T> { + tree: KVec<RBTreeNodeReservation<usize, Descriptor<T>>>, + free_tree: KVec<RBTreeNodeReservation<FreeKey, ()>>, +} + +impl<T> FromArrayAllocs<T> { + pub(crate) fn try_new(len: usize) -> Result<Self> { + let num_descriptors = 2 * len + 1; + + let mut tree = KVec::with_capacity(num_descriptors, GFP_KERNEL)?; + for _ in 0..num_descriptors { + tree.push(RBTreeNodeReservation::new(GFP_KERNEL)?, GFP_KERNEL)?; + } + + let mut free_tree = KVec::with_capacity(num_descriptors, GFP_KERNEL)?; + for _ in 0..num_descriptors { + free_tree.push(RBTreeNodeReservation::new(GFP_KERNEL)?, GFP_KERNEL)?; + } + + Ok(Self { tree, free_tree }) + } +} diff --git a/drivers/android/binder/rust_binder.h b/drivers/android/binder/rust_binder.h new file mode 100644 index 000000000000..31806890ed1a --- /dev/null +++ b/drivers/android/binder/rust_binder.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2025 Google, Inc. + */ + +#ifndef _LINUX_RUST_BINDER_H +#define _LINUX_RUST_BINDER_H + +#include <uapi/linux/android/binder.h> +#include <uapi/linux/android/binderfs.h> + +/* + * These symbols are exposed by `rust_binderfs.c` and exist here so that Rust + * Binder can call them. + */ +int init_rust_binderfs(void); + +struct dentry; +struct inode; +struct dentry *rust_binderfs_create_proc_file(struct inode *nodp, int pid); +void rust_binderfs_remove_file(struct dentry *dentry); + +#endif diff --git a/drivers/android/binder/rust_binder_events.c b/drivers/android/binder/rust_binder_events.c new file mode 100644 index 000000000000..488b1470060c --- /dev/null +++ b/drivers/android/binder/rust_binder_events.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* rust_binder_events.c + * + * Rust Binder tracepoints. + * + * Copyright 2025 Google LLC + */ + +#include "rust_binder.h" + +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", +}; + +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" +}; + +#define CREATE_TRACE_POINTS +#define CREATE_RUST_TRACE_POINTS +#include "rust_binder_events.h" diff --git a/drivers/android/binder/rust_binder_events.h b/drivers/android/binder/rust_binder_events.h new file mode 100644 index 000000000000..2f3efbf9dba6 --- /dev/null +++ b/drivers/android/binder/rust_binder_events.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Google, Inc. + */ + +#undef TRACE_SYSTEM +#undef TRACE_INCLUDE_FILE +#undef TRACE_INCLUDE_PATH +#define TRACE_SYSTEM rust_binder +#define TRACE_INCLUDE_FILE rust_binder_events +#define TRACE_INCLUDE_PATH ../drivers/android/binder + +#if !defined(_RUST_BINDER_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _RUST_BINDER_TRACE_H + +#include <linux/tracepoint.h> + +TRACE_EVENT(rust_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) +); + +#endif /* _RUST_BINDER_TRACE_H */ + +/* This part must be outside protection */ +#include <trace/define_trace.h> diff --git a/drivers/android/binder/rust_binder_internal.h b/drivers/android/binder/rust_binder_internal.h new file mode 100644 index 000000000000..78288fe7964d --- /dev/null +++ b/drivers/android/binder/rust_binder_internal.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* rust_binder_internal.h + * + * This file contains internal data structures used by Rust Binder. Mostly, + * these are type definitions used only by binderfs or things that Rust Binder + * define and export to binderfs. + * + * It does not include things exported by binderfs to Rust Binder since this + * file is not included as input to bindgen. + * + * Copyright (C) 2025 Google LLC. + */ + +#ifndef _LINUX_RUST_BINDER_INTERNAL_H +#define _LINUX_RUST_BINDER_INTERNAL_H + +#define RUST_BINDERFS_SUPER_MAGIC 0x6c6f6f71 + +#include <linux/seq_file.h> +#include <uapi/linux/android/binder.h> +#include <uapi/linux/android/binderfs.h> + +/* + * The internal data types in the Rust Binder driver are opaque to C, so we use + * void pointer typedefs for these types. + */ +typedef void *rust_binder_context; + +/** + * struct binder_device - information about a binder device node + * @minor: the minor number used by this device + * @ctx: the Rust Context used by this device, or null for binder-control + * + * This is used as the private data for files directly in binderfs, but not + * files in the binder_logs subdirectory. This struct owns a refcount on `ctx` + * and the entry for `minor` in `binderfs_minors`. For binder-control `ctx` is + * null. + */ +struct binder_device { + int minor; + rust_binder_context ctx; +}; + +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_proc_show(struct seq_file *m, void *pid); + +extern const struct file_operations rust_binder_fops; +rust_binder_context rust_binder_new_context(char *name); +void rust_binder_remove_context(rust_binder_context device); + +/** + * 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; +}; + +#endif /* _LINUX_RUST_BINDER_INTERNAL_H */ diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs new file mode 100644 index 000000000000..6773b7c273ec --- /dev/null +++ b/drivers/android/binder/rust_binder_main.rs @@ -0,0 +1,627 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! Binder -- the Android IPC mechanism. +#![recursion_limit = "256"] +#![allow( + clippy::as_underscore, + clippy::ref_as_ptr, + clippy::ptr_as_ptr, + clippy::cast_lossless +)] + +use kernel::{ + bindings::{self, seq_file}, + fs::File, + list::{ListArc, ListArcSafe, ListLinksSelfPtr, TryNewListArc}, + prelude::*, + seq_file::SeqFile, + seq_print, + sync::poll::PollTable, + sync::Arc, + task::Pid, + transmute::AsBytes, + types::ForeignOwnable, + uaccess::UserSliceWriter, +}; + +use crate::{context::Context, page_range::Shrinker, process::Process, thread::Thread}; + +use core::{ + ptr::NonNull, + sync::atomic::{AtomicBool, AtomicUsize, Ordering}, +}; + +mod allocation; +mod context; +mod deferred_close; +mod defs; +mod error; +mod node; +mod page_range; +mod process; +mod range_alloc; +mod stats; +mod thread; +mod trace; +mod transaction; + +#[allow(warnings)] // generated bindgen code +mod binderfs { + use kernel::bindings::{dentry, inode}; + + extern "C" { + pub fn init_rust_binderfs() -> kernel::ffi::c_int; + } + extern "C" { + pub fn rust_binderfs_create_proc_file( + nodp: *mut inode, + pid: kernel::ffi::c_int, + ) -> *mut dentry; + } + extern "C" { + pub fn rust_binderfs_remove_file(dentry: *mut dentry); + } + pub type rust_binder_context = *mut kernel::ffi::c_void; + #[repr(C)] + #[derive(Copy, Clone)] + pub struct binder_device { + pub minor: kernel::ffi::c_int, + pub ctx: rust_binder_context, + } + impl Default for binder_device { + fn default() -> Self { + let mut s = ::core::mem::MaybeUninit::<Self>::uninit(); + unsafe { + ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } + } +} + +module! { + type: BinderModule, + name: "rust_binder", + authors: ["Wedson Almeida Filho", "Alice Ryhl"], + description: "Android Binder", + license: "GPL", +} + +fn next_debug_id() -> usize { + static NEXT_DEBUG_ID: AtomicUsize = AtomicUsize::new(0); + + NEXT_DEBUG_ID.fetch_add(1, Ordering::Relaxed) +} + +/// Provides a single place to write Binder return values via the +/// supplied `UserSliceWriter`. +pub(crate) struct BinderReturnWriter<'a> { + writer: UserSliceWriter, + thread: &'a Thread, +} + +impl<'a> BinderReturnWriter<'a> { + fn new(writer: UserSliceWriter, thread: &'a Thread) -> Self { + BinderReturnWriter { writer, thread } + } + + /// Write a return code back to user space. + /// Should be a `BR_` constant from [`defs`] e.g. [`defs::BR_TRANSACTION_COMPLETE`]. + fn write_code(&mut self, code: u32) -> Result { + stats::GLOBAL_STATS.inc_br(code); + self.thread.process.stats.inc_br(code); + self.writer.write(&code) + } + + /// Write something *other than* a return code to user space. + fn write_payload<T: AsBytes>(&mut self, payload: &T) -> Result { + self.writer.write(payload) + } + + fn len(&self) -> usize { + self.writer.len() + } +} + +/// Specifies how a type should be delivered to the read part of a BINDER_WRITE_READ ioctl. +/// +/// When a value is pushed to the todo list for a process or thread, it is stored as a trait object +/// with the type `Arc<dyn DeliverToRead>`. Trait objects are a Rust feature that lets you +/// implement dynamic dispatch over many different types. This lets us store many different types +/// in the todo list. +trait DeliverToRead: ListArcSafe + Send + Sync { + /// Performs work. Returns true if remaining work items in the queue should be processed + /// immediately, or false if it should return to caller before processing additional work + /// items. + fn do_work( + self: DArc<Self>, + thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool>; + + /// Cancels the given work item. This is called instead of [`DeliverToRead::do_work`] when work + /// won't be delivered. + fn cancel(self: DArc<Self>); + + /// Should we use `wake_up_interruptible_sync` or `wake_up_interruptible` when scheduling this + /// work item? + /// + /// 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<()>; +} + +// Wrapper around a `DeliverToRead` with linked list links. +#[pin_data] +struct DTRWrap<T: ?Sized> { + #[pin] + links: ListLinksSelfPtr<DTRWrap<dyn DeliverToRead>>, + #[pin] + wrapped: T, +} +kernel::list::impl_list_arc_safe! { + impl{T: ListArcSafe + ?Sized} ListArcSafe<0> for DTRWrap<T> { + tracked_by wrapped: T; + } +} +kernel::list::impl_list_item! { + impl ListItem<0> for DTRWrap<dyn DeliverToRead> { + using ListLinksSelfPtr { self.links }; + } +} + +impl<T: ?Sized> core::ops::Deref for DTRWrap<T> { + type Target = T; + fn deref(&self) -> &T { + &self.wrapped + } +} + +type DArc<T> = kernel::sync::Arc<DTRWrap<T>>; +type DLArc<T> = kernel::list::ListArc<DTRWrap<T>>; + +impl<T: ListArcSafe> DTRWrap<T> { + fn new(val: impl PinInit<T>) -> impl PinInit<Self> { + pin_init!(Self { + links <- ListLinksSelfPtr::new(), + wrapped <- val, + }) + } + + fn arc_try_new(val: T) -> Result<DLArc<T>, kernel::alloc::AllocError> { + ListArc::pin_init( + try_pin_init!(Self { + links <- ListLinksSelfPtr::new(), + wrapped: val, + }), + GFP_KERNEL, + ) + .map_err(|_| kernel::alloc::AllocError) + } + + fn arc_pin_init(init: impl PinInit<T>) -> Result<DLArc<T>, kernel::error::Error> { + ListArc::pin_init( + try_pin_init!(Self { + links <- ListLinksSelfPtr::new(), + wrapped <- init, + }), + GFP_KERNEL, + ) + } +} + +struct DeliverCode { + code: u32, + skip: AtomicBool, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for DeliverCode { untracked; } +} + +impl DeliverCode { + fn new(code: u32) -> Self { + Self { + code, + skip: AtomicBool::new(false), + } + } + + /// Disable this DeliverCode and make it do nothing. + /// + /// This is used instead of removing it from the work list, since `LinkedList::remove` is + /// unsafe, whereas this method is not. + fn skip(&self) { + self.skip.store(true, Ordering::Relaxed); + } +} + +impl DeliverToRead for DeliverCode { + fn do_work( + self: DArc<Self>, + _thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + if !self.skip.load(Ordering::Relaxed) { + writer.write_code(self.code)?; + } + Ok(true) + } + + fn cancel(self: DArc<Self>) {} + + fn should_sync_wakeup(&self) -> bool { + false + } + + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + seq_print!(m, "{}", prefix); + if self.skip.load(Ordering::Relaxed) { + seq_print!(m, "(skipped) "); + } + if self.code == defs::BR_TRANSACTION_COMPLETE { + seq_print!(m, "transaction complete\n"); + } else { + seq_print!(m, "transaction error: {}\n", self.code); + } + Ok(()) + } +} + +fn ptr_align(value: usize) -> Option<usize> { + let size = core::mem::size_of::<usize>() - 1; + Some(value.checked_add(size)? & !size) +} + +// SAFETY: We call register in `init`. +static BINDER_SHRINKER: Shrinker = unsafe { Shrinker::new() }; + +struct BinderModule {} + +impl kernel::Module for BinderModule { + fn init(_module: &'static kernel::ThisModule) -> Result<Self> { + // SAFETY: The module initializer never runs twice, so we only call this once. + unsafe { crate::context::CONTEXTS.init() }; + + pr_warn!("Loaded Rust Binder."); + + BINDER_SHRINKER.register(kernel::c_str!("android-binder"))?; + + // SAFETY: The module is being loaded, so we can initialize binderfs. + unsafe { kernel::error::to_result(binderfs::init_rust_binderfs())? }; + + Ok(Self {}) + } +} + +/// Makes the inner type Sync. +#[repr(transparent)] +pub struct AssertSync<T>(T); +// SAFETY: Used only to insert `file_operations` into a global, which is safe. +unsafe impl<T> Sync for AssertSync<T> {} + +/// File operations that rust_binderfs.c can use. +#[no_mangle] +#[used] +pub static rust_binder_fops: AssertSync<kernel::bindings::file_operations> = { + // SAFETY: All zeroes is safe for the `file_operations` type. + let zeroed_ops = unsafe { core::mem::MaybeUninit::zeroed().assume_init() }; + + let ops = kernel::bindings::file_operations { + owner: THIS_MODULE.as_ptr(), + poll: Some(rust_binder_poll), + unlocked_ioctl: Some(rust_binder_unlocked_ioctl), + compat_ioctl: Some(rust_binder_compat_ioctl), + mmap: Some(rust_binder_mmap), + open: Some(rust_binder_open), + release: Some(rust_binder_release), + flush: Some(rust_binder_flush), + ..zeroed_ops + }; + AssertSync(ops) +}; + +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_new_context( + name: *const kernel::ffi::c_char, +) -> *mut kernel::ffi::c_void { + // SAFETY: The caller will always provide a valid c string here. + let name = unsafe { kernel::str::CStr::from_char_ptr(name) }; + match Context::new(name) { + Ok(ctx) => Arc::into_foreign(ctx), + Err(_err) => core::ptr::null_mut(), + } +} + +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_remove_context(device: *mut kernel::ffi::c_void) { + if !device.is_null() { + // SAFETY: The caller ensures that the `device` pointer came from a previous call to + // `rust_binder_new_device`. + let ctx = unsafe { Arc::<Context>::from_foreign(device) }; + ctx.deregister(); + drop(ctx); + } +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_open( + inode: *mut bindings::inode, + file_ptr: *mut bindings::file, +) -> kernel::ffi::c_int { + // SAFETY: The `rust_binderfs.c` file ensures that `i_private` is set to a + // `struct binder_device`. + let device = unsafe { (*inode).i_private } as *const binderfs::binder_device; + + assert!(!device.is_null()); + + // SAFETY: The `rust_binderfs.c` file ensures that `device->ctx` holds a binder context when + // using the rust binder fops. + let ctx = unsafe { Arc::<Context>::borrow((*device).ctx) }; + + // SAFETY: The caller provides a valid file pointer to a new `struct file`. + let file = unsafe { File::from_raw_file(file_ptr) }; + let process = match Process::open(ctx, file) { + Ok(process) => process, + Err(err) => return err.to_errno(), + }; + + // SAFETY: This is an `inode` for a newly created binder file. + match unsafe { BinderfsProcFile::new(inode, process.task.pid()) } { + Ok(Some(file)) => process.inner.lock().binderfs_file = Some(file), + Ok(None) => { /* pid already exists */ } + Err(err) => return err.to_errno(), + } + + // SAFETY: This file is associated with Rust binder, so we own the `private_data` field. + unsafe { (*file_ptr).private_data = process.into_foreign() }; + 0 +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_release( + _inode: *mut bindings::inode, + file: *mut bindings::file, +) -> kernel::ffi::c_int { + // SAFETY: We previously set `private_data` in `rust_binder_open`. + let process = unsafe { Arc::<Process>::from_foreign((*file).private_data) }; + // SAFETY: The caller ensures that the file is valid. + let file = unsafe { File::from_raw_file(file) }; + Process::release(process, file); + 0 +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_compat_ioctl( + file: *mut bindings::file, + cmd: kernel::ffi::c_uint, + arg: kernel::ffi::c_ulong, +) -> kernel::ffi::c_long { + // SAFETY: We previously set `private_data` in `rust_binder_open`. + let f = unsafe { Arc::<Process>::borrow((*file).private_data) }; + // SAFETY: The caller ensures that the file is valid. + match Process::compat_ioctl(f, unsafe { File::from_raw_file(file) }, cmd as _, arg as _) { + Ok(()) => 0, + Err(err) => err.to_errno() as isize, + } +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_unlocked_ioctl( + file: *mut bindings::file, + cmd: kernel::ffi::c_uint, + arg: kernel::ffi::c_ulong, +) -> kernel::ffi::c_long { + // SAFETY: We previously set `private_data` in `rust_binder_open`. + let f = unsafe { Arc::<Process>::borrow((*file).private_data) }; + // SAFETY: The caller ensures that the file is valid. + match Process::ioctl(f, unsafe { File::from_raw_file(file) }, cmd as _, arg as _) { + Ok(()) => 0, + Err(err) => err.to_errno() as isize, + } +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_mmap( + file: *mut bindings::file, + vma: *mut bindings::vm_area_struct, +) -> kernel::ffi::c_int { + // SAFETY: We previously set `private_data` in `rust_binder_open`. + let f = unsafe { Arc::<Process>::borrow((*file).private_data) }; + // SAFETY: The caller ensures that the vma is valid. + let area = unsafe { kernel::mm::virt::VmaNew::from_raw(vma) }; + // SAFETY: The caller ensures that the file is valid. + match Process::mmap(f, unsafe { File::from_raw_file(file) }, area) { + Ok(()) => 0, + Err(err) => err.to_errno(), + } +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_poll( + file: *mut bindings::file, + wait: *mut bindings::poll_table_struct, +) -> bindings::__poll_t { + // SAFETY: We previously set `private_data` in `rust_binder_open`. + let f = unsafe { Arc::<Process>::borrow((*file).private_data) }; + // SAFETY: The caller ensures that the file is valid. + let fileref = unsafe { File::from_raw_file(file) }; + // SAFETY: The caller ensures that the `PollTable` is valid. + match Process::poll(f, fileref, unsafe { PollTable::from_raw(wait) }) { + Ok(v) => v, + Err(_) => bindings::POLLERR, + } +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_flush( + file: *mut bindings::file, + _id: bindings::fl_owner_t, +) -> kernel::ffi::c_int { + // SAFETY: We previously set `private_data` in `rust_binder_open`. + let f = unsafe { Arc::<Process>::borrow((*file).private_data) }; + match Process::flush(f) { + Ok(()) => 0, + Err(err) => err.to_errno(), + } +} + +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_stats_show( + ptr: *mut seq_file, + _: *mut kernel::ffi::c_void, +) -> kernel::ffi::c_int { + // 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) }; + if let Err(err) = rust_binder_stats_show_impl(m) { + seq_print!(m, "failed to generate state: {:?}\n", err); + } + 0 +} + +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_state_show( + ptr: *mut seq_file, + _: *mut kernel::ffi::c_void, +) -> kernel::ffi::c_int { + // 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) }; + if let Err(err) = rust_binder_state_show_impl(m) { + seq_print!(m, "failed to generate state: {:?}\n", err); + } + 0 +} + +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_proc_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 pid = (unsafe { (*ptr).private }) as usize as Pid; + // 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) }; + if let Err(err) = rust_binder_proc_show_impl(m, pid) { + seq_print!(m, "failed to generate state: {:?}\n", err); + } + 0 +} + +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_transactions_show( + ptr: *mut seq_file, + _: *mut kernel::ffi::c_void, +) -> kernel::ffi::c_int { + // 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) }; + if let Err(err) = rust_binder_transactions_show_impl(m) { + seq_print!(m, "failed to generate state: {:?}\n", err); + } + 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 { + let procs = ctx.get_all_procs()?; + for proc in procs { + proc.debug_print(m, &ctx, false)?; + seq_print!(m, "\n"); + } + } + Ok(()) +} + +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()?; + for ctx in contexts { + let procs = ctx.get_all_procs()?; + for proc in procs { + proc.debug_print_stats(m, &ctx)?; + seq_print!(m, "\n"); + } + } + Ok(()) +} + +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 { + let procs = ctx.get_all_procs()?; + for proc in procs { + proc.debug_print(m, &ctx, true)?; + seq_print!(m, "\n"); + } + } + Ok(()) +} + +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 { + let procs = ctx.get_procs_with_pid(pid)?; + for proc in procs { + proc.debug_print(m, &ctx, true)?; + seq_print!(m, "\n"); + } + } + Ok(()) +} + +struct BinderfsProcFile(NonNull<bindings::dentry>); + +// SAFETY: Safe to drop any thread. +unsafe impl Send for BinderfsProcFile {} + +impl BinderfsProcFile { + /// # Safety + /// + /// Takes an inode from a newly created binder file. + unsafe fn new(nodp: *mut bindings::inode, pid: i32) -> Result<Option<Self>> { + // SAFETY: The caller passes an `inode` for a newly created binder file. + let dentry = unsafe { binderfs::rust_binderfs_create_proc_file(nodp, pid) }; + match kernel::error::from_err_ptr(dentry) { + Ok(dentry) => Ok(NonNull::new(dentry).map(Self)), + Err(err) if err == EEXIST => Ok(None), + Err(err) => Err(err), + } + } +} + +impl Drop for BinderfsProcFile { + fn drop(&mut self) { + // SAFETY: This is a dentry from `rust_binderfs_remove_file` that has not been deleted yet. + unsafe { binderfs::rust_binderfs_remove_file(self.0.as_ptr()) }; + } +} diff --git a/drivers/android/binder/rust_binderfs.c b/drivers/android/binder/rust_binderfs.c new file mode 100644 index 000000000000..6b497146b698 --- /dev/null +++ b/drivers/android/binder/rust_binderfs.c @@ -0,0 +1,850 @@ +// 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/asm-generic/errno-base.h> +#include <uapi/linux/android/binder.h> +#include <uapi/linux/android/binderfs.h> + +#include "rust_binder.h" +#include "rust_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) + +DEFINE_SHOW_ATTRIBUTE(rust_binder_stats); +DEFINE_SHOW_ATTRIBUTE(rust_binder_state); +DEFINE_SHOW_ATTRIBUTE(rust_binder_transactions); +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); + +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; +}; + +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, +}; + +static inline struct binderfs_info *BINDERFS_SB(const struct super_block *sb) +{ + return sb->s_fs_info; +} + +/** + * binderfs_binder_device_create - allocate inode from super block of a + * binderfs mount + * @ref_inode: inode from wich 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 = NULL; + rust_binder_context ctx = 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 : + BINDERFS_MAX_MINOR_CAPPED, + 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(sizeof(*device), GFP_KERNEL); + if (!device) + goto err; + + req->name[BINDERFS_MAX_NAME] = '\0'; /* NUL-terminate */ + + ctx = rust_binder_new_context(req->name); + if (!ctx) + 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 = &rust_binder_fops; + inode->i_uid = info->root_uid; + inode->i_gid = info->root_gid; + + req->major = MAJOR(binderfs_dev); + req->minor = minor; + device->ctx = ctx; + device->minor = minor; + + if (userp && copy_to_user(userp, req, sizeof(*req))) { + ret = -EFAULT; + goto err; + } + + root = sb->s_root; + inode_lock(d_inode(root)); + + /* look it up */ + dentry = lookup_noperm(&QSTR(req->name), root); + if (IS_ERR(dentry)) { + inode_unlock(d_inode(root)); + ret = PTR_ERR(dentry); + goto err; + } + + if (d_really_is_positive(dentry)) { + /* already exists */ + dput(dentry); + inode_unlock(d_inode(root)); + ret = -EEXIST; + goto err; + } + + inode->i_private = device; + d_instantiate(dentry, inode); + fsnotify_create(root->d_inode, dentry); + inode_unlock(d_inode(root)); + + return 0; + +err: + kfree(device); + rust_binder_remove_context(ctx); + 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 + * + * 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->minor); + mutex_unlock(&binderfs_minors_mutex); + + /* ctx is null for binder-control, but this function ignores null pointers */ + rust_binder_remove_context(device->ctx); + + 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_puts(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(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(sizeof(*device), GFP_KERNEL); + if (!device) + return -ENOMEM; + + /* If we have already created a binder-control node, return. */ + if (info->control_dentry) { + ret = 0; + goto out; + } + + 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 : + BINDERFS_MAX_MINOR_CAPPED, + 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; + + device->minor = minor; + device->ctx = NULL; + + dentry = d_alloc_name(root, "binder-control"); + if (!dentry) + goto out; + + inode->i_private = device; + info->control_dentry = dentry; + d_add(dentry, inode); + + 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; +} + +static struct dentry *binderfs_create_dentry(struct dentry *parent, + const char *name) +{ + struct dentry *dentry; + + dentry = lookup_noperm(&QSTR(name), parent); + if (IS_ERR(dentry)) + return dentry; + + /* Return error if the file/dir already exists. */ + if (d_really_is_positive(dentry)) { + dput(dentry); + return ERR_PTR(-EEXIST); + } + + return dentry; +} + +void rust_binderfs_remove_file(struct dentry *dentry) +{ + struct inode *parent_inode; + + parent_inode = d_inode(dentry->d_parent); + inode_lock(parent_inode); + if (simple_positive(dentry)) { + dget(dentry); + simple_unlink(parent_inode, dentry); + d_delete(dentry); + dput(dentry); + } + inode_unlock(parent_inode); +} + +static struct dentry *rust_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); + inode_lock(parent_inode); + + dentry = binderfs_create_dentry(parent, name); + if (IS_ERR(dentry)) + goto out; + + sb = parent_inode->i_sb; + new_inode = binderfs_make_inode(sb, S_IFREG | 0444); + if (!new_inode) { + dput(dentry); + dentry = ERR_PTR(-ENOMEM); + goto out; + } + + new_inode->i_fop = fops; + new_inode->i_private = data; + d_instantiate(dentry, new_inode); + fsnotify_create(parent_inode, dentry); + +out: + inode_unlock(parent_inode); + return dentry; +} + +struct dentry *rust_binderfs_create_proc_file(struct inode *nodp, int pid) +{ + struct binderfs_info *info = nodp->i_sb->s_fs_info; + struct dentry *dir = info->proc_log_dir; + char strbuf[20 + 1]; + void *data = (void *)(unsigned long) pid; + + if (!dir) + return NULL; + + snprintf(strbuf, sizeof(strbuf), "%u", pid); + return rust_binderfs_create_file(dir, strbuf, &rust_binder_proc_fops, data); +} + +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); + inode_lock(parent_inode); + + dentry = binderfs_create_dentry(parent, name); + if (IS_ERR(dentry)) + goto out; + + sb = parent_inode->i_sb; + new_inode = binderfs_make_inode(sb, S_IFDIR | 0755); + if (!new_inode) { + dput(dentry); + dentry = ERR_PTR(-ENOMEM); + goto out; + } + + new_inode->i_fop = &simple_dir_operations; + new_inode->i_op = &simple_dir_inode_operations; + + set_nlink(new_inode, 2); + d_instantiate(dentry, new_inode); + inc_nlink(parent_inode); + fsnotify_mkdir(parent_inode, dentry); + +out: + inode_unlock(parent_inode); + 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 = rust_binderfs_create_file(dir, "oneway_spam_detection", + &binder_features_fops, + &binder_features.oneway_spam_detection); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + dentry = rust_binderfs_create_file(dir, "extended_error", + &binder_features_fops, + &binder_features.extended_error); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + dentry = rust_binderfs_create_file(dir, "freeze_notification", + &binder_features_fops, + &binder_features.freeze_notification); + 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; + 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; + } + + dentry = rust_binderfs_create_file(binder_logs_root_dir, "stats", + &rust_binder_stats_fops, NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto out; + } + + dentry = rust_binderfs_create_file(binder_logs_root_dir, "state", + &rust_binder_state_fops, NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto out; + } + + dentry = rust_binderfs_create_file(binder_logs_root_dir, "transactions", + &rust_binder_transactions_fops, NULL); + 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 = RUST_BINDERFS_SUPER_MAGIC; + sb->s_op = &binderfs_super_ops; + sb->s_time_gran = 1; + + sb->s_fs_info = kzalloc(sizeof(struct binderfs_info), GFP_KERNEL); + 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 = rust_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(sizeof(struct binderfs_mount_opts), GFP_KERNEL); + 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_litter_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_rust_binderfs(void) +{ + int ret; + const char *name; + size_t len; + + /* Verify that the default binderfs device names are valid. */ + name = rust_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, + "rust_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/binder/stats.rs b/drivers/android/binder/stats.rs new file mode 100644 index 000000000000..a83ec111d2cb --- /dev/null +++ b/drivers/android/binder/stats.rs @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! Keep track of statistics for binder_logs. + +use crate::defs::*; +use core::sync::atomic::{AtomicU32, Ordering::Relaxed}; +use kernel::{ioctl::_IOC_NR, seq_file::SeqFile, seq_print}; + +const BC_COUNT: usize = _IOC_NR(BC_REPLY_SG) as usize + 1; +const BR_COUNT: usize = _IOC_NR(BR_TRANSACTION_PENDING_FROZEN) as usize + 1; + +pub(crate) static GLOBAL_STATS: BinderStats = BinderStats::new(); + +pub(crate) struct BinderStats { + bc: [AtomicU32; BC_COUNT], + br: [AtomicU32; BR_COUNT], +} + +impl BinderStats { + pub(crate) const fn new() -> Self { + #[expect(clippy::declare_interior_mutable_const)] + const ZERO: AtomicU32 = AtomicU32::new(0); + + Self { + bc: [ZERO; BC_COUNT], + br: [ZERO; BR_COUNT], + } + } + + pub(crate) fn inc_bc(&self, bc: u32) { + let idx = _IOC_NR(bc) as usize; + if let Some(bc_ref) = self.bc.get(idx) { + bc_ref.fetch_add(1, Relaxed); + } + } + + pub(crate) fn inc_br(&self, br: u32) { + let idx = _IOC_NR(br) as usize; + if let Some(br_ref) = self.br.get(idx) { + br_ref.fetch_add(1, Relaxed); + } + } + + pub(crate) fn debug_print(&self, prefix: &str, m: &SeqFile) { + for (i, cnt) in self.bc.iter().enumerate() { + let cnt = cnt.load(Relaxed); + if cnt > 0 { + seq_print!(m, "{}{}: {}\n", prefix, command_string(i), cnt); + } + } + for (i, cnt) in self.br.iter().enumerate() { + let cnt = cnt.load(Relaxed); + if cnt > 0 { + seq_print!(m, "{}{}: {}\n", prefix, return_string(i), cnt); + } + } + } +} + +mod strings { + use core::str::from_utf8_unchecked; + use kernel::str::CStr; + + extern "C" { + static binder_command_strings: [*const u8; super::BC_COUNT]; + static binder_return_strings: [*const u8; super::BR_COUNT]; + } + + pub(super) fn command_string(i: usize) -> &'static str { + // SAFETY: Accessing `binder_command_strings` is always safe. + let c_str_ptr = unsafe { binder_command_strings[i] }; + // SAFETY: The `binder_command_strings` array only contains nul-terminated strings. + let bytes = unsafe { CStr::from_char_ptr(c_str_ptr) }.as_bytes(); + // SAFETY: The `binder_command_strings` array only contains strings with ascii-chars. + unsafe { from_utf8_unchecked(bytes) } + } + + pub(super) fn return_string(i: usize) -> &'static str { + // SAFETY: Accessing `binder_return_strings` is always safe. + let c_str_ptr = unsafe { binder_return_strings[i] }; + // SAFETY: The `binder_command_strings` array only contains nul-terminated strings. + let bytes = unsafe { CStr::from_char_ptr(c_str_ptr) }.as_bytes(); + // SAFETY: The `binder_command_strings` array only contains strings with ascii-chars. + unsafe { from_utf8_unchecked(bytes) } + } +} +use strings::{command_string, return_string}; diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs new file mode 100644 index 000000000000..7e34ccd394f8 --- /dev/null +++ b/drivers/android/binder/thread.rs @@ -0,0 +1,1596 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! This module defines the `Thread` type, which represents a userspace thread that is using +//! binder. +//! +//! The `Process` object stores all of the threads in an rb tree. + +use kernel::{ + bindings, + fs::{File, LocalFile}, + list::{AtomicTracker, List, ListArc, ListLinks, TryNewListArc}, + prelude::*, + security, + seq_file::SeqFile, + seq_print, + sync::poll::{PollCondVar, PollTable}, + sync::{Arc, SpinLock}, + task::Task, + types::ARef, + uaccess::UserSlice, + uapi, +}; + +use crate::{ + allocation::{Allocation, AllocationView, BinderObject, BinderObjectRef, NewAllocation}, + defs::*, + error::BinderResult, + process::{GetWorkOrRegister, Process}, + ptr_align, + stats::GLOBAL_STATS, + transaction::Transaction, + BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverCode, DeliverToRead, +}; + +use core::{ + mem::size_of, + sync::atomic::{AtomicU32, Ordering}, +}; + +/// Stores the layout of the scatter-gather entries. This is used during the `translate_objects` +/// call and is discarded when it returns. +struct ScatterGatherState { + /// A struct that tracks the amount of unused buffer space. + unused_buffer_space: UnusedBufferSpace, + /// Scatter-gather entries to copy. + sg_entries: KVec<ScatterGatherEntry>, + /// Indexes into `sg_entries` corresponding to the last binder_buffer_object that + /// was processed and all of its ancestors. The array is in sorted order. + ancestors: KVec<usize>, +} + +/// This entry specifies an additional buffer that should be copied using the scatter-gather +/// mechanism. +struct ScatterGatherEntry { + /// The index in the offset array of the BINDER_TYPE_PTR that this entry originates from. + obj_index: usize, + /// Offset in target buffer. + offset: usize, + /// User address in source buffer. + sender_uaddr: usize, + /// Number of bytes to copy. + length: usize, + /// The minimum offset of the next fixup in this buffer. + fixup_min_offset: usize, + /// The offsets within this buffer that contain pointers which should be translated. + pointer_fixups: KVec<PointerFixupEntry>, +} + +/// This entry specifies that a fixup should happen at `target_offset` of the +/// buffer. If `skip` is nonzero, then the fixup is a `binder_fd_array_object` +/// and is applied later. Otherwise if `skip` is zero, then the size of the +/// fixup is `sizeof::<u64>()` and `pointer_value` is written to the buffer. +struct PointerFixupEntry { + /// The number of bytes to skip, or zero for a `binder_buffer_object` fixup. + skip: usize, + /// The translated pointer to write when `skip` is zero. + pointer_value: u64, + /// The offset at which the value should be written. The offset is relative + /// to the original buffer. + target_offset: usize, +} + +/// Return type of `apply_and_validate_fixup_in_parent`. +struct ParentFixupInfo { + /// The index of the parent buffer in `sg_entries`. + parent_sg_index: usize, + /// The number of ancestors of the buffer. + /// + /// The buffer is considered an ancestor of itself, so this is always at + /// least one. + num_ancestors: usize, + /// New value of `fixup_min_offset` if this fixup is applied. + new_min_offset: usize, + /// The offset of the fixup in the target buffer. + target_offset: usize, +} + +impl ScatterGatherState { + /// Called when a `binder_buffer_object` or `binder_fd_array_object` tries + /// to access a region in its parent buffer. These accesses have various + /// restrictions, which this method verifies. + /// + /// The `parent_offset` and `length` arguments describe the offset and + /// length of the access in the parent buffer. + /// + /// # Detailed restrictions + /// + /// Obviously the fixup must be in-bounds for the parent buffer. + /// + /// 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 + /// + /// Arcerring 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 + fn validate_parent_fixup( + &self, + parent: usize, + parent_offset: usize, + length: usize, + ) -> Result<ParentFixupInfo> { + // Using `position` would also be correct, but `rposition` avoids + // quadratic running times. + let ancestors_i = self + .ancestors + .iter() + .copied() + .rposition(|sg_idx| self.sg_entries[sg_idx].obj_index == parent) + .ok_or(EINVAL)?; + let sg_idx = self.ancestors[ancestors_i]; + let sg_entry = match self.sg_entries.get(sg_idx) { + Some(sg_entry) => sg_entry, + None => { + pr_err!( + "self.ancestors[{}] is {}, but self.sg_entries.len() is {}", + ancestors_i, + sg_idx, + self.sg_entries.len() + ); + return Err(EINVAL); + } + }; + if sg_entry.fixup_min_offset > parent_offset { + pr_warn!( + "validate_parent_fixup: fixup_min_offset={}, parent_offset={}", + sg_entry.fixup_min_offset, + parent_offset + ); + return Err(EINVAL); + } + let new_min_offset = parent_offset.checked_add(length).ok_or(EINVAL)?; + if new_min_offset > sg_entry.length { + pr_warn!( + "validate_parent_fixup: new_min_offset={}, sg_entry.length={}", + new_min_offset, + sg_entry.length + ); + return Err(EINVAL); + } + let target_offset = sg_entry.offset.checked_add(parent_offset).ok_or(EINVAL)?; + // The `ancestors_i + 1` operation can't overflow since the output of the addition is at + // most `self.ancestors.len()`, which also fits in a usize. + Ok(ParentFixupInfo { + parent_sg_index: sg_idx, + num_ancestors: ancestors_i + 1, + new_min_offset, + target_offset, + }) + } +} + +/// Keeps track of how much unused buffer space is left. The initial amount is the number of bytes +/// requested by the user using the `buffers_size` field of `binder_transaction_data_sg`. Each time +/// we translate an object of type `BINDER_TYPE_PTR`, some of the unused buffer space is consumed. +struct UnusedBufferSpace { + /// The start of the remaining space. + offset: usize, + /// The end of the remaining space. + limit: usize, +} +impl UnusedBufferSpace { + /// Claim the next `size` bytes from the unused buffer space. The offset for the claimed chunk + /// into the buffer is returned. + fn claim_next(&mut self, size: usize) -> Result<usize> { + // We require every chunk to be aligned. + let size = ptr_align(size).ok_or(EINVAL)?; + let new_offset = self.offset.checked_add(size).ok_or(EINVAL)?; + + if new_offset <= self.limit { + let offset = self.offset; + self.offset = new_offset; + Ok(offset) + } else { + Err(EINVAL) + } + } +} + +pub(crate) enum PushWorkRes { + Ok, + FailedDead(DLArc<dyn DeliverToRead>), +} + +impl PushWorkRes { + fn is_ok(&self) -> bool { + match self { + PushWorkRes::Ok => true, + PushWorkRes::FailedDead(_) => false, + } + } +} + +/// The fields of `Thread` protected by the spinlock. +struct InnerThread { + /// Determines the looper state of the thread. It is a bit-wise combination of the constants + /// prefixed with `LOOPER_`. + looper_flags: u32, + + /// Determines whether the looper should return. + looper_need_return: bool, + + /// Determines if thread is dead. + is_dead: bool, + + /// Work item used to deliver error codes to the thread that started a transaction. Stored here + /// so that it can be reused. + reply_work: DArc<ThreadError>, + + /// Work item used to deliver error codes to the current thread. Stored here so that it can be + /// reused. + return_work: DArc<ThreadError>, + + /// Determines whether the work list below should be processed. When set to false, `work_list` + /// is treated as if it were empty. + process_work_list: bool, + /// List of work items to deliver to userspace. + work_list: List<DTRWrap<dyn DeliverToRead>>, + current_transaction: Option<DArc<Transaction>>, + + /// Extended error information for this thread. + extended_error: ExtendedError, +} + +const LOOPER_REGISTERED: u32 = 0x01; +const LOOPER_ENTERED: u32 = 0x02; +const LOOPER_EXITED: u32 = 0x04; +const LOOPER_INVALID: u32 = 0x08; +const LOOPER_WAITING: u32 = 0x10; +const LOOPER_WAITING_PROC: u32 = 0x20; +const LOOPER_POLL: u32 = 0x40; + +impl InnerThread { + fn new() -> Result<Self> { + fn next_err_id() -> u32 { + static EE_ID: AtomicU32 = AtomicU32::new(0); + EE_ID.fetch_add(1, Ordering::Relaxed) + } + + Ok(Self { + looper_flags: 0, + looper_need_return: false, + is_dead: false, + process_work_list: false, + reply_work: ThreadError::try_new()?, + return_work: ThreadError::try_new()?, + work_list: List::new(), + current_transaction: None, + extended_error: ExtendedError::new(next_err_id(), BR_OK, 0), + }) + } + + fn pop_work(&mut self) -> Option<DLArc<dyn DeliverToRead>> { + if !self.process_work_list { + return None; + } + + let ret = self.work_list.pop_front(); + self.process_work_list = !self.work_list.is_empty(); + ret + } + + fn push_work(&mut self, work: DLArc<dyn DeliverToRead>) -> PushWorkRes { + if self.is_dead { + PushWorkRes::FailedDead(work) + } else { + self.work_list.push_back(work); + self.process_work_list = true; + PushWorkRes::Ok + } + } + + fn push_reply_work(&mut self, code: u32) { + if let Ok(work) = ListArc::try_from_arc(self.reply_work.clone()) { + work.set_error_code(code); + self.push_work(work); + } else { + pr_warn!("Thread reply work is already in use."); + } + } + + fn push_return_work(&mut self, reply: u32) { + if let Ok(work) = ListArc::try_from_arc(self.return_work.clone()) { + work.set_error_code(reply); + self.push_work(work); + } else { + pr_warn!("Thread return work is already in use."); + } + } + + /// Used to push work items that do not need to be processed immediately and can wait until the + /// thread gets another work item. + fn push_work_deferred(&mut self, work: DLArc<dyn DeliverToRead>) { + self.work_list.push_back(work); + } + + /// Fetches the transaction this thread can reply to. If the thread has a pending transaction + /// (that it could respond to) but it has also issued a transaction, it must first wait for the + /// previously-issued transaction to complete. + /// + /// The `thread` parameter should be the thread containing this `ThreadInner`. + fn pop_transaction_to_reply(&mut self, thread: &Thread) -> Result<DArc<Transaction>> { + let transaction = self.current_transaction.take().ok_or(EINVAL)?; + if core::ptr::eq(thread, transaction.from.as_ref()) { + self.current_transaction = Some(transaction); + return Err(EINVAL); + } + // Find a new current transaction for this thread. + self.current_transaction = transaction.find_from(thread).cloned(); + Ok(transaction) + } + + fn pop_transaction_replied(&mut self, transaction: &DArc<Transaction>) -> bool { + match self.current_transaction.take() { + None => false, + Some(old) => { + if !Arc::ptr_eq(transaction, &old) { + self.current_transaction = Some(old); + return false; + } + self.current_transaction = old.clone_next(); + true + } + } + } + + fn looper_enter(&mut self) { + self.looper_flags |= LOOPER_ENTERED; + if self.looper_flags & LOOPER_REGISTERED != 0 { + self.looper_flags |= LOOPER_INVALID; + } + } + + fn looper_register(&mut self, valid: bool) { + self.looper_flags |= LOOPER_REGISTERED; + if !valid || self.looper_flags & LOOPER_ENTERED != 0 { + self.looper_flags |= LOOPER_INVALID; + } + } + + fn looper_exit(&mut self) { + self.looper_flags |= LOOPER_EXITED; + } + + /// Determines whether the thread is part of a pool, i.e., if it is a looper. + fn is_looper(&self) -> bool { + self.looper_flags & (LOOPER_ENTERED | LOOPER_REGISTERED) != 0 + } + + /// Determines whether the thread should attempt to fetch work items from the process queue. + /// This is generally case when the thread is registered as a looper and not part of a + /// transaction stack. But if there is local work, we want to return to userspace before we + /// deliver any remote work. + fn should_use_process_work_queue(&self) -> bool { + self.current_transaction.is_none() && !self.process_work_list && self.is_looper() + } + + fn poll(&mut self) -> u32 { + self.looper_flags |= LOOPER_POLL; + if self.process_work_list || self.looper_need_return { + bindings::POLLIN + } else { + 0 + } + } +} + +/// This represents a thread that's used with binder. +#[pin_data] +pub(crate) struct Thread { + pub(crate) id: i32, + pub(crate) process: Arc<Process>, + pub(crate) task: ARef<Task>, + #[pin] + inner: SpinLock<InnerThread>, + #[pin] + work_condvar: PollCondVar, + /// Used to insert this thread into the process' `ready_threads` list. + /// + /// INVARIANT: May never be used for any other list than the `self.process.ready_threads`. + #[pin] + links: ListLinks, + #[pin] + links_track: AtomicTracker, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for Thread { + tracked_by links_track: AtomicTracker; + } +} +kernel::list::impl_list_item! { + impl ListItem<0> for Thread { + using ListLinks { self.links }; + } +} + +impl Thread { + pub(crate) fn new(id: i32, process: Arc<Process>) -> Result<Arc<Self>> { + let inner = InnerThread::new()?; + + Arc::pin_init( + try_pin_init!(Thread { + id, + process, + task: ARef::from(&**kernel::current!()), + inner <- kernel::new_spinlock!(inner, "Thread::inner"), + work_condvar <- kernel::new_poll_condvar!("Thread::work_condvar"), + links <- ListLinks::new(), + links_track <- AtomicTracker::new(), + }), + GFP_KERNEL, + ) + } + + #[inline(never)] + 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() { + seq_print!( + m, + " thread {}: l {:02x} need_return {}\n", + self.id, + inner.looper_flags, + inner.looper_need_return, + ); + } + + let mut t_opt = inner.current_transaction.as_ref(); + while let Some(t) = t_opt { + if Arc::ptr_eq(&t.from, self) { + t.debug_print_inner(m, " outgoing transaction "); + t_opt = t.from_parent.as_ref(); + } else if Arc::ptr_eq(&t.to, &self.process) { + t.debug_print_inner(m, " incoming transaction "); + t_opt = t.find_from(self); + } else { + t.debug_print_inner(m, " bad transaction "); + t_opt = None; + } + } + + for work in &inner.work_list { + work.debug_print(m, " ", " pending transaction ")?; + } + Ok(()) + } + + pub(crate) fn get_extended_error(&self, data: UserSlice) -> Result { + let mut writer = data.writer(); + let ee = self.inner.lock().extended_error; + writer.write(&ee)?; + Ok(()) + } + + pub(crate) fn set_current_transaction(&self, transaction: DArc<Transaction>) { + self.inner.lock().current_transaction = Some(transaction); + } + + pub(crate) fn has_current_transaction(&self) -> bool { + self.inner.lock().current_transaction.is_some() + } + + /// Attempts to fetch a work item from the thread-local queue. The behaviour if the queue is + /// empty depends on `wait`: if it is true, the function waits for some work to be queued (or a + /// signal); otherwise it returns indicating that none is available. + fn get_work_local(self: &Arc<Self>, wait: bool) -> Result<Option<DLArc<dyn DeliverToRead>>> { + { + let mut inner = self.inner.lock(); + if inner.looper_need_return { + return Ok(inner.pop_work()); + } + } + + // Try once if the caller does not want to wait. + if !wait { + return self.inner.lock().pop_work().ok_or(EAGAIN).map(Some); + } + + // Loop waiting only on the local queue (i.e., not registering with the process queue). + let mut inner = self.inner.lock(); + loop { + if let Some(work) = inner.pop_work() { + return Ok(Some(work)); + } + + inner.looper_flags |= LOOPER_WAITING; + let signal_pending = self.work_condvar.wait_interruptible_freezable(&mut inner); + inner.looper_flags &= !LOOPER_WAITING; + + if signal_pending { + return Err(EINTR); + } + if inner.looper_need_return { + return Ok(None); + } + } + } + + /// Attempts to fetch a work item from the thread-local queue, falling back to the process-wide + /// queue if none is available locally. + /// + /// This must only be called when the thread is not participating in a transaction chain. If it + /// is, the local version (`get_work_local`) should be used instead. + fn get_work(self: &Arc<Self>, wait: bool) -> Result<Option<DLArc<dyn DeliverToRead>>> { + // Try to get work from the thread's work queue, using only a local lock. + { + let mut inner = self.inner.lock(); + if let Some(work) = inner.pop_work() { + return Ok(Some(work)); + } + if inner.looper_need_return { + drop(inner); + return Ok(self.process.get_work()); + } + } + + // 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. + 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) { + GetWorkOrRegister::Work(work) => return Ok(Some(work)), + GetWorkOrRegister::Register(reg) => reg, + }; + + let mut inner = self.inner.lock(); + loop { + if let Some(work) = inner.pop_work() { + return Ok(Some(work)); + } + + inner.looper_flags |= LOOPER_WAITING | LOOPER_WAITING_PROC; + let signal_pending = self.work_condvar.wait_interruptible_freezable(&mut inner); + inner.looper_flags &= !(LOOPER_WAITING | LOOPER_WAITING_PROC); + + 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). + drop(inner); + drop(reg); + + let res = match self.inner.lock().pop_work() { + Some(work) => Ok(Some(work)), + None if signal_pending => Err(EINTR), + None => Ok(None), + }; + return res; + } + } + } + + /// Push the provided work item to be delivered to user space via this thread. + /// + /// Returns whether the item was successfully pushed. This can only fail if the thread is dead. + pub(crate) fn push_work(&self, work: DLArc<dyn DeliverToRead>) -> PushWorkRes { + let sync = work.should_sync_wakeup(); + + let res = self.inner.lock().push_work(work); + + if res.is_ok() { + if sync { + self.work_condvar.notify_sync(); + } else { + self.work_condvar.notify_one(); + } + } + + res + } + + /// Attempts to push to given work item to the thread if it's a looper thread (i.e., if it's + /// part of a thread pool) and is alive. Otherwise, push the work item to the process instead. + 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 { + inner.push_work(work); + Ok(()) + } else { + drop(inner); + self.process.push_work(work) + } + } + + pub(crate) fn push_work_deferred(&self, work: DLArc<dyn DeliverToRead>) { + self.inner.lock().push_work_deferred(work); + } + + pub(crate) fn push_return_work(&self, reply: u32) { + self.inner.lock().push_return_work(reply); + } + + fn translate_object( + &self, + obj_index: usize, + offset: usize, + object: BinderObjectRef<'_>, + view: &mut AllocationView<'_>, + allow_fds: bool, + sg_state: &mut ScatterGatherState, + ) -> BinderResult { + match object { + BinderObjectRef::Binder(obj) => { + let strong = obj.hdr.type_ == BINDER_TYPE_BINDER; + // SAFETY: `binder` is a `binder_uintptr_t`; any bit pattern is a valid + // representation. + let ptr = unsafe { obj.__bindgen_anon_1.binder } as _; + let cookie = obj.cookie as _; + let flags = obj.flags as _; + let node = self + .process + .as_arc_borrow() + .get_node(ptr, cookie, flags, strong, self)?; + security::binder_transfer_binder(&self.process.cred, &view.alloc.process.cred)?; + view.transfer_binder_object(offset, obj, strong, node)?; + } + BinderObjectRef::Handle(obj) => { + let strong = obj.hdr.type_ == BINDER_TYPE_HANDLE; + // SAFETY: `handle` is a `u32`; any bit pattern is a valid representation. + let handle = unsafe { obj.__bindgen_anon_1.handle } as _; + let node = self.process.get_node_from_handle(handle, strong)?; + security::binder_transfer_binder(&self.process.cred, &view.alloc.process.cred)?; + view.transfer_binder_object(offset, obj, strong, node)?; + } + BinderObjectRef::Fd(obj) => { + if !allow_fds { + return Err(EPERM.into()); + } + + // SAFETY: `fd` is a `u32`; any bit pattern is a valid representation. + let fd = unsafe { obj.__bindgen_anon_1.fd }; + let file = LocalFile::fget(fd)?; + // SAFETY: The binder driver never calls `fdget_pos` and this code runs from an + // ioctl, so there are no active calls to `fdget_pos` on this thread. + let file = unsafe { LocalFile::assume_no_fdget_pos(file) }; + security::binder_transfer_file( + &self.process.cred, + &view.alloc.process.cred, + &file, + )?; + + let mut obj_write = BinderFdObject::default(); + obj_write.hdr.type_ = BINDER_TYPE_FD; + // This will be overwritten with the actual fd when the transaction is received. + obj_write.__bindgen_anon_1.fd = u32::MAX; + obj_write.cookie = obj.cookie; + view.write::<BinderFdObject>(offset, &obj_write)?; + + const FD_FIELD_OFFSET: usize = + core::mem::offset_of!(uapi::binder_fd_object, __bindgen_anon_1.fd); + + let field_offset = offset + FD_FIELD_OFFSET; + + view.alloc.info_add_fd(file, field_offset, false)?; + } + BinderObjectRef::Ptr(obj) => { + let obj_length = obj.length.try_into().map_err(|_| EINVAL)?; + let alloc_offset = match sg_state.unused_buffer_space.claim_next(obj_length) { + Ok(alloc_offset) => alloc_offset, + Err(err) => { + pr_warn!( + "Failed to claim space for a BINDER_TYPE_PTR. (offset: {}, limit: {}, size: {})", + sg_state.unused_buffer_space.offset, + sg_state.unused_buffer_space.limit, + obj_length, + ); + return Err(err.into()); + } + }; + + let sg_state_idx = sg_state.sg_entries.len(); + sg_state.sg_entries.push( + ScatterGatherEntry { + obj_index, + offset: alloc_offset, + sender_uaddr: obj.buffer as _, + length: obj_length, + pointer_fixups: KVec::new(), + fixup_min_offset: 0, + }, + GFP_KERNEL, + )?; + + let buffer_ptr_in_user_space = (view.alloc.ptr + alloc_offset) as u64; + + if obj.flags & uapi::BINDER_BUFFER_FLAG_HAS_PARENT == 0 { + sg_state.ancestors.clear(); + sg_state.ancestors.push(sg_state_idx, GFP_KERNEL)?; + } else { + // Another buffer also has a pointer to this buffer, and we need to fixup that + // pointer too. + + let parent_index = usize::try_from(obj.parent).map_err(|_| EINVAL)?; + let parent_offset = usize::try_from(obj.parent_offset).map_err(|_| EINVAL)?; + + let info = sg_state.validate_parent_fixup( + parent_index, + parent_offset, + size_of::<u64>(), + )?; + + sg_state.ancestors.truncate(info.num_ancestors); + sg_state.ancestors.push(sg_state_idx, GFP_KERNEL)?; + + let parent_entry = match sg_state.sg_entries.get_mut(info.parent_sg_index) { + Some(parent_entry) => parent_entry, + None => { + pr_err!( + "validate_parent_fixup returned index out of bounds for sg.entries" + ); + return Err(EINVAL.into()); + } + }; + + parent_entry.fixup_min_offset = info.new_min_offset; + parent_entry.pointer_fixups.push( + PointerFixupEntry { + skip: 0, + pointer_value: buffer_ptr_in_user_space, + target_offset: info.target_offset, + }, + GFP_KERNEL, + )?; + } + + let mut obj_write = BinderBufferObject::default(); + obj_write.hdr.type_ = BINDER_TYPE_PTR; + obj_write.flags = obj.flags; + obj_write.buffer = buffer_ptr_in_user_space; + obj_write.length = obj.length; + obj_write.parent = obj.parent; + obj_write.parent_offset = obj.parent_offset; + view.write::<BinderBufferObject>(offset, &obj_write)?; + } + BinderObjectRef::Fda(obj) => { + if !allow_fds { + return Err(EPERM.into()); + } + let parent_index = usize::try_from(obj.parent).map_err(|_| EINVAL)?; + let parent_offset = usize::try_from(obj.parent_offset).map_err(|_| EINVAL)?; + let num_fds = usize::try_from(obj.num_fds).map_err(|_| EINVAL)?; + let fds_len = num_fds.checked_mul(size_of::<u32>()).ok_or(EINVAL)?; + + let info = sg_state.validate_parent_fixup(parent_index, parent_offset, fds_len)?; + view.alloc.info_add_fd_reserve(num_fds)?; + + sg_state.ancestors.truncate(info.num_ancestors); + let parent_entry = match sg_state.sg_entries.get_mut(info.parent_sg_index) { + Some(parent_entry) => parent_entry, + None => { + pr_err!( + "validate_parent_fixup returned index out of bounds for sg.entries" + ); + return Err(EINVAL.into()); + } + }; + + parent_entry.fixup_min_offset = info.new_min_offset; + parent_entry + .pointer_fixups + .push( + PointerFixupEntry { + skip: fds_len, + pointer_value: 0, + target_offset: info.target_offset, + }, + GFP_KERNEL, + ) + .map_err(|_| ENOMEM)?; + + let fda_uaddr = parent_entry + .sender_uaddr + .checked_add(parent_offset) + .ok_or(EINVAL)?; + let mut fda_bytes = KVec::new(); + UserSlice::new(UserPtr::from_addr(fda_uaddr as _), fds_len) + .read_all(&mut fda_bytes, GFP_KERNEL)?; + + if fds_len != fda_bytes.len() { + pr_err!("UserSlice::read_all returned wrong length in BINDER_TYPE_FDA"); + return Err(EINVAL.into()); + } + + for i in (0..fds_len).step_by(size_of::<u32>()) { + let fd = { + let mut fd_bytes = [0u8; size_of::<u32>()]; + fd_bytes.copy_from_slice(&fda_bytes[i..i + size_of::<u32>()]); + u32::from_ne_bytes(fd_bytes) + }; + + let file = LocalFile::fget(fd)?; + // SAFETY: The binder driver never calls `fdget_pos` and this code runs from an + // ioctl, so there are no active calls to `fdget_pos` on this thread. + let file = unsafe { LocalFile::assume_no_fdget_pos(file) }; + security::binder_transfer_file( + &self.process.cred, + &view.alloc.process.cred, + &file, + )?; + + // The `validate_parent_fixup` call ensuers that this addition will not + // overflow. + view.alloc.info_add_fd(file, info.target_offset + i, true)?; + } + drop(fda_bytes); + + let mut obj_write = BinderFdArrayObject::default(); + obj_write.hdr.type_ = BINDER_TYPE_FDA; + obj_write.num_fds = obj.num_fds; + obj_write.parent = obj.parent; + obj_write.parent_offset = obj.parent_offset; + view.write::<BinderFdArrayObject>(offset, &obj_write)?; + } + } + Ok(()) + } + + fn apply_sg(&self, alloc: &mut Allocation, sg_state: &mut ScatterGatherState) -> BinderResult { + for sg_entry in &mut sg_state.sg_entries { + let mut end_of_previous_fixup = sg_entry.offset; + let offset_end = sg_entry.offset.checked_add(sg_entry.length).ok_or(EINVAL)?; + + let mut reader = + UserSlice::new(UserPtr::from_addr(sg_entry.sender_uaddr), sg_entry.length).reader(); + for fixup in &mut sg_entry.pointer_fixups { + let fixup_len = if fixup.skip == 0 { + size_of::<u64>() + } else { + fixup.skip + }; + + let target_offset_end = fixup.target_offset.checked_add(fixup_len).ok_or(EINVAL)?; + if fixup.target_offset < end_of_previous_fixup || offset_end < target_offset_end { + pr_warn!( + "Fixups oob {} {} {} {}", + fixup.target_offset, + end_of_previous_fixup, + offset_end, + target_offset_end + ); + return Err(EINVAL.into()); + } + + let copy_off = end_of_previous_fixup; + let copy_len = fixup.target_offset - end_of_previous_fixup; + if let Err(err) = alloc.copy_into(&mut reader, copy_off, copy_len) { + pr_warn!("Failed copying into alloc: {:?}", err); + return Err(err.into()); + } + if fixup.skip == 0 { + let res = alloc.write::<u64>(fixup.target_offset, &fixup.pointer_value); + if let Err(err) = res { + pr_warn!("Failed copying ptr into alloc: {:?}", err); + return Err(err.into()); + } + } + if let Err(err) = reader.skip(fixup_len) { + pr_warn!("Failed skipping {} from reader: {:?}", fixup_len, err); + return Err(err.into()); + } + end_of_previous_fixup = target_offset_end; + } + let copy_off = end_of_previous_fixup; + let copy_len = offset_end - end_of_previous_fixup; + if let Err(err) = alloc.copy_into(&mut reader, copy_off, copy_len) { + pr_warn!("Failed copying remainder into alloc: {:?}", err); + return Err(err.into()); + } + } + Ok(()) + } + + /// This method copies the payload of a transaction into the target process. + /// + /// The resulting payload will have several different components, which will be stored next to + /// each other in the allocation. Furthermore, various objects can be embedded in the payload, + /// and those objects have to be translated so that they make sense to the target transaction. + pub(crate) fn copy_transaction_data( + &self, + to_process: Arc<Process>, + tr: &BinderTransactionDataSg, + debug_id: usize, + allow_fds: bool, + txn_security_ctx_offset: Option<&mut usize>, + ) -> BinderResult<NewAllocation> { + let trd = &tr.transaction_data; + let is_oneway = trd.flags & TF_ONE_WAY != 0; + let mut secctx = if let Some(offset) = txn_security_ctx_offset { + let secid = self.process.cred.get_secid(); + let ctx = match security::SecurityCtx::from_secid(secid) { + Ok(ctx) => ctx, + Err(err) => { + pr_warn!("Failed to get security ctx for id {}: {:?}", secid, err); + return Err(err.into()); + } + }; + Some((offset, ctx)) + } else { + None + }; + + let data_size = trd.data_size.try_into().map_err(|_| EINVAL)?; + let aligned_data_size = ptr_align(data_size).ok_or(EINVAL)?; + let offsets_size = trd.offsets_size.try_into().map_err(|_| EINVAL)?; + let aligned_offsets_size = ptr_align(offsets_size).ok_or(EINVAL)?; + let buffers_size = tr.buffers_size.try_into().map_err(|_| EINVAL)?; + let aligned_buffers_size = ptr_align(buffers_size).ok_or(EINVAL)?; + let aligned_secctx_size = match secctx.as_ref() { + Some((_offset, ctx)) => ptr_align(ctx.len()).ok_or(EINVAL)?, + None => 0, + }; + + // This guarantees that at least `sizeof(usize)` bytes will be allocated. + let len = usize::max( + aligned_data_size + .checked_add(aligned_offsets_size) + .and_then(|sum| sum.checked_add(aligned_buffers_size)) + .and_then(|sum| sum.checked_add(aligned_secctx_size)) + .ok_or(ENOMEM)?, + size_of::<usize>(), + ); + let secctx_off = aligned_data_size + aligned_offsets_size + aligned_buffers_size; + let mut alloc = + match to_process.buffer_alloc(debug_id, len, is_oneway, self.process.task.pid()) { + Ok(alloc) => alloc, + Err(err) => { + pr_warn!( + "Failed to allocate buffer. len:{}, is_oneway:{}", + len, + is_oneway + ); + return Err(err); + } + }; + + // SAFETY: This accesses a union field, but it's okay because the field's type is valid for + // all bit-patterns. + let trd_data_ptr = unsafe { &trd.data.ptr }; + let mut buffer_reader = + UserSlice::new(UserPtr::from_addr(trd_data_ptr.buffer as _), data_size).reader(); + let mut end_of_previous_object = 0; + let mut sg_state = None; + + // Copy offsets if there are any. + if offsets_size > 0 { + { + let mut reader = + UserSlice::new(UserPtr::from_addr(trd_data_ptr.offsets as _), offsets_size) + .reader(); + alloc.copy_into(&mut reader, aligned_data_size, offsets_size)?; + } + + let offsets_start = aligned_data_size; + let offsets_end = aligned_data_size + aligned_offsets_size; + + // This state is used for BINDER_TYPE_PTR objects. + let sg_state = sg_state.insert(ScatterGatherState { + unused_buffer_space: UnusedBufferSpace { + offset: offsets_end, + limit: len, + }, + sg_entries: KVec::new(), + ancestors: KVec::new(), + }); + + // Traverse the objects specified. + let mut view = AllocationView::new(&mut alloc, data_size); + for (index, index_offset) in (offsets_start..offsets_end) + .step_by(size_of::<usize>()) + .enumerate() + { + let offset = view.alloc.read(index_offset)?; + + if offset < end_of_previous_object { + pr_warn!("Got transaction with invalid offset."); + return Err(EINVAL.into()); + } + + // Copy data between two objects. + if end_of_previous_object < offset { + view.copy_into( + &mut buffer_reader, + end_of_previous_object, + offset - end_of_previous_object, + )?; + } + + let mut object = BinderObject::read_from(&mut buffer_reader)?; + + match self.translate_object( + index, + offset, + object.as_ref(), + &mut view, + allow_fds, + sg_state, + ) { + Ok(()) => end_of_previous_object = offset + object.size(), + Err(err) => { + pr_warn!("Error while translating object."); + return Err(err); + } + } + + // Update the indexes containing objects to clean up. + let offset_after_object = index_offset + size_of::<usize>(); + view.alloc + .set_info_offsets(offsets_start..offset_after_object); + } + } + + // Copy remaining raw data. + alloc.copy_into( + &mut buffer_reader, + end_of_previous_object, + data_size - end_of_previous_object, + )?; + + if let Some(sg_state) = sg_state.as_mut() { + if let Err(err) = self.apply_sg(&mut alloc, sg_state) { + pr_warn!("Failure in apply_sg: {:?}", err); + return Err(err); + } + } + + if let Some((off_out, secctx)) = secctx.as_mut() { + if let Err(err) = alloc.write(secctx_off, secctx.as_bytes()) { + pr_warn!("Failed to write security context: {:?}", err); + return Err(err.into()); + } + **off_out = secctx_off; + } + Ok(alloc) + } + + fn unwind_transaction_stack(self: &Arc<Self>) { + let mut thread = self.clone(); + while let Ok(transaction) = { + let mut inner = thread.inner.lock(); + inner.pop_transaction_to_reply(thread.as_ref()) + } { + let reply = Err(BR_DEAD_REPLY); + if !transaction.from.deliver_single_reply(reply, &transaction) { + break; + } + + thread = transaction.from.clone(); + } + } + + pub(crate) fn deliver_reply( + &self, + reply: Result<DLArc<Transaction>, u32>, + transaction: &DArc<Transaction>, + ) { + if self.deliver_single_reply(reply, transaction) { + transaction.from.unwind_transaction_stack(); + } + } + + /// Delivers a reply to the thread that started a transaction. The reply can either be a + /// reply-transaction or an error code to be delivered instead. + /// + /// Returns whether the thread is dead. If it is, the caller is expected to unwind the + /// transaction stack by completing transactions for threads that are dead. + fn deliver_single_reply( + &self, + reply: Result<DLArc<Transaction>, u32>, + transaction: &DArc<Transaction>, + ) -> bool { + if let Ok(transaction) = &reply { + transaction.set_outstanding(&mut self.process.inner.lock()); + } + + { + let mut inner = self.inner.lock(); + if !inner.pop_transaction_replied(transaction) { + return false; + } + + if inner.is_dead { + return true; + } + + match reply { + Ok(work) => { + inner.push_work(work); + } + Err(code) => inner.push_reply_work(code), + } + } + + // Notify the thread now that we've released the inner lock. + self.work_condvar.notify_sync(); + false + } + + /// Determines if the given transaction is the current transaction for this thread. + fn is_current_transaction(&self, transaction: &DArc<Transaction>) -> bool { + let inner = self.inner.lock(); + match &inner.current_transaction { + None => false, + Some(current) => Arc::ptr_eq(current, transaction), + } + } + + /// Determines the current top of the transaction stack. It fails if the top is in another + /// thread (i.e., this thread belongs to a stack but it has called another thread). The top is + /// [`None`] if the thread is not currently participating in a transaction stack. + fn top_of_transaction_stack(&self) -> Result<Option<DArc<Transaction>>> { + let inner = self.inner.lock(); + if let Some(cur) = &inner.current_transaction { + if core::ptr::eq(self, cur.from.as_ref()) { + pr_warn!("got new transaction with bad transaction stack"); + return Err(EINVAL); + } + Ok(Some(cur.clone())) + } else { + Ok(None) + } + } + + fn transaction<T>(self: &Arc<Self>, tr: &BinderTransactionDataSg, inner: T) + where + T: FnOnce(&Arc<Self>, &BinderTransactionDataSg) -> BinderResult, + { + if let Err(err) = inner(self, tr) { + if err.should_pr_warn() { + let mut ee = self.inner.lock().extended_error; + ee.command = err.reply; + ee.param = err.as_errno(); + pr_warn!( + "Transaction failed: {:?} my_pid:{}", + err, + self.process.pid_in_current_ns() + ); + } + + self.push_return_work(err.reply); + } + } + + fn transaction_inner(self: &Arc<Self>, tr: &BinderTransactionDataSg) -> BinderResult { + // SAFETY: Handle's type has no invalid bit patterns. + let handle = unsafe { tr.transaction_data.target.handle }; + let node_ref = self.process.get_transaction_node(handle)?; + 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? + let top = self.top_of_transaction_stack()?; + let list_completion = DTRWrap::arc_try_new(DeliverCode::new(BR_TRANSACTION_COMPLETE))?; + let completion = list_completion.clone_arc(); + let transaction = Transaction::new(node_ref, top, self, tr)?; + + // Check that the transaction stack hasn't changed while the lock was released, then update + // it with the new transaction. + { + let mut inner = self.inner.lock(); + if !transaction.is_stacked_on(&inner.current_transaction) { + pr_warn!("Transaction stack changed during transaction!"); + return Err(EINVAL.into()); + } + inner.current_transaction = Some(transaction.clone_arc()); + // We push the completion as a deferred work so that we wait for the reply before + // returning to userland. + inner.push_work_deferred(list_completion); + } + + if let Err(e) = transaction.submit() { + completion.skip(); + // Define `transaction` first to drop it after `inner`. + let transaction; + let mut inner = self.inner.lock(); + transaction = inner.current_transaction.take().unwrap(); + inner.current_transaction = transaction.clone_next(); + Err(e) + } else { + Ok(()) + } + } + + fn reply_inner(self: &Arc<Self>, tr: &BinderTransactionDataSg) -> BinderResult { + let orig = self.inner.lock().pop_transaction_to_reply(self)?; + if !orig.from.is_current_transaction(&orig) { + return Err(EINVAL.into()); + } + + // We need to complete the transaction even if we cannot complete building the reply. + let out = (|| -> BinderResult<_> { + let completion = DTRWrap::arc_try_new(DeliverCode::new(BR_TRANSACTION_COMPLETE))?; + let process = orig.from.process.clone(); + let allow_fds = orig.flags & TF_ACCEPT_FDS != 0; + let reply = Transaction::new_reply(self, process, tr, allow_fds)?; + self.inner.lock().push_work(completion); + orig.from.deliver_reply(Ok(reply), &orig); + Ok(()) + })() + .map_err(|mut err| { + // 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!( + "Failure {:?} during reply - delivering BR_FAILED_REPLY to sender.", + err + ); + let reply = Err(BR_FAILED_REPLY); + orig.from.deliver_reply(reply, &orig); + err.reply = BR_TRANSACTION_COMPLETE; + err + }); + + out + } + + fn oneway_transaction_inner(self: &Arc<Self>, tr: &BinderTransactionDataSg) -> BinderResult { + // SAFETY: The `handle` field is valid for all possible byte values, so reading from the + // union is okay. + let handle = unsafe { tr.transaction_data.target.handle }; + let node_ref = self.process.get_transaction_node(handle)?; + security::binder_transaction(&self.process.cred, &node_ref.node.owner.cred)?; + let transaction = Transaction::new(node_ref, None, self, tr)?; + let code = if self.process.is_oneway_spam_detection_enabled() + && transaction.oneway_spam_detected + { + BR_ONEWAY_SPAM_SUSPECT + } else { + BR_TRANSACTION_COMPLETE + }; + let list_completion = DTRWrap::arc_try_new(DeliverCode::new(code))?; + let completion = list_completion.clone_arc(); + self.inner.lock().push_work(list_completion); + match transaction.submit() { + Ok(()) => Ok(()), + Err(err) => { + completion.skip(); + Err(err) + } + } + } + + fn write(self: &Arc<Self>, req: &mut BinderWriteRead) -> Result { + let write_start = req.write_buffer.wrapping_add(req.write_consumed); + let write_len = req.write_size.saturating_sub(req.write_consumed); + let mut reader = + UserSlice::new(UserPtr::from_addr(write_start as _), write_len as _).reader(); + + while reader.len() >= size_of::<u32>() && self.inner.lock().return_work.is_unused() { + let before = reader.len(); + let cmd = reader.read::<u32>()?; + GLOBAL_STATS.inc_bc(cmd); + self.process.stats.inc_bc(cmd); + match cmd { + BC_TRANSACTION => { + let tr = reader.read::<BinderTransactionData>()?.with_buffers_size(0); + if tr.transaction_data.flags & TF_ONE_WAY != 0 { + self.transaction(&tr, Self::oneway_transaction_inner); + } else { + self.transaction(&tr, Self::transaction_inner); + } + } + BC_TRANSACTION_SG => { + let tr = reader.read::<BinderTransactionDataSg>()?; + if tr.transaction_data.flags & TF_ONE_WAY != 0 { + self.transaction(&tr, Self::oneway_transaction_inner); + } else { + self.transaction(&tr, Self::transaction_inner); + } + } + BC_REPLY => { + let tr = reader.read::<BinderTransactionData>()?.with_buffers_size(0); + self.transaction(&tr, Self::reply_inner) + } + BC_REPLY_SG => { + let tr = reader.read::<BinderTransactionDataSg>()?; + self.transaction(&tr, Self::reply_inner) + } + BC_FREE_BUFFER => { + let buffer = self.process.buffer_get(reader.read()?); + if let Some(buffer) = &buffer { + if buffer.looper_need_return_on_free() { + self.inner.lock().looper_need_return = true; + } + } + drop(buffer); + } + BC_INCREFS => { + self.process + .as_arc_borrow() + .update_ref(reader.read()?, true, false)? + } + BC_ACQUIRE => { + self.process + .as_arc_borrow() + .update_ref(reader.read()?, true, true)? + } + BC_RELEASE => { + self.process + .as_arc_borrow() + .update_ref(reader.read()?, false, true)? + } + BC_DECREFS => { + self.process + .as_arc_borrow() + .update_ref(reader.read()?, false, false)? + } + BC_INCREFS_DONE => self.process.inc_ref_done(&mut reader, false)?, + BC_ACQUIRE_DONE => self.process.inc_ref_done(&mut reader, true)?, + BC_REQUEST_DEATH_NOTIFICATION => self.process.request_death(&mut reader, self)?, + BC_CLEAR_DEATH_NOTIFICATION => self.process.clear_death(&mut reader, self)?, + BC_DEAD_BINDER_DONE => self.process.dead_binder_done(reader.read()?, self), + BC_REGISTER_LOOPER => { + let valid = self.process.register_thread(); + self.inner.lock().looper_register(valid); + } + BC_ENTER_LOOPER => self.inner.lock().looper_enter(), + BC_EXIT_LOOPER => self.inner.lock().looper_exit(), + BC_REQUEST_FREEZE_NOTIFICATION => self.process.request_freeze_notif(&mut reader)?, + BC_CLEAR_FREEZE_NOTIFICATION => self.process.clear_freeze_notif(&mut reader)?, + BC_FREEZE_NOTIFICATION_DONE => self.process.freeze_notif_done(&mut reader)?, + + // Fail if given an unknown error code. + // BC_ATTEMPT_ACQUIRE and BC_ACQUIRE_RESULT are no longer supported. + _ => return Err(EINVAL), + } + // Update the number of write bytes consumed. + req.write_consumed += (before - reader.len()) as u64; + } + + Ok(()) + } + + fn read(self: &Arc<Self>, req: &mut BinderWriteRead, wait: bool) -> Result { + let read_start = req.read_buffer.wrapping_add(req.read_consumed); + let read_len = req.read_size.saturating_sub(req.read_consumed); + let mut writer = BinderReturnWriter::new( + UserSlice::new(UserPtr::from_addr(read_start as _), read_len as _).writer(), + self, + ); + let (in_pool, use_proc_queue) = { + let inner = self.inner.lock(); + (inner.is_looper(), inner.should_use_process_work_queue()) + }; + + let getter = if use_proc_queue { + Self::get_work + } else { + Self::get_work_local + }; + + // Reserve some room at the beginning of the read buffer so that we can send a + // BR_SPAWN_LOOPER if we need to. + let mut has_noop_placeholder = false; + if req.read_consumed == 0 { + if let Err(err) = writer.write_code(BR_NOOP) { + pr_warn!("Failure when writing BR_NOOP at beginning of buffer."); + return Err(err); + } + has_noop_placeholder = true; + } + + // Loop doing work while there is room in the buffer. + let initial_len = writer.len(); + while writer.len() >= size_of::<uapi::binder_transaction_data_secctx>() + 4 { + match getter(self, wait && initial_len == writer.len()) { + Ok(Some(work)) => match work.into_arc().do_work(self, &mut writer) { + Ok(true) => {} + Ok(false) => break, + Err(err) => { + return Err(err); + } + }, + Ok(None) => { + break; + } + Err(err) => { + // Propagate the error if we haven't written anything else. + if err != EINTR && err != EAGAIN { + pr_warn!("Failure in work getter: {:?}", err); + } + if initial_len == writer.len() { + return Err(err); + } else { + break; + } + } + } + } + + req.read_consumed += read_len - writer.len() as u64; + + // Write BR_SPAWN_LOOPER if the process needs more threads for its pool. + if has_noop_placeholder && in_pool && self.process.needs_thread() { + let mut writer = + UserSlice::new(UserPtr::from_addr(req.read_buffer as _), req.read_size as _) + .writer(); + writer.write(&BR_SPAWN_LOOPER)?; + } + Ok(()) + } + + pub(crate) fn write_read(self: &Arc<Self>, data: UserSlice, wait: bool) -> Result { + let (mut reader, mut writer) = data.reader_writer(); + let mut req = reader.read::<BinderWriteRead>()?; + + // Go through the write buffer. + let mut ret = Ok(()); + if req.write_size > 0 { + ret = self.write(&mut req); + if let Err(err) = ret { + pr_warn!( + "Write failure {:?} in pid:{}", + err, + self.process.pid_in_current_ns() + ); + req.read_consumed = 0; + writer.write(&req)?; + self.inner.lock().looper_need_return = false; + return ret; + } + } + + // Go through the work queue. + if req.read_size > 0 { + ret = self.read(&mut req, wait); + if ret.is_err() && ret != Err(EINTR) { + pr_warn!( + "Read failure {:?} in pid:{}", + ret, + self.process.pid_in_current_ns() + ); + } + } + + // Write the request back so that the consumed fields are visible to the caller. + writer.write(&req)?; + + self.inner.lock().looper_need_return = false; + + ret + } + + pub(crate) fn poll(&self, file: &File, table: PollTable<'_>) -> (bool, u32) { + table.register_wait(file, &self.work_condvar); + let mut inner = self.inner.lock(); + (inner.should_use_process_work_queue(), inner.poll()) + } + + /// Make the call to `get_work` or `get_work_local` return immediately, if any. + pub(crate) fn exit_looper(&self) { + let mut inner = self.inner.lock(); + let should_notify = inner.looper_flags & LOOPER_WAITING != 0; + if should_notify { + inner.looper_need_return = true; + } + drop(inner); + + if should_notify { + self.work_condvar.notify_one(); + } + } + + pub(crate) fn notify_if_poll_ready(&self, sync: bool) { + // Determine if we need to notify. This requires the lock. + let inner = self.inner.lock(); + let notify = inner.looper_flags & LOOPER_POLL != 0 && inner.should_use_process_work_queue(); + drop(inner); + + // Now that the lock is no longer held, notify the waiters if we have to. + if notify { + if sync { + self.work_condvar.notify_sync(); + } else { + self.work_condvar.notify_one(); + } + } + } + + pub(crate) fn release(self: &Arc<Self>) { + self.inner.lock().is_dead = true; + + //self.work_condvar.clear(); + self.unwind_transaction_stack(); + + // Cancel all pending work items. + while let Ok(Some(work)) = self.get_work_local(false) { + work.into_arc().cancel(); + } + } +} + +#[pin_data] +struct ThreadError { + error_code: AtomicU32, + #[pin] + links_track: AtomicTracker, +} + +impl ThreadError { + fn try_new() -> Result<DArc<Self>> { + DTRWrap::arc_pin_init(pin_init!(Self { + error_code: AtomicU32::new(BR_OK), + links_track <- AtomicTracker::new(), + })) + .map(ListArc::into_arc) + } + + fn set_error_code(&self, code: u32) { + self.error_code.store(code, Ordering::Relaxed); + } + + fn is_unused(&self) -> bool { + self.error_code.load(Ordering::Relaxed) == BR_OK + } +} + +impl DeliverToRead for ThreadError { + fn do_work( + self: DArc<Self>, + _thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + let code = self.error_code.load(Ordering::Relaxed); + self.error_code.store(BR_OK, Ordering::Relaxed); + writer.write_code(code)?; + Ok(true) + } + + fn cancel(self: DArc<Self>) {} + + fn should_sync_wakeup(&self) -> bool { + false + } + + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + seq_print!( + m, + "{}transaction error: {}\n", + prefix, + self.error_code.load(Ordering::Relaxed) + ); + Ok(()) + } +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for ThreadError { + tracked_by links_track: AtomicTracker; + } +} diff --git a/drivers/android/binder/trace.rs b/drivers/android/binder/trace.rs new file mode 100644 index 000000000000..af0e4392805e --- /dev/null +++ b/drivers/android/binder/trace.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::ffi::{c_uint, c_ulong}; +use kernel::tracepoint::declare_trace; + +declare_trace! { + unsafe fn rust_binder_ioctl(cmd: c_uint, arg: c_ulong); +} + +#[inline] +pub(crate) fn trace_ioctl(cmd: u32, arg: usize) { + // SAFETY: Always safe to call. + unsafe { rust_binder_ioctl(cmd, arg as c_ulong) } +} diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs new file mode 100644 index 000000000000..02512175d622 --- /dev/null +++ b/drivers/android/binder/transaction.rs @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use core::sync::atomic::{AtomicBool, Ordering}; +use kernel::{ + prelude::*, + seq_file::SeqFile, + seq_print, + sync::{Arc, SpinLock}, + task::Kuid, + time::{Instant, Monotonic}, + types::ScopeGuard, +}; + +use crate::{ + allocation::{Allocation, TranslatedFds}, + defs::*, + error::{BinderError, BinderResult}, + node::{Node, NodeRef}, + process::{Process, ProcessInner}, + ptr_align, + thread::{PushWorkRes, Thread}, + BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead, +}; + +#[pin_data(PinnedDrop)] +pub(crate) struct Transaction { + pub(crate) debug_id: usize, + target_node: Option<DArc<Node>>, + pub(crate) from_parent: Option<DArc<Transaction>>, + pub(crate) from: Arc<Thread>, + pub(crate) to: Arc<Process>, + #[pin] + allocation: SpinLock<Option<Allocation>>, + is_outstanding: AtomicBool, + code: u32, + pub(crate) flags: u32, + data_size: usize, + offsets_size: usize, + data_address: usize, + sender_euid: Kuid, + txn_security_ctx_off: Option<usize>, + pub(crate) oneway_spam_detected: bool, + start_time: Instant<Monotonic>, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for Transaction { untracked; } +} + +impl Transaction { + pub(crate) fn new( + node_ref: NodeRef, + from_parent: Option<DArc<Transaction>>, + from: &Arc<Thread>, + tr: &BinderTransactionDataSg, + ) -> BinderResult<DLArc<Self>> { + let debug_id = super::next_debug_id(); + let trd = &tr.transaction_data; + let allow_fds = node_ref.node.flags & FLAT_BINDER_FLAG_ACCEPTS_FDS != 0; + 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( + to.clone(), + tr, + 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); + } + }; + let oneway_spam_detected = alloc.oneway_spam_detected; + if trd.flags & TF_ONE_WAY != 0 { + if from_parent.is_some() { + pr_warn!("Oneway transaction should not be in a transaction stack."); + return Err(EINVAL.into()); + } + alloc.set_info_oneway_node(node_ref.node.clone()); + } + if trd.flags & TF_CLEAR_BUF != 0 { + alloc.set_info_clear_on_drop(); + } + let target_node = node_ref.node.clone(); + alloc.set_info_target_node(node_ref); + let data_address = alloc.ptr; + + Ok(DTRWrap::arc_pin_init(pin_init!(Transaction { + debug_id, + target_node: Some(target_node), + from_parent, + sender_euid: from.process.task.euid(), + from: from.clone(), + to, + code: trd.code, + flags: trd.flags, + data_size: trd.data_size as _, + offsets_size: trd.offsets_size as _, + data_address, + allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"), + is_outstanding: AtomicBool::new(false), + txn_security_ctx_off, + oneway_spam_detected, + start_time: Instant::now(), + }))?) + } + + pub(crate) fn new_reply( + from: &Arc<Thread>, + to: Arc<Process>, + tr: &BinderTransactionDataSg, + allow_fds: bool, + ) -> BinderResult<DLArc<Self>> { + let debug_id = super::next_debug_id(); + let trd = &tr.transaction_data; + let mut alloc = match from.copy_transaction_data(to.clone(), tr, debug_id, allow_fds, None) + { + Ok(alloc) => alloc, + Err(err) => { + pr_warn!("Failure in copy_transaction_data: {:?}", err); + return Err(err); + } + }; + let oneway_spam_detected = alloc.oneway_spam_detected; + if trd.flags & TF_CLEAR_BUF != 0 { + alloc.set_info_clear_on_drop(); + } + Ok(DTRWrap::arc_pin_init(pin_init!(Transaction { + debug_id, + target_node: None, + from_parent: None, + sender_euid: from.process.task.euid(), + from: from.clone(), + to, + code: trd.code, + flags: trd.flags, + data_size: trd.data_size as _, + offsets_size: trd.offsets_size as _, + data_address: alloc.ptr, + allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"), + is_outstanding: AtomicBool::new(false), + txn_security_ctx_off: None, + oneway_spam_detected, + start_time: Instant::now(), + }))?) + } + + #[inline(never)] + pub(crate) fn debug_print_inner(&self, m: &SeqFile, prefix: &str) { + seq_print!( + m, + "{}{}: from {}:{} to {} code {:x} flags {:x} elapsed {}ms", + prefix, + self.debug_id, + self.from.process.task.pid(), + self.from.id, + self.to.task.pid(), + self.code, + self.flags, + self.start_time.elapsed().as_millis(), + ); + if let Some(target_node) = &self.target_node { + seq_print!(m, " node {}", target_node.debug_id); + } + seq_print!(m, " size {}:{}\n", self.data_size, self.offsets_size); + } + + /// Determines if the transaction is stacked on top of the given transaction. + pub(crate) fn is_stacked_on(&self, onext: &Option<DArc<Self>>) -> bool { + match (&self.from_parent, onext) { + (None, None) => true, + (Some(from_parent), Some(next)) => Arc::ptr_eq(from_parent, next), + _ => false, + } + } + + /// Returns a pointer to the next transaction on the transaction stack, if there is one. + pub(crate) fn clone_next(&self) -> Option<DArc<Self>> { + Some(self.from_parent.as_ref()?.clone()) + } + + /// Searches in the transaction stack for a thread that belongs to the target process. This is + /// useful when finding a target for a new transaction: if the node belongs to a process that + /// is already part of the transaction stack, we reuse the thread. + fn find_target_thread(&self) -> Option<Arc<Thread>> { + let mut it = &self.from_parent; + while let Some(transaction) = it { + if Arc::ptr_eq(&transaction.from.process, &self.to) { + return Some(transaction.from.clone()); + } + it = &transaction.from_parent; + } + None + } + + /// Searches in the transaction stack for a transaction originating at the given thread. + pub(crate) fn find_from(&self, thread: &Thread) -> Option<&DArc<Transaction>> { + let mut it = &self.from_parent; + while let Some(transaction) = it { + if core::ptr::eq(thread, transaction.from.as_ref()) { + return Some(transaction); + } + + it = &transaction.from_parent; + } + None + } + + pub(crate) fn set_outstanding(&self, to_process: &mut ProcessInner) { + // No race because this method is only called once. + if !self.is_outstanding.load(Ordering::Relaxed) { + self.is_outstanding.store(true, Ordering::Relaxed); + to_process.add_outstanding_txn(); + } + } + + /// Decrement `outstanding_txns` in `to` if it hasn't already been decremented. + fn drop_outstanding_txn(&self) { + // No race because this is called at most twice, and one of the calls are in the + // destructor, which is guaranteed to not race with any other operations on the + // transaction. It also cannot race with `set_outstanding`, since submission happens + // before delivery. + if self.is_outstanding.load(Ordering::Relaxed) { + self.is_outstanding.store(false, Ordering::Relaxed); + self.to.drop_outstanding_txn(); + } + } + + /// Submits the transaction to a work queue. Uses a thread if there is one in the transaction + /// stack, otherwise uses the destination process. + /// + /// Not used for replies. + pub(crate) fn submit(self: DLArc<Self>) -> BinderResult { + // Defined before `process_inner` so that the destructor runs after releasing the lock. + let mut _t_outdated; + + let oneway = self.flags & TF_ONE_WAY != 0; + let process = self.to.clone(); + let mut process_inner = process.inner.lock(); + + self.set_outstanding(&mut process_inner); + + if oneway { + if let Some(target_node) = self.target_node.clone() { + if process_inner.is_frozen { + process_inner.async_recv = true; + if self.flags & TF_UPDATE_TXN != 0 { + if let Some(t_outdated) = + target_node.take_outdated_transaction(&self, &mut process_inner) + { + // Save the transaction to be dropped after locks are released. + _t_outdated = t_outdated; + } + } + } + match target_node.submit_oneway(self, &mut process_inner) { + Ok(()) => {} + Err((err, work)) => { + drop(process_inner); + // Drop work after releasing process lock. + drop(work); + return Err(err); + } + } + + if process_inner.is_frozen { + return Err(BinderError::new_frozen_oneway()); + } else { + return Ok(()); + } + } else { + pr_err!("Failed to submit oneway transaction to node."); + } + } + + if process_inner.is_frozen { + process_inner.sync_recv = true; + return Err(BinderError::new_frozen()); + } + + let res = if let Some(thread) = self.find_target_thread() { + match thread.push_work(self) { + PushWorkRes::Ok => Ok(()), + PushWorkRes::FailedDead(me) => Err((BinderError::new_dead(), me)), + } + } else { + process_inner.push_work(self) + }; + drop(process_inner); + + match res { + Ok(()) => Ok(()), + Err((err, work)) => { + // Drop work after releasing process lock. + drop(work); + Err(err) + } + } + } + + /// Check whether one oneway transaction can supersede another. + pub(crate) fn can_replace(&self, old: &Transaction) -> bool { + if self.from.process.task.pid() != old.from.process.task.pid() { + return false; + } + + if self.flags & old.flags & (TF_ONE_WAY | TF_UPDATE_TXN) != (TF_ONE_WAY | TF_UPDATE_TXN) { + return false; + } + + let target_node_match = match (self.target_node.as_ref(), old.target_node.as_ref()) { + (None, None) => true, + (Some(tn1), Some(tn2)) => Arc::ptr_eq(tn1, tn2), + _ => false, + }; + + self.code == old.code && self.flags == old.flags && target_node_match + } + + fn prepare_file_list(&self) -> Result<TranslatedFds> { + let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?; + + match alloc.translate_fds() { + Ok(translated) => { + *self.allocation.lock() = Some(alloc); + Ok(translated) + } + Err(err) => { + // Free the allocation eagerly. + drop(alloc); + Err(err) + } + } + } +} + +impl DeliverToRead for Transaction { + fn do_work( + self: DArc<Self>, + thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + let send_failed_reply = ScopeGuard::new(|| { + if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { + let reply = Err(BR_FAILED_REPLY); + self.from.deliver_reply(reply, &self); + } + self.drop_outstanding_txn(); + }); + + let files = if let Ok(list) = self.prepare_file_list() { + list + } else { + // On failure to process the list, we send a reply back to the sender and ignore the + // transaction on the recipient. + return Ok(true); + }; + + let mut tr_sec = BinderTransactionDataSecctx::default(); + let tr = tr_sec.tr_data(); + if let Some(target_node) = &self.target_node { + let (ptr, cookie) = target_node.get_id(); + tr.target.ptr = ptr as _; + tr.cookie = cookie as _; + }; + tr.code = self.code; + tr.flags = self.flags; + tr.data_size = self.data_size as _; + tr.data.ptr.buffer = self.data_address as _; + tr.offsets_size = self.offsets_size as _; + if tr.offsets_size > 0 { + tr.data.ptr.offsets = (self.data_address + ptr_align(self.data_size).unwrap()) as _; + } + tr.sender_euid = self.sender_euid.into_uid_in_current_ns(); + tr.sender_pid = 0; + if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { + // Not a reply and not one-way. + tr.sender_pid = self.from.process.pid_in_current_ns(); + } + let code = if self.target_node.is_none() { + BR_REPLY + } else if self.txn_security_ctx_off.is_some() { + BR_TRANSACTION_SEC_CTX + } else { + BR_TRANSACTION + }; + + // Write the transaction code and data to the user buffer. + writer.write_code(code)?; + if let Some(off) = self.txn_security_ctx_off { + tr_sec.secctx = (self.data_address + off) as u64; + writer.write_payload(&tr_sec)?; + } else { + writer.write_payload(&*tr)?; + } + + let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?; + + // Dismiss the completion of transaction with a failure. No failure paths are allowed from + // here on out. + send_failed_reply.dismiss(); + + // Commit files, and set FDs in FDA to be closed on buffer free. + let close_on_free = files.commit(); + alloc.set_info_close_on_free(close_on_free); + + // It is now the user's responsibility to clear the allocation. + alloc.keep_alive(); + + self.drop_outstanding_txn(); + + // When this is not a reply and not a oneway transaction, update `current_transaction`. If + // it's a reply, `current_transaction` has already been updated appropriately. + if self.target_node.is_some() && tr_sec.transaction_data.flags & TF_ONE_WAY == 0 { + thread.set_current_transaction(self); + } + + Ok(false) + } + + fn cancel(self: DArc<Self>) { + let allocation = self.allocation.lock().take(); + drop(allocation); + + // If this is not a reply or oneway transaction, then send a dead reply. + if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { + let reply = Err(BR_DEAD_REPLY); + self.from.deliver_reply(reply, &self); + } + + self.drop_outstanding_txn(); + } + + fn should_sync_wakeup(&self) -> bool { + self.flags & TF_ONE_WAY == 0 + } + + fn debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result<()> { + self.debug_print_inner(m, tprefix); + Ok(()) + } +} + +#[pinned_drop] +impl PinnedDrop for Transaction { + fn drop(self: Pin<&mut Self>) { + self.drop_outstanding_txn(); + } +} diff --git a/drivers/android/binder_internal.h b/drivers/android/binder_internal.h index 8b08976146ba..342574bfd28a 100644 --- a/drivers/android/binder_internal.h +++ b/drivers/android/binder_internal.h @@ -537,8 +537,8 @@ struct binder_transaction { struct binder_proc *to_proc; struct binder_thread *to_thread; struct binder_transaction *to_parent; - unsigned need_reply:1; - /* unsigned is_dead:1; */ /* not used at the moment */ + unsigned is_async:1; + unsigned is_reply:1; struct binder_buffer *buffer; unsigned int code; diff --git a/drivers/android/binder_netlink.c b/drivers/android/binder_netlink.c new file mode 100644 index 000000000000..d05397a50ca6 --- /dev/null +++ b/drivers/android/binder_netlink.c @@ -0,0 +1,31 @@ +// 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 */ + +#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 new file mode 100644 index 000000000000..882c7a6b537e --- /dev/null +++ b/drivers/android/binder_netlink.h @@ -0,0 +1,20 @@ +/* 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 */ + +#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 index 97a78e5623db..fa5eb61cf580 100644 --- a/drivers/android/binder_trace.h +++ b/drivers/android/binder_trace.h @@ -402,6 +402,43 @@ TRACE_EVENT(binder_return, "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 diff --git a/drivers/android/binderfs.c b/drivers/android/binderfs.c index 0d9d95a7fb60..be8e64eb39ec 100644 --- a/drivers/android/binderfs.c +++ b/drivers/android/binderfs.c @@ -59,6 +59,7 @@ struct binder_features { bool oneway_spam_detection; bool extended_error; bool freeze_notification; + bool transaction_report; }; static const struct constant_table binderfs_param_stats[] = { @@ -76,6 +77,7 @@ 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) @@ -601,6 +603,12 @@ static int init_binder_features(struct super_block *sb) 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; } diff --git a/drivers/android/dbitmap.h b/drivers/android/dbitmap.h index 956f1bd087d1..c7299ce8b374 100644 --- a/drivers/android/dbitmap.h +++ b/drivers/android/dbitmap.h @@ -37,6 +37,7 @@ 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. */ diff --git a/drivers/bus/mhi/ep/main.c b/drivers/bus/mhi/ep/main.c index b3eafcf2a2c5..cdea24e92919 100644 --- a/drivers/bus/mhi/ep/main.c +++ b/drivers/bus/mhi/ep/main.c @@ -403,17 +403,13 @@ static int mhi_ep_read_channel(struct mhi_ep_cntrl *mhi_cntrl, { struct mhi_ep_chan *mhi_chan = &mhi_cntrl->mhi_chan[ring->ch_id]; struct device *dev = &mhi_cntrl->mhi_dev->dev; - size_t tr_len, read_offset, write_offset; + size_t tr_len, read_offset; struct mhi_ep_buf_info buf_info = {}; u32 len = MHI_EP_DEFAULT_MTU; struct mhi_ring_element *el; - bool tr_done = false; void *buf_addr; - u32 buf_left; int ret; - buf_left = len; - do { /* Don't process the transfer ring if the channel is not in RUNNING state */ if (mhi_chan->state != MHI_CH_STATE_RUNNING) { @@ -426,24 +422,23 @@ static int mhi_ep_read_channel(struct mhi_ep_cntrl *mhi_cntrl, /* Check if there is data pending to be read from previous read operation */ if (mhi_chan->tre_bytes_left) { dev_dbg(dev, "TRE bytes remaining: %u\n", mhi_chan->tre_bytes_left); - tr_len = min(buf_left, mhi_chan->tre_bytes_left); + tr_len = min(len, mhi_chan->tre_bytes_left); } else { mhi_chan->tre_loc = MHI_TRE_DATA_GET_PTR(el); mhi_chan->tre_size = MHI_TRE_DATA_GET_LEN(el); mhi_chan->tre_bytes_left = mhi_chan->tre_size; - tr_len = min(buf_left, mhi_chan->tre_size); + tr_len = min(len, mhi_chan->tre_size); } read_offset = mhi_chan->tre_size - mhi_chan->tre_bytes_left; - write_offset = len - buf_left; buf_addr = kmem_cache_zalloc(mhi_cntrl->tre_buf_cache, GFP_KERNEL); if (!buf_addr) return -ENOMEM; buf_info.host_addr = mhi_chan->tre_loc + read_offset; - buf_info.dev_addr = buf_addr + write_offset; + buf_info.dev_addr = buf_addr; buf_info.size = tr_len; buf_info.cb = mhi_ep_read_completion; buf_info.cb_buf = buf_addr; @@ -459,16 +454,12 @@ static int mhi_ep_read_channel(struct mhi_ep_cntrl *mhi_cntrl, goto err_free_buf_addr; } - buf_left -= tr_len; mhi_chan->tre_bytes_left -= tr_len; - if (!mhi_chan->tre_bytes_left) { - if (MHI_TRE_DATA_GET_IEOT(el)) - tr_done = true; - + if (!mhi_chan->tre_bytes_left) mhi_chan->rd_offset = (mhi_chan->rd_offset + 1) % ring->ring_size; - } - } while (buf_left && !tr_done); + /* Read until the some buffer is left or the ring becomes not empty */ + } while (!mhi_ep_queue_is_empty(mhi_chan->mhi_dev, DMA_TO_DEVICE)); return 0; @@ -502,15 +493,11 @@ static int mhi_ep_process_ch_ring(struct mhi_ep_ring *ring) mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result); } else { /* UL channel */ - do { - ret = mhi_ep_read_channel(mhi_cntrl, ring); - if (ret < 0) { - dev_err(&mhi_chan->mhi_dev->dev, "Failed to read channel\n"); - return ret; - } - - /* Read until the ring becomes empty */ - } while (!mhi_ep_queue_is_empty(mhi_chan->mhi_dev, DMA_TO_DEVICE)); + ret = mhi_ep_read_channel(mhi_cntrl, ring); + if (ret < 0) { + dev_err(&mhi_chan->mhi_dev->dev, "Failed to read channel\n"); + return ret; + } } return 0; diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c index 7f72aab38ce9..099be8dd1900 100644 --- a/drivers/bus/mhi/host/init.c +++ b/drivers/bus/mhi/host/init.c @@ -194,7 +194,6 @@ static void mhi_deinit_free_irq(struct mhi_controller *mhi_cntrl) static int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl) { struct mhi_event *mhi_event = mhi_cntrl->mhi_event; - struct device *dev = &mhi_cntrl->mhi_dev->dev; unsigned long irq_flags = IRQF_SHARED | IRQF_NO_SUSPEND; int i, ret; @@ -221,7 +220,7 @@ static int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl) continue; if (mhi_event->irq >= mhi_cntrl->nr_irqs) { - dev_err(dev, "irq %d not available for event ring\n", + dev_err(mhi_cntrl->cntrl_dev, "irq %d not available for event ring\n", mhi_event->irq); ret = -EINVAL; goto error_request; @@ -232,7 +231,7 @@ static int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl) irq_flags, "mhi", mhi_event); if (ret) { - dev_err(dev, "Error requesting irq:%d for ev:%d\n", + dev_err(mhi_cntrl->cntrl_dev, "Error requesting irq:%d for ev:%d\n", mhi_cntrl->irq[mhi_event->irq], i); goto error_request; } diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h index 034be33565b7..7937bb1f742c 100644 --- a/drivers/bus/mhi/host/internal.h +++ b/drivers/bus/mhi/host/internal.h @@ -170,6 +170,8 @@ enum mhi_pm_state { MHI_PM_IN_ERROR_STATE(pm_state)) #define MHI_PM_IN_SUSPEND_STATE(pm_state) (pm_state & \ (MHI_PM_M3_ENTER | MHI_PM_M3)) +#define MHI_PM_FATAL_ERROR(pm_state) ((pm_state == MHI_PM_FW_DL_ERR) || \ + (pm_state >= MHI_PM_SYS_ERR_FAIL)) #define NR_OF_CMD_RINGS 1 #define CMD_EL_PER_RING 128 @@ -403,6 +405,7 @@ int mhi_process_data_event_ring(struct mhi_controller *mhi_cntrl, struct mhi_event *mhi_event, u32 event_quota); int mhi_process_ctrl_ev_ring(struct mhi_controller *mhi_cntrl, struct mhi_event *mhi_event, u32 event_quota); +void mhi_uevent_notify(struct mhi_controller *mhi_cntrl, enum mhi_ee_type ee); /* ISR handlers */ irqreturn_t mhi_irq_handler(int irq_number, void *dev); diff --git a/drivers/bus/mhi/host/main.c b/drivers/bus/mhi/host/main.c index 52bef663e182..861551274319 100644 --- a/drivers/bus/mhi/host/main.c +++ b/drivers/bus/mhi/host/main.c @@ -512,6 +512,7 @@ irqreturn_t mhi_intvec_threaded_handler(int irq_number, void *priv) if (mhi_cntrl->rddm_image && mhi_is_active(mhi_cntrl)) { mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_RDDM); mhi_cntrl->ee = ee; + mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee); wake_up_all(&mhi_cntrl->state_event); } break; diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c index 4edb5bb476ba..b188bbf7de04 100644 --- a/drivers/bus/mhi/host/pci_generic.c +++ b/drivers/bus/mhi/host/pci_generic.c @@ -34,28 +34,34 @@ /** * struct mhi_pci_dev_info - MHI PCI device specific information * @config: MHI controller configuration + * @vf_config: MHI controller configuration for Virtual function (optional) * @name: name of the PCI module * @fw: firmware path (if any) * @edl: emergency download mode firmware path (if any) * @edl_trigger: capable of triggering EDL mode in the device (if supported) * @bar_num: PCI base address register to use for MHI MMIO register space * @dma_data_width: DMA transfer word size (32 or 64 bits) + * @vf_dma_data_width: DMA transfer word size for VF's (optional) * @mru_default: default MRU size for MBIM network packets * @sideband_wake: Devices using dedicated sideband GPIO for wakeup instead * of inband wake support (such as sdx24) * @no_m3: M3 not supported + * @reset_on_remove: Set true for devices that require SoC during driver removal */ struct mhi_pci_dev_info { const struct mhi_controller_config *config; + const struct mhi_controller_config *vf_config; const char *name; const char *fw; const char *edl; bool edl_trigger; unsigned int bar_num; unsigned int dma_data_width; + unsigned int vf_dma_data_width; unsigned int mru_default; bool sideband_wake; bool no_m3; + bool reset_on_remove; }; #define MHI_CHANNEL_CONFIG_UL(ch_num, ch_name, el_count, ev_ring) \ @@ -296,8 +302,10 @@ static const struct mhi_pci_dev_info mhi_qcom_qdu100_info = { .config = &mhi_qcom_qdu100_config, .bar_num = MHI_PCI_DEFAULT_BAR_NUM, .dma_data_width = 32, + .vf_dma_data_width = 40, .sideband_wake = false, .no_m3 = true, + .reset_on_remove = true, }; static const struct mhi_channel_config mhi_qcom_sa8775p_channels[] = { @@ -917,20 +925,8 @@ static const struct pci_device_id mhi_pci_id_table[] = { /* Telit FE990A */ { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, 0x1c5d, 0x2015), .driver_data = (kernel_ulong_t) &mhi_telit_fe990a_info }, - /* Foxconn T99W696.01, Lenovo Generic SKU */ - { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe142), - .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info }, - /* Foxconn T99W696.02, Lenovo X1 Carbon SKU */ - { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe143), - .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info }, - /* Foxconn T99W696.03, Lenovo X1 2in1 SKU */ - { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe144), - .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info }, - /* Foxconn T99W696.04, Lenovo PRC SKU */ - { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe145), - .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info }, - /* Foxconn T99W696.00, Foxconn SKU */ - { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe146), + /* Foxconn T99W696, all variants */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, PCI_ANY_ID), .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info }, { PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x0308), .driver_data = (kernel_ulong_t) &mhi_qcom_sdx65_info }, @@ -1037,6 +1033,7 @@ struct mhi_pci_device { struct work_struct recovery_work; struct timer_list health_check_timer; unsigned long status; + bool reset_on_remove; }; static int mhi_pci_read_reg(struct mhi_controller *mhi_cntrl, @@ -1092,7 +1089,7 @@ static bool mhi_pci_is_alive(struct mhi_controller *mhi_cntrl) struct pci_dev *pdev = to_pci_dev(mhi_cntrl->cntrl_dev); u16 vendor = 0; - if (pci_read_config_word(pdev, PCI_VENDOR_ID, &vendor)) + if (pci_read_config_word(pci_physfn(pdev), PCI_VENDOR_ID, &vendor)) return false; if (vendor == (u16) ~0 || vendor == 0) @@ -1203,7 +1200,9 @@ static void mhi_pci_recovery_work(struct work_struct *work) dev_warn(&pdev->dev, "device recovery started\n"); - timer_delete(&mhi_pdev->health_check_timer); + if (pdev->is_physfn) + timer_delete(&mhi_pdev->health_check_timer); + pm_runtime_forbid(&pdev->dev); /* Clean up MHI state */ @@ -1230,7 +1229,10 @@ static void mhi_pci_recovery_work(struct work_struct *work) dev_dbg(&pdev->dev, "Recovery completed\n"); set_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status); - mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); + + if (pdev->is_physfn) + mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); + return; err_unprepare: @@ -1301,6 +1303,7 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) const struct mhi_controller_config *mhi_cntrl_config; struct mhi_pci_device *mhi_pdev; struct mhi_controller *mhi_cntrl; + unsigned int dma_data_width; int err; dev_info(&pdev->dev, "MHI PCI device found: %s\n", info->name); @@ -1311,14 +1314,24 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) return -ENOMEM; INIT_WORK(&mhi_pdev->recovery_work, mhi_pci_recovery_work); - timer_setup(&mhi_pdev->health_check_timer, health_check, 0); - mhi_cntrl_config = info->config; + if (pdev->is_virtfn && info->vf_config) + mhi_cntrl_config = info->vf_config; + else + mhi_cntrl_config = info->config; + + /* Initialize health check monitor only for Physical functions */ + if (pdev->is_physfn) + timer_setup(&mhi_pdev->health_check_timer, health_check, 0); + mhi_cntrl = &mhi_pdev->mhi_cntrl; + dma_data_width = (pdev->is_virtfn && info->vf_dma_data_width) ? + info->vf_dma_data_width : info->dma_data_width; + mhi_cntrl->cntrl_dev = &pdev->dev; mhi_cntrl->iova_start = 0; - mhi_cntrl->iova_stop = (dma_addr_t)DMA_BIT_MASK(info->dma_data_width); + mhi_cntrl->iova_stop = (dma_addr_t)DMA_BIT_MASK(dma_data_width); mhi_cntrl->fw_image = info->fw; mhi_cntrl->edl_image = info->edl; @@ -1330,6 +1343,9 @@ 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; + if (pdev->is_physfn) + mhi_pdev->reset_on_remove = info->reset_on_remove; + if (info->edl_trigger) mhi_cntrl->edl_trigger = mhi_pci_generic_edl_trigger; @@ -1339,7 +1355,7 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) mhi_cntrl->wake_toggle = mhi_pci_wake_toggle_nop; } - err = mhi_pci_claim(mhi_cntrl, info->bar_num, DMA_BIT_MASK(info->dma_data_width)); + err = mhi_pci_claim(mhi_cntrl, info->bar_num, DMA_BIT_MASK(dma_data_width)); if (err) return err; @@ -1376,7 +1392,8 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) set_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status); /* start health check */ - mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); + if (pdev->is_physfn) + mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); /* Allow runtime suspend only if both PME from D3Hot and M3 are supported */ if (pci_pme_capable(pdev, PCI_D3hot) && !(info->no_m3)) { @@ -1401,7 +1418,10 @@ static void mhi_pci_remove(struct pci_dev *pdev) struct mhi_pci_device *mhi_pdev = pci_get_drvdata(pdev); struct mhi_controller *mhi_cntrl = &mhi_pdev->mhi_cntrl; - timer_delete_sync(&mhi_pdev->health_check_timer); + pci_disable_sriov(pdev); + + if (pdev->is_physfn) + timer_delete_sync(&mhi_pdev->health_check_timer); cancel_work_sync(&mhi_pdev->recovery_work); if (test_and_clear_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status)) { @@ -1413,6 +1433,9 @@ static void mhi_pci_remove(struct pci_dev *pdev) if (pci_pme_capable(pdev, PCI_D3hot)) pm_runtime_get_noresume(&pdev->dev); + if (mhi_pdev->reset_on_remove) + mhi_soc_reset(mhi_cntrl); + mhi_unregister_controller(mhi_cntrl); } @@ -1429,7 +1452,8 @@ static void mhi_pci_reset_prepare(struct pci_dev *pdev) dev_info(&pdev->dev, "reset\n"); - timer_delete(&mhi_pdev->health_check_timer); + if (pdev->is_physfn) + timer_delete(&mhi_pdev->health_check_timer); /* Clean up MHI state */ if (test_and_clear_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status)) { @@ -1474,7 +1498,8 @@ static void mhi_pci_reset_done(struct pci_dev *pdev) } set_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status); - mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); + if (pdev->is_physfn) + mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); } static pci_ers_result_t mhi_pci_error_detected(struct pci_dev *pdev, @@ -1539,7 +1564,9 @@ static int __maybe_unused mhi_pci_runtime_suspend(struct device *dev) if (test_and_set_bit(MHI_PCI_DEV_SUSPENDED, &mhi_pdev->status)) return 0; - timer_delete(&mhi_pdev->health_check_timer); + if (pdev->is_physfn) + timer_delete(&mhi_pdev->health_check_timer); + cancel_work_sync(&mhi_pdev->recovery_work); if (!test_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status) || @@ -1590,7 +1617,8 @@ static int __maybe_unused mhi_pci_runtime_resume(struct device *dev) } /* Resume health check */ - mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); + if (pdev->is_physfn) + mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); /* It can be a remote wakeup (no mhi runtime_get), update access time */ pm_runtime_mark_last_busy(dev); @@ -1676,7 +1704,8 @@ static struct pci_driver mhi_pci_driver = { .remove = mhi_pci_remove, .shutdown = mhi_pci_shutdown, .err_handler = &mhi_pci_err_handler, - .driver.pm = &mhi_pci_pm_ops + .driver.pm = &mhi_pci_pm_ops, + .sriov_configure = pci_sriov_configure_simple, }; module_pci_driver(mhi_pci_driver); diff --git a/drivers/bus/mhi/host/pm.c b/drivers/bus/mhi/host/pm.c index 33d92bf2fc3e..b4ef115189b5 100644 --- a/drivers/bus/mhi/host/pm.c +++ b/drivers/bus/mhi/host/pm.c @@ -418,6 +418,7 @@ static int mhi_pm_mission_mode_transition(struct mhi_controller *mhi_cntrl) device_for_each_child(&mhi_cntrl->mhi_dev->dev, ¤t_ee, mhi_destroy_device); mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_MISSION_MODE); + mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee); /* Force MHI to be in M0 state before continuing */ ret = __mhi_device_get_sync(mhi_cntrl); @@ -631,6 +632,8 @@ static void mhi_pm_sys_error_transition(struct mhi_controller *mhi_cntrl) /* Wake up threads waiting for state transition */ wake_up_all(&mhi_cntrl->state_event); + mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee); + if (MHI_REG_ACCESS_VALID(prev_state)) { /* * If the device is in PBL or SBL, it will only respond to @@ -829,6 +832,8 @@ void mhi_pm_st_worker(struct work_struct *work) mhi_create_devices(mhi_cntrl); if (mhi_cntrl->fbc_download) mhi_download_amss_image(mhi_cntrl); + + mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee); break; case DEV_ST_TRANSITION_MISSION_MODE: mhi_pm_mission_mode_transition(mhi_cntrl); @@ -838,6 +843,7 @@ void mhi_pm_st_worker(struct work_struct *work) mhi_cntrl->ee = MHI_EE_FP; write_unlock_irq(&mhi_cntrl->pm_lock); mhi_create_devices(mhi_cntrl); + mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee); break; case DEV_ST_TRANSITION_READY: mhi_ready_state_transition(mhi_cntrl); @@ -1240,6 +1246,8 @@ static void __mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful, write_unlock_irq(&mhi_cntrl->pm_lock); mutex_unlock(&mhi_cntrl->pm_mutex); + mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee); + if (destroy_device) mhi_queue_state_transition(mhi_cntrl, DEV_ST_TRANSITION_DISABLE_DESTROY_DEVICE); @@ -1279,7 +1287,7 @@ int mhi_sync_power_up(struct mhi_controller *mhi_cntrl) mhi_cntrl->ready_timeout_ms : mhi_cntrl->timeout_ms; wait_event_timeout(mhi_cntrl->state_event, MHI_IN_MISSION_MODE(mhi_cntrl->ee) || - MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state), + MHI_PM_FATAL_ERROR(mhi_cntrl->pm_state), msecs_to_jiffies(timeout_ms)); ret = (MHI_IN_MISSION_MODE(mhi_cntrl->ee)) ? 0 : -ETIMEDOUT; @@ -1338,3 +1346,22 @@ void mhi_device_put(struct mhi_device *mhi_dev) read_unlock_bh(&mhi_cntrl->pm_lock); } EXPORT_SYMBOL_GPL(mhi_device_put); + +void mhi_uevent_notify(struct mhi_controller *mhi_cntrl, enum mhi_ee_type ee) +{ + struct device *dev = &mhi_cntrl->mhi_dev->dev; + char *buf[2]; + int ret; + + buf[0] = kasprintf(GFP_KERNEL, "EXEC_ENV=%s", TO_MHI_EXEC_STR(ee)); + buf[1] = NULL; + + if (!buf[0]) + return; + + ret = kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, buf); + if (ret) + dev_err(dev, "Failed to send %s uevent\n", TO_MHI_EXEC_STR(ee)); + + kfree(buf[0]); +} diff --git a/drivers/cdx/cdx_msi.c b/drivers/cdx/cdx_msi.c index 3388a5d1462c..91b95422b263 100644 --- a/drivers/cdx/cdx_msi.c +++ b/drivers/cdx/cdx_msi.c @@ -174,6 +174,7 @@ struct irq_domain *cdx_msi_domain_init(struct device *dev) } parent = irq_find_matching_fwnode(of_fwnode_handle(parent_node), DOMAIN_BUS_NEXUS); + of_node_put(parent_node); if (!parent || !msi_get_domain_info(parent)) { dev_err(dev, "unable to locate ITS domain\n"); return NULL; diff --git a/drivers/char/Makefile b/drivers/char/Makefile index e9b360cdc99a..1291369b9126 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -6,6 +6,7 @@ obj-y += mem.o random.o obj-$(CONFIG_TTY_PRINTK) += ttyprintk.o obj-y += misc.o +obj-$(CONFIG_TEST_MISC_MINOR) += misc_minor_kunit.o obj-$(CONFIG_ATARI_DSP56K) += dsp56k.o obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o obj-$(CONFIG_UV_MMTIMER) += uv_mmtimer.o diff --git a/drivers/char/adi.c b/drivers/char/adi.c index f9bec10a6064..4312b0cc391c 100644 --- a/drivers/char/adi.c +++ b/drivers/char/adi.c @@ -131,7 +131,7 @@ static ssize_t adi_write(struct file *file, const char __user *buf, ssize_t ret; int i; - if (count <= 0) + if (count == 0) return -EINVAL; ver_buf_sz = min_t(size_t, count, MAX_BUF_SZ); diff --git a/drivers/char/hpet.c b/drivers/char/hpet.c index 0713ea2b2a51..4f5ccd3a1f56 100644 --- a/drivers/char/hpet.c +++ b/drivers/char/hpet.c @@ -867,7 +867,7 @@ int hpet_alloc(struct hpet_data *hdp) printk(KERN_INFO "hpet%u: at MMIO 0x%lx, IRQ%s", hpetp->hp_which, hdp->hd_phys_address, - hpetp->hp_ntimer > 1 ? "s" : ""); + str_plural(hpetp->hp_ntimer)); for (i = 0; i < hpetp->hp_ntimer; i++) printk(KERN_CONT "%s %u", i > 0 ? "," : "", hdp->hd_irq[i]); printk(KERN_CONT "\n"); diff --git a/drivers/char/misc.c b/drivers/char/misc.c index 558302a64dd9..726516fb0a3b 100644 --- a/drivers/char/misc.c +++ b/drivers/char/misc.c @@ -132,7 +132,8 @@ static int misc_open(struct inode *inode, struct file *file) break; } - if (!new_fops) { + /* Only request module for fixed minor code */ + if (!new_fops && minor < MISC_DYNAMIC_MINOR) { mutex_unlock(&misc_mtx); request_module("char-major-%d-%d", MISC_MAJOR, minor); mutex_lock(&misc_mtx); @@ -144,10 +145,11 @@ static int misc_open(struct inode *inode, struct file *file) new_fops = fops_get(iter->fops); break; } - if (!new_fops) - goto fail; } + if (!new_fops) + goto fail; + /* * Place the miscdevice in the file's * private_data so it can be used by the @@ -210,6 +212,12 @@ int misc_register(struct miscdevice *misc) int err = 0; bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR); + if (misc->minor > MISC_DYNAMIC_MINOR) { + pr_err("Invalid fixed minor %d for miscdevice '%s'\n", + misc->minor, misc->name); + return -EINVAL; + } + INIT_LIST_HEAD(&misc->list); mutex_lock(&misc_mtx); @@ -275,13 +283,12 @@ EXPORT_SYMBOL(misc_register); void misc_deregister(struct miscdevice *misc) { - if (WARN_ON(list_empty(&misc->list))) - return; - mutex_lock(&misc_mtx); - list_del(&misc->list); + list_del_init(&misc->list); device_destroy(&misc_class, MKDEV(MISC_MAJOR, misc->minor)); misc_minor_free(misc->minor); + if (misc->minor > MISC_DYNAMIC_MINOR) + misc->minor = MISC_DYNAMIC_MINOR; mutex_unlock(&misc_mtx); } EXPORT_SYMBOL(misc_deregister); diff --git a/drivers/misc/misc_minor_kunit.c b/drivers/char/misc_minor_kunit.c index 30eceac5f1b6..6fc8b05169c5 100644 --- a/drivers/misc/misc_minor_kunit.c +++ b/drivers/char/misc_minor_kunit.c @@ -7,12 +7,6 @@ #include <linux/file.h> #include <linux/init_syscalls.h> -/* dynamic minor (2) */ -static struct miscdevice dev_dynamic_minor = { - .minor = 2, - .name = "dev_dynamic_minor", -}; - /* static minor (LCD_MINOR) */ static struct miscdevice dev_static_minor = { .minor = LCD_MINOR, @@ -25,16 +19,6 @@ static struct miscdevice dev_misc_dynamic_minor = { .name = "dev_misc_dynamic_minor", }; -static void kunit_dynamic_minor(struct kunit *test) -{ - int ret; - - ret = misc_register(&dev_dynamic_minor); - KUNIT_EXPECT_EQ(test, 0, ret); - KUNIT_EXPECT_EQ(test, 2, dev_dynamic_minor.minor); - misc_deregister(&dev_dynamic_minor); -} - static void kunit_static_minor(struct kunit *test) { int ret; @@ -157,13 +141,7 @@ static bool is_valid_dynamic_minor(int minor) { if (minor < 0) return false; - if (minor == MISC_DYNAMIC_MINOR) - return false; - if (minor >= 0 && minor <= 15) - return false; - if (minor >= 128 && minor < MISC_DYNAMIC_MINOR) - return false; - return true; + return minor > MISC_DYNAMIC_MINOR; } static int miscdev_test_open(struct inode *inode, struct file *file) @@ -557,7 +535,7 @@ static void __init miscdev_test_conflict(struct kunit *test) */ miscstat.minor = miscdyn.minor; ret = misc_register(&miscstat); - KUNIT_EXPECT_EQ(test, ret, -EBUSY); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); if (ret == 0) misc_deregister(&miscstat); @@ -590,8 +568,9 @@ static void __init miscdev_test_conflict_reverse(struct kunit *test) misc_deregister(&miscdyn); ret = misc_register(&miscstat); - KUNIT_EXPECT_EQ(test, ret, 0); - KUNIT_EXPECT_EQ(test, miscstat.minor, miscdyn.minor); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + if (ret == 0) + misc_deregister(&miscstat); /* * Try to register a dynamic minor after registering a static minor @@ -601,25 +580,81 @@ static void __init miscdev_test_conflict_reverse(struct kunit *test) miscdyn.minor = MISC_DYNAMIC_MINOR; ret = misc_register(&miscdyn); KUNIT_EXPECT_EQ(test, ret, 0); - KUNIT_EXPECT_NE(test, miscdyn.minor, miscstat.minor); + KUNIT_EXPECT_EQ(test, miscdyn.minor, miscstat.minor); KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn.minor)); if (ret == 0) misc_deregister(&miscdyn); +} - miscdev_test_can_open(test, &miscstat); +/* Take minor(> MISC_DYNAMIC_MINOR) as invalid when register miscdevice */ +static void miscdev_test_invalid_input(struct kunit *test) +{ + struct miscdevice misc_test = { + .minor = MISC_DYNAMIC_MINOR + 1, + .name = "misc_test", + .fops = &miscdev_test_fops, + }; + int ret; - misc_deregister(&miscstat); + ret = misc_register(&misc_test); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + if (ret == 0) + misc_deregister(&misc_test); +} + +/* + * Verify if @miscdyn_a can still be registered successfully without + * reinitialization even if its minor ever owned was requested by + * another miscdevice such as @miscdyn_b. + */ +static void miscdev_test_dynamic_reentry(struct kunit *test) +{ + struct miscdevice miscdyn_a = { + .name = "miscdyn_a", + .minor = MISC_DYNAMIC_MINOR, + .fops = &miscdev_test_fops, + }; + struct miscdevice miscdyn_b = { + .name = "miscdyn_b", + .minor = MISC_DYNAMIC_MINOR, + .fops = &miscdev_test_fops, + }; + int ret, minor_a; + + ret = misc_register(&miscdyn_a); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn_a.minor)); + minor_a = miscdyn_a.minor; + if (ret != 0) + return; + misc_deregister(&miscdyn_a); + + ret = misc_register(&miscdyn_b); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, miscdyn_b.minor, minor_a); + if (ret != 0) + return; + + ret = misc_register(&miscdyn_a); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn_a.minor)); + KUNIT_EXPECT_NE(test, miscdyn_a.minor, miscdyn_b.minor); + if (ret == 0) + misc_deregister(&miscdyn_a); + + misc_deregister(&miscdyn_b); } static struct kunit_case test_cases[] = { - KUNIT_CASE(kunit_dynamic_minor), KUNIT_CASE(kunit_static_minor), KUNIT_CASE(kunit_misc_dynamic_minor), + KUNIT_CASE(miscdev_test_invalid_input), KUNIT_CASE_PARAM(miscdev_test_twice, miscdev_gen_params), KUNIT_CASE_PARAM(miscdev_test_duplicate_minor, miscdev_gen_params), KUNIT_CASE(miscdev_test_duplicate_name), KUNIT_CASE(miscdev_test_duplicate_name_leak), KUNIT_CASE_PARAM(miscdev_test_duplicate_error, miscdev_gen_params), + KUNIT_CASE(miscdev_test_dynamic_reentry), {} }; diff --git a/drivers/comedi/Kconfig b/drivers/comedi/Kconfig index 93c68a40a17b..6dcc2567de6d 100644 --- a/drivers/comedi/Kconfig +++ b/drivers/comedi/Kconfig @@ -705,6 +705,15 @@ config COMEDI_ADL_PCI6208 To compile this driver as a module, choose M here: the module will be called adl_pci6208. +config COMEDI_ADL_PCI7250 + tristate "ADLink PCI-7250 support" + help + Enable support for ADLink PCI-7250/LPCI-7250/LPCIe-7250 relay output + and isolated digital input boards. + + To compile this driver as a module, choose M here: the module will be + called adl_pci7250. + config COMEDI_ADL_PCI7X3X tristate "ADLink PCI-723X/743X isolated digital i/o board support" depends on HAS_IOPORT diff --git a/drivers/comedi/drivers/Makefile b/drivers/comedi/drivers/Makefile index b24ac00cab73..7b99a431330d 100644 --- a/drivers/comedi/drivers/Makefile +++ b/drivers/comedi/drivers/Makefile @@ -73,6 +73,7 @@ obj-$(CONFIG_COMEDI_ADDI_APCI_3120) += addi_apci_3120.o obj-$(CONFIG_COMEDI_ADDI_APCI_3501) += addi_apci_3501.o obj-$(CONFIG_COMEDI_ADDI_APCI_3XXX) += addi_apci_3xxx.o obj-$(CONFIG_COMEDI_ADL_PCI6208) += adl_pci6208.o +obj-$(CONFIG_COMEDI_ADL_PCI7250) += adl_pci7250.o obj-$(CONFIG_COMEDI_ADL_PCI7X3X) += adl_pci7x3x.o obj-$(CONFIG_COMEDI_ADL_PCI8164) += adl_pci8164.o obj-$(CONFIG_COMEDI_ADL_PCI9111) += adl_pci9111.o diff --git a/drivers/comedi/drivers/adl_pci7250.c b/drivers/comedi/drivers/adl_pci7250.c new file mode 100644 index 000000000000..78c85a402435 --- /dev/null +++ b/drivers/comedi/drivers/adl_pci7250.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * adl_pci7250.c + * + * Comedi driver for ADLink PCI-7250 series cards. + * + * Copyright (C) 2015, 2025 Ian Abbott <abbotti@mev.co.uk> + */ + +/* + * Driver: adl_pci7250 + * Description: Driver for the ADLINK PCI-7250 relay output & digital input card + * Devices: [ADLINK] PCI-7250 (adl_pci7250) LPCI-7250 LPCIe-7250 + * Author: Ian Abbott <abbotti@mev.co.uk> + * Status: works + * Updated: Mon, 02 Jun 2025 13:54:11 +0100 + * + * The driver assumes that 3 PCI-7251 modules are fitted to the PCI-7250, + * giving 32 channels of relay outputs and 32 channels of isolated digital + * inputs. That is also the case for the LPCI-7250 and older LPCIe-7250 + * cards although they do not physically support the PCI-7251 modules. + * Newer LPCIe-7250 cards have a different PCI subsystem device ID, so + * set the number of channels to 8 for these cards. + * + * Not fitting the PCI-7251 modules shouldn't do any harm, but the extra + * inputs and relay outputs won't work! + * + * Configuration Options: not applicable, uses PCI auto config + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pci.h> + +static unsigned char adl_pci7250_read8(struct comedi_device *dev, + unsigned int offset) +{ +#ifdef CONFIG_HAS_IOPORT + if (!dev->mmio) + return inb(dev->iobase + offset); +#endif + return readb(dev->mmio + offset); +} + +static void adl_pci7250_write8(struct comedi_device *dev, unsigned int offset, + unsigned char val) +{ +#ifdef CONFIG_HAS_IOPORT + if (!dev->mmio) { + outb(val, dev->iobase + offset); + return; + } +#endif + writeb(val, dev->mmio + offset); +} + +static int adl_pci7250_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int mask = comedi_dio_update_state(s, data); + + if (mask) { + unsigned int state = s->state; + unsigned int i; + + for (i = 0; i * 8 < s->n_chan; i++) { + if ((mask & 0xffu) != 0) { + /* write relay data to even offset registers */ + adl_pci7250_write8(dev, i * 2, state & 0xffu); + } + state >>= 8; + mask >>= 8; + } + } + + data[1] = s->state; + + return 2; +} + +static int adl_pci7250_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int value = 0; + unsigned int i; + + for (i = 0; i * 8 < s->n_chan; i++) { + /* read DI value from odd offset registers */ + value |= (unsigned int)adl_pci7250_read8(dev, i * 2 + 1) << + (i * 8); + } + + data[1] = value; + + return 2; +} + +static int pci7250_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + unsigned int max_chans; + unsigned int i; + int ret; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + if (pci_resource_len(pcidev, 2) < 8) + return -ENXIO; + + /* + * Newer LPCIe-7250 boards use MMIO. Older LPCIe-7250, LPCI-7250, and + * PCI-7250 boards use Port I/O. + */ + if (pci_resource_flags(pcidev, 2) & IORESOURCE_MEM) { + dev->mmio = pci_ioremap_bar(pcidev, 2); + if (!dev->mmio) + return -ENOMEM; + } else if (IS_ENABLED(CONFIG_HAS_IOPORT)) { + dev->iobase = pci_resource_start(pcidev, 2); + } else { + dev_err(dev->class_dev, + "error! need I/O port support\n"); + return -ENXIO; + } + + if (pcidev->subsystem_device == 0x7000) { + /* + * This is a newer LPCIe-7250 variant and cannot possibly + * have PCI-7251 modules fitted, so limit the number of + * channels to 8. + */ + max_chans = 8; + } else { + /* + * It is unknown whether the board is a PCI-7250, an LPCI-7250, + * or an older LPCIe-7250 variant, so treat it as a PCI-7250 + * and assume it can have PCI-7251 modules fitted to increase + * the number of channels to a maximum of 32. + */ + max_chans = 32; + } + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + /* Relay digital output. */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = max_chans; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = adl_pci7250_do_insn_bits; + /* Read initial state of relays from the even offset registers. */ + s->state = 0; + for (i = 0; i * 8 < max_chans; i++) { + s->state |= (unsigned int)adl_pci7250_read8(dev, i * 2) << + (i * 8); + } + + /* Isolated digital input. */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = max_chans; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = adl_pci7250_di_insn_bits; + + return 0; +} + +static struct comedi_driver adl_pci7250_driver = { + .driver_name = "adl_pci7250", + .module = THIS_MODULE, + .auto_attach = pci7250_auto_attach, + .detach = comedi_pci_detach, +}; + +static int adl_pci7250_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &adl_pci7250_driver, + id->driver_data); +} + +static const struct pci_device_id adl_pci7250_pci_table[] = { +#ifdef CONFIG_HAS_IOPORT + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, + 0x9999, 0x7250) }, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_ADLINK, 0x7250, + 0x9999, 0x7250) }, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_ADLINK, 0x7250, + PCI_VENDOR_ID_ADLINK, 0x7250) }, +#endif + { PCI_DEVICE_SUB(PCI_VENDOR_ID_ADLINK, 0x7250, + PCI_VENDOR_ID_ADLINK, 0x7000) }, /* newer LPCIe-7250 */ + { 0 } +}; +MODULE_DEVICE_TABLE(pci, adl_pci7250_pci_table); + +static struct pci_driver adl_pci7250_pci_driver = { + .name = "adl_pci7250", + .id_table = adl_pci7250_pci_table, + .probe = adl_pci7250_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(adl_pci7250_driver, adl_pci7250_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for ADLink PCI-7250 series boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/counter/ti-ecap-capture.c b/drivers/counter/ti-ecap-capture.c index 3faaf7f60539..3586a7ab9887 100644 --- a/drivers/counter/ti-ecap-capture.c +++ b/drivers/counter/ti-ecap-capture.c @@ -465,11 +465,6 @@ static irqreturn_t ecap_cnt_isr(int irq, void *dev_id) return IRQ_HANDLED; } -static void ecap_cnt_pm_disable(void *dev) -{ - pm_runtime_disable(dev); -} - static int ecap_cnt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -523,12 +518,9 @@ static int ecap_cnt_probe(struct platform_device *pdev) platform_set_drvdata(pdev, counter_dev); - pm_runtime_enable(dev); - - /* Register a cleanup callback to care for disabling PM */ - ret = devm_add_action_or_reset(dev, ecap_cnt_pm_disable, dev); + ret = devm_pm_runtime_enable(dev); if (ret) - return dev_err_probe(dev, ret, "failed to add pm disable action\n"); + return ret; ret = devm_counter_add(dev, counter_dev); if (ret) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index a6f6d467aacf..aec46bf03302 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -134,6 +134,19 @@ config EXTCON_MAX8997 Maxim MAX8997 PMIC. The MAX8997 MUIC is a USB port accessory detector and switch. +config EXTCON_MAX14526 + tristate "Maxim MAX14526 EXTCON Support" + depends on I2C + select IRQ_DOMAIN + select REGMAP_I2C + help + If you say yes here you get support for the Maxim MAX14526 + MUIC device. The MAX14526 MUIC is a USB port accessory + detector and switch. The MAX14526 is designed to simplify + interface requirements on portable devices by multiplexing + common inputs (USB, UART, Microphone, Stereo Audio and + Composite Video) on a single micro/mini USB connector. + config EXTCON_PALMAS tristate "Palmas USB EXTCON support" depends on MFD_PALMAS diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 0d6d23faf748..6482f2bfd661 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o obj-$(CONFIG_EXTCON_MAX77843) += extcon-max77843.o obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o +obj-$(CONFIG_EXTCON_MAX14526) += extcon-max14526.o obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o obj-$(CONFIG_EXTCON_PTN5150) += extcon-ptn5150.o obj-$(CONFIG_EXTCON_QCOM_SPMI_MISC) += extcon-qcom-spmi-misc.o diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c index 46c40d85c2ac..7e3c9f38297b 100644 --- a/drivers/extcon/extcon-adc-jack.c +++ b/drivers/extcon/extcon-adc-jack.c @@ -164,6 +164,8 @@ static void adc_jack_remove(struct platform_device *pdev) { struct adc_jack_data *data = platform_get_drvdata(pdev); + if (data->wakeup_source) + device_init_wakeup(&pdev->dev, false); free_irq(data->irq, data); cancel_work_sync(&data->handler.work); } diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c index d3bcbe839c09..19856dddade6 100644 --- a/drivers/extcon/extcon-axp288.c +++ b/drivers/extcon/extcon-axp288.c @@ -470,7 +470,7 @@ static int axp288_extcon_probe(struct platform_device *pdev) if (ret < 0) return ret; - device_init_wakeup(dev, true); + devm_device_init_wakeup(dev); platform_set_drvdata(pdev, info); return 0; diff --git a/drivers/extcon/extcon-fsa9480.c b/drivers/extcon/extcon-fsa9480.c index b11b43171063..a031eb0914a0 100644 --- a/drivers/extcon/extcon-fsa9480.c +++ b/drivers/extcon/extcon-fsa9480.c @@ -317,7 +317,7 @@ static int fsa9480_probe(struct i2c_client *client) return ret; } - device_init_wakeup(info->dev, true); + devm_device_init_wakeup(info->dev); fsa9480_detect_dev(info); return 0; diff --git a/drivers/extcon/extcon-max14526.c b/drivers/extcon/extcon-max14526.c new file mode 100644 index 000000000000..3750a5c20612 --- /dev/null +++ b/drivers/extcon/extcon-max14526.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/device.h> +#include <linux/devm-helpers.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/extcon-provider.h> +#include <linux/i2c.h> +#include <linux/mod_devicetable.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/regmap.h> + +/* I2C addresses of MUIC internal registers */ +#define MAX14526_DEVICE_ID 0x00 +#define MAX14526_ID 0x02 + +/* CONTROL_1 register masks */ +#define MAX14526_CONTROL_1 0x01 +#define ID_2P2 BIT(6) +#define ID_620 BIT(5) +#define ID_200 BIT(4) +#define VLDO BIT(3) +#define SEMREN BIT(2) +#define ADC_EN BIT(1) +#define CP_EN BIT(0) + +/* CONTROL_2 register masks */ +#define MAX14526_CONTROL_2 0x02 +#define INTPOL BIT(7) +#define INT_EN BIT(6) +#define MIC_LP BIT(5) +#define CP_AUD BIT(4) +#define CHG_TYPE BIT(1) +#define USB_DET_DIS BIT(0) + +/* SW_CONTROL register masks */ +#define MAX14526_SW_CONTROL 0x03 +#define SW_DATA 0x00 +#define SW_UART 0x01 +#define SW_AUDIO 0x02 +#define SW_OPEN 0x07 + +/* INT_STATUS register masks */ +#define MAX14526_INT_STAT 0x04 +#define CHGDET BIT(7) +#define MR_COMP BIT(6) +#define SENDEND BIT(5) +#define V_VBUS BIT(4) + +/* STATUS register masks */ +#define MAX14526_STATUS 0x05 +#define CPORT BIT(7) +#define CHPORT BIT(6) +#define C1COMP BIT(0) + +enum max14526_idno_resistance { + MAX14526_GND, + MAX14526_24KOHM, + MAX14526_56KOHM, + MAX14526_100KOHM, + MAX14526_130KOHM, + MAX14526_180KOHM, + MAX14526_240KOHM, + MAX14526_330KOHM, + MAX14526_430KOHM, + MAX14526_620KOHM, + MAX14526_910KOHM, + MAX14526_OPEN +}; + +enum max14526_field_idx { + VENDOR_ID, CHIP_REV, /* DEVID */ + DM, DP, /* SW_CONTROL */ + MAX14526_N_REGMAP_FIELDS +}; + +static const struct reg_field max14526_reg_field[MAX14526_N_REGMAP_FIELDS] = { + [VENDOR_ID] = REG_FIELD(MAX14526_DEVICE_ID, 4, 7), + [CHIP_REV] = REG_FIELD(MAX14526_DEVICE_ID, 0, 3), + [DM] = REG_FIELD(MAX14526_SW_CONTROL, 0, 2), + [DP] = REG_FIELD(MAX14526_SW_CONTROL, 3, 5), +}; + +struct max14526_data { + struct i2c_client *client; + struct extcon_dev *edev; + + struct regmap *regmap; + struct regmap_field *rfield[MAX14526_N_REGMAP_FIELDS]; + + int last_state; + int cable; +}; + +enum max14526_muic_modes { + MAX14526_OTG = MAX14526_GND, /* no power */ + MAX14526_MHL = MAX14526_56KOHM, /* no power */ + MAX14526_OTG_Y = MAX14526_GND | V_VBUS, + MAX14526_MHL_CHG = MAX14526_GND | V_VBUS | CHGDET, + MAX14526_NONE = MAX14526_OPEN, + MAX14526_USB = MAX14526_OPEN | V_VBUS, + MAX14526_CHG = MAX14526_OPEN | V_VBUS | CHGDET, +}; + +static const unsigned int max14526_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_CHG_USB_FAST, + EXTCON_DISP_MHL, + EXTCON_NONE, +}; + +static int max14526_ap_usb_mode(struct max14526_data *priv) +{ + struct device *dev = &priv->client->dev; + int ret; + + /* Enable USB Path */ + ret = regmap_field_write(priv->rfield[DM], SW_DATA); + if (ret) + return ret; + + ret = regmap_field_write(priv->rfield[DP], SW_DATA); + if (ret) + return ret; + + /* Enable 200K, Charger Pump and ADC */ + ret = regmap_write(priv->regmap, MAX14526_CONTROL_1, + ID_200 | ADC_EN | CP_EN); + if (ret) + return ret; + + dev_dbg(dev, "AP USB mode set\n"); + + return 0; +} + +static irqreturn_t max14526_interrupt(int irq, void *dev_id) +{ + struct max14526_data *priv = dev_id; + struct device *dev = &priv->client->dev; + int state, ret; + + /* + * Upon an MUIC IRQ (MUIC_INT_N falls), wait at least 70ms + * before reading INT_STAT and STATUS. After the reads, + * MUIC_INT_N returns to high (but the INT_STAT and STATUS + * contents will be held). + */ + msleep(100); + + ret = regmap_read(priv->regmap, MAX14526_INT_STAT, &state); + if (ret) + dev_err(dev, "failed to read MUIC state %d\n", ret); + + if (state == priv->last_state) + return IRQ_HANDLED; + + /* Detach previous device */ + extcon_set_state_sync(priv->edev, priv->cable, false); + + switch (state) { + case MAX14526_USB: + priv->cable = EXTCON_USB; + break; + + case MAX14526_CHG: + priv->cable = EXTCON_CHG_USB_FAST; + break; + + case MAX14526_OTG: + case MAX14526_OTG_Y: + priv->cable = EXTCON_USB_HOST; + break; + + case MAX14526_MHL: + case MAX14526_MHL_CHG: + priv->cable = EXTCON_DISP_MHL; + break; + + case MAX14526_NONE: + default: + priv->cable = EXTCON_NONE; + break; + } + + extcon_set_state_sync(priv->edev, priv->cable, true); + + priv->last_state = state; + + return IRQ_HANDLED; +} + +static const struct regmap_config max14526_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX14526_STATUS, +}; + +static int max14526_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct max14526_data *priv; + int ret, dev_id, rev, i; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + i2c_set_clientdata(client, priv); + + priv->regmap = devm_regmap_init_i2c(client, &max14526_regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), "cannot allocate regmap\n"); + + for (i = 0; i < MAX14526_N_REGMAP_FIELDS; i++) { + priv->rfield[i] = devm_regmap_field_alloc(dev, priv->regmap, + max14526_reg_field[i]); + if (IS_ERR(priv->rfield[i])) + return dev_err_probe(dev, PTR_ERR(priv->rfield[i]), + "cannot allocate regmap field\n"); + } + + /* Detect if MUIC version is supported */ + ret = regmap_field_read(priv->rfield[VENDOR_ID], &dev_id); + if (ret) + return dev_err_probe(dev, ret, "failed to read MUIC ID\n"); + + regmap_field_read(priv->rfield[CHIP_REV], &rev); + + if (dev_id == MAX14526_ID) + dev_info(dev, "detected MAX14526 MUIC with id 0x%x, rev 0x%x\n", dev_id, rev); + else + dev_err_probe(dev, -EINVAL, "MUIC vendor id 0x%X is not recognized\n", dev_id); + + priv->edev = devm_extcon_dev_allocate(dev, max14526_extcon_cable); + if (IS_ERR(priv->edev)) + return dev_err_probe(dev, (IS_ERR(priv->edev)), + "failed to allocate extcon device\n"); + + ret = devm_extcon_dev_register(dev, priv->edev); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to register extcon device\n"); + + ret = max14526_ap_usb_mode(priv); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to set AP USB mode\n"); + + regmap_write_bits(priv->regmap, MAX14526_CONTROL_2, INT_EN, INT_EN); + regmap_write_bits(priv->regmap, MAX14526_CONTROL_2, USB_DET_DIS, (u32)~USB_DET_DIS); + + ret = devm_request_threaded_irq(dev, client->irq, NULL, &max14526_interrupt, + IRQF_ONESHOT | IRQF_SHARED, client->name, priv); + if (ret) + return dev_err_probe(dev, ret, "failed to register IRQ\n"); + + irq_wake_thread(client->irq, priv); + + return 0; +} + +static int max14526_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max14526_data *priv = i2c_get_clientdata(client); + + irq_wake_thread(client->irq, priv); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(max14526_pm_ops, NULL, max14526_resume); + +static const struct of_device_id max14526_match[] = { + { .compatible = "maxim,max14526" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, max14526_match); + +static const struct i2c_device_id max14526_id[] = { + { "max14526" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max14526_id); + +static struct i2c_driver max14526_driver = { + .driver = { + .name = "max14526", + .of_match_table = max14526_match, + .pm = &max14526_pm_ops, + }, + .probe = max14526_probe, + .id_table = max14526_id, +}; +module_i2c_driver(max14526_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); +MODULE_DESCRIPTION("MAX14526 extcon driver to support MUIC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/extcon/extcon-qcom-spmi-misc.c b/drivers/extcon/extcon-qcom-spmi-misc.c index 53de581a393a..afaba5685c3d 100644 --- a/drivers/extcon/extcon-qcom-spmi-misc.c +++ b/drivers/extcon/extcon-qcom-spmi-misc.c @@ -155,7 +155,7 @@ static int qcom_usb_extcon_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, info); - device_init_wakeup(dev, 1); + devm_device_init_wakeup(dev); /* Perform initial detection */ qcom_usb_extcon_detect_cable(&info->wq_detcable.work); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index caeb7bee50cf..7ee3afbc2b05 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1951,6 +1951,17 @@ config GPIO_MPSSE GPIO driver for FTDI's MPSSE interface. These can do input and output. Each MPSSE provides 16 IO pins. +config GPIO_USBIO + tristate "Intel USBIO GPIO support" + depends on USB_USBIO + default USB_USBIO + help + Select this option to enable GPIO driver for the INTEL + USBIO driver stack. + + This driver can also be built as a module. If so, the module + will be called gpio_usbio. + endmenu menu "Virtual GPIO drivers" diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 000fa2e397c2..ec296fa14bfd 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -194,6 +194,7 @@ obj-$(CONFIG_GPIO_TS5500) += gpio-ts5500.o obj-$(CONFIG_GPIO_TWL4030) += gpio-twl4030.o obj-$(CONFIG_GPIO_TWL6040) += gpio-twl6040.o obj-$(CONFIG_GPIO_UNIPHIER) += gpio-uniphier.o +obj-$(CONFIG_GPIO_USBIO) += gpio-usbio.o obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o obj-$(CONFIG_GPIO_VIPERBOARD) += gpio-viperboard.o obj-$(CONFIG_GPIO_VIRTUSER) += gpio-virtuser.o diff --git a/drivers/gpio/gpio-usbio.c b/drivers/gpio/gpio-usbio.c new file mode 100644 index 000000000000..e13c120824e3 --- /dev/null +++ b/drivers/gpio/gpio-usbio.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025 Intel Corporation. + * Copyright (c) 2025 Red Hat, Inc. + */ + +#include <linux/acpi.h> +#include <linux/auxiliary_bus.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/gpio/driver.h> +#include <linux/mutex.h> +#include <linux/types.h> +#include <linux/usb/usbio.h> + +struct usbio_gpio_bank { + u8 config[USBIO_GPIOSPERBANK]; + u32 bitmap; +}; + +struct usbio_gpio { + struct mutex config_mutex; /* Protects banks[x].config */ + struct usbio_gpio_bank banks[USBIO_MAX_GPIOBANKS]; + struct gpio_chip gc; + struct auxiliary_device *adev; +}; + +static const struct acpi_device_id usbio_gpio_acpi_hids[] = { + { "INTC1007" }, /* MTL */ + { "INTC10B2" }, /* ARL */ + { "INTC10B5" }, /* LNL */ + { "INTC10E2" }, /* PTL */ + { } +}; + +static void usbio_gpio_get_bank_and_pin(struct gpio_chip *gc, unsigned int offset, + struct usbio_gpio_bank **bank_ret, + unsigned int *pin_ret) +{ + struct usbio_gpio *gpio = gpiochip_get_data(gc); + struct device *dev = &gpio->adev->dev; + struct usbio_gpio_bank *bank; + unsigned int pin; + + bank = &gpio->banks[offset / USBIO_GPIOSPERBANK]; + pin = offset % USBIO_GPIOSPERBANK; + if (~bank->bitmap & BIT(pin)) { + /* The FW bitmap sometimes is invalid, warn and continue */ + dev_warn_once(dev, FW_BUG "GPIO %u is not in FW pins bitmap\n", offset); + } + + *bank_ret = bank; + *pin_ret = pin; +} + +static int usbio_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct usbio_gpio_bank *bank; + unsigned int pin; + u8 cfg; + + usbio_gpio_get_bank_and_pin(gc, offset, &bank, &pin); + + cfg = bank->config[pin] & USBIO_GPIO_PINMOD_MASK; + + return (cfg == USBIO_GPIO_PINMOD_OUTPUT) ? + GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; +} + +static int usbio_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct usbio_gpio *gpio = gpiochip_get_data(gc); + struct usbio_gpio_bank *bank; + struct usbio_gpio_rw gbuf; + unsigned int pin; + int ret; + + usbio_gpio_get_bank_and_pin(gc, offset, &bank, &pin); + + gbuf.bankid = offset / USBIO_GPIOSPERBANK; + gbuf.pincount = 1; + gbuf.pin = pin; + + ret = usbio_control_msg(gpio->adev, USBIO_PKTTYPE_GPIO, USBIO_GPIOCMD_READ, + &gbuf, sizeof(gbuf) - sizeof(gbuf.value), + &gbuf, sizeof(gbuf)); + if (ret != sizeof(gbuf)) + return (ret < 0) ? ret : -EPROTO; + + return (le32_to_cpu(gbuf.value) >> pin) & 1; +} + +static int usbio_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct usbio_gpio *gpio = gpiochip_get_data(gc); + struct usbio_gpio_bank *bank; + struct usbio_gpio_rw gbuf; + unsigned int pin; + + usbio_gpio_get_bank_and_pin(gc, offset, &bank, &pin); + + gbuf.bankid = offset / USBIO_GPIOSPERBANK; + gbuf.pincount = 1; + gbuf.pin = pin; + gbuf.value = cpu_to_le32(value << pin); + + return usbio_control_msg(gpio->adev, USBIO_PKTTYPE_GPIO, USBIO_GPIOCMD_WRITE, + &gbuf, sizeof(gbuf), NULL, 0); +} + +static int usbio_gpio_update_config(struct gpio_chip *gc, unsigned int offset, + u8 mask, u8 value) +{ + struct usbio_gpio *gpio = gpiochip_get_data(gc); + struct usbio_gpio_bank *bank; + struct usbio_gpio_init gbuf; + unsigned int pin; + + usbio_gpio_get_bank_and_pin(gc, offset, &bank, &pin); + + guard(mutex)(&gpio->config_mutex); + + bank->config[pin] &= ~mask; + bank->config[pin] |= value; + + gbuf.bankid = offset / USBIO_GPIOSPERBANK; + gbuf.config = bank->config[pin]; + gbuf.pincount = 1; + gbuf.pin = pin; + + return usbio_control_msg(gpio->adev, USBIO_PKTTYPE_GPIO, USBIO_GPIOCMD_INIT, + &gbuf, sizeof(gbuf), NULL, 0); +} + +static int usbio_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + return usbio_gpio_update_config(gc, offset, USBIO_GPIO_PINMOD_MASK, + USBIO_GPIO_SET_PINMOD(USBIO_GPIO_PINMOD_INPUT)); +} + +static int usbio_gpio_direction_output(struct gpio_chip *gc, + unsigned int offset, int value) +{ + int ret; + + ret = usbio_gpio_update_config(gc, offset, USBIO_GPIO_PINMOD_MASK, + USBIO_GPIO_SET_PINMOD(USBIO_GPIO_PINMOD_OUTPUT)); + if (ret) + return ret; + + return usbio_gpio_set(gc, offset, value); +} + +static int usbio_gpio_set_config(struct gpio_chip *gc, unsigned int offset, + unsigned long config) +{ + u8 value; + + switch (pinconf_to_config_param(config)) { + case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT: + value = USBIO_GPIO_SET_PINCFG(USBIO_GPIO_PINCFG_DEFAULT); + break; + case PIN_CONFIG_BIAS_PULL_UP: + value = USBIO_GPIO_SET_PINCFG(USBIO_GPIO_PINCFG_PULLUP); + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + value = USBIO_GPIO_SET_PINCFG(USBIO_GPIO_PINCFG_PULLDOWN); + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + value = USBIO_GPIO_SET_PINCFG(USBIO_GPIO_PINCFG_PUSHPULL); + break; + default: + return -ENOTSUPP; + } + + return usbio_gpio_update_config(gc, offset, USBIO_GPIO_PINCFG_MASK, value); +} + +static int usbio_gpio_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *adev_id) +{ + struct usbio_gpio_bank_desc *bank_desc; + struct device *dev = &adev->dev; + struct usbio_gpio *gpio; + int bank, ret; + + bank_desc = dev_get_platdata(dev); + if (!bank_desc) + return -EINVAL; + + gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + ret = devm_mutex_init(dev, &gpio->config_mutex); + if (ret) + return ret; + + gpio->adev = adev; + + usbio_acpi_bind(gpio->adev, usbio_gpio_acpi_hids); + + for (bank = 0; bank < USBIO_MAX_GPIOBANKS && bank_desc[bank].bmap; bank++) + gpio->banks[bank].bitmap = le32_to_cpu(bank_desc[bank].bmap); + + gpio->gc.label = ACPI_COMPANION(dev) ? + acpi_dev_name(ACPI_COMPANION(dev)) : dev_name(dev); + gpio->gc.parent = dev; + gpio->gc.owner = THIS_MODULE; + gpio->gc.get_direction = usbio_gpio_get_direction; + gpio->gc.direction_input = usbio_gpio_direction_input; + gpio->gc.direction_output = usbio_gpio_direction_output; + gpio->gc.get = usbio_gpio_get; + gpio->gc.set = usbio_gpio_set; + gpio->gc.set_config = usbio_gpio_set_config; + gpio->gc.base = -1; + gpio->gc.ngpio = bank * USBIO_GPIOSPERBANK; + gpio->gc.can_sleep = true; + + ret = devm_gpiochip_add_data(dev, &gpio->gc, gpio); + if (ret) + return ret; + + if (has_acpi_companion(dev)) + acpi_dev_clear_dependencies(ACPI_COMPANION(dev)); + + return 0; +} + +static const struct auxiliary_device_id usbio_gpio_id_table[] = { + { "usbio.usbio-gpio" }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, usbio_gpio_id_table); + +static struct auxiliary_driver usbio_gpio_driver = { + .name = USBIO_GPIO_CLIENT, + .probe = usbio_gpio_probe, + .id_table = usbio_gpio_id_table +}; +module_auxiliary_driver(usbio_gpio_driver); + +MODULE_DESCRIPTION("Intel USBIO GPIO driver"); +MODULE_AUTHOR("Israel Cepeda <israel.a.cepeda.lopez@intel.com>"); +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USBIO"); diff --git a/drivers/greybus/svc.c b/drivers/greybus/svc.c index 4256467fcd35..35ea7147dca6 100644 --- a/drivers/greybus/svc.c +++ b/drivers/greybus/svc.c @@ -10,6 +10,7 @@ #include <linux/kstrtox.h> #include <linux/workqueue.h> #include <linux/greybus.h> +#include <linux/string_choices.h> #define SVC_INTF_EJECT_TIMEOUT 9000 #define SVC_INTF_ACTIVATE_TIMEOUT 6000 @@ -73,7 +74,7 @@ static ssize_t watchdog_show(struct device *dev, struct device_attribute *attr, struct gb_svc *svc = to_gb_svc(dev); return sprintf(buf, "%s\n", - gb_svc_watchdog_enabled(svc) ? "enabled" : "disabled"); + str_enabled_disabled(gb_svc_watchdog_enabled(svc))); } static ssize_t watchdog_store(struct device *dev, diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index b934523593d9..7ff85c7200e5 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -92,6 +92,17 @@ config HID_GENERIC If unsure, say Y. +config HID_HAPTIC + tristate "Haptic touchpad support" + default n + help + Support for touchpads with force sensors and haptic actuators instead of a + traditional button. + Adds extra parsing and FF device for the hid multitouch driver. + It can be used for Elan 2703 haptic touchpad. + + If unsure, say N. + menu "Special HID drivers" config HID_A4TECH diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 10ae5dedbd84..361a7daedeb8 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -4,6 +4,7 @@ # hid-y := hid-core.o hid-input.o hid-quirks.o hid-$(CONFIG_DEBUG_FS) += hid-debug.o +hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o obj-$(CONFIG_HID_BPF) += bpf/ diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 8db9d4e7c3b0..a444d41e53b6 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -1388,9 +1388,6 @@ static const struct hid_device_id asus_devices[] = { USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2), QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, - USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3), - QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, - { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR), QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, @@ -1420,6 +1417,9 @@ static const struct hid_device_id asus_devices[] = { * part, while letting hid-multitouch.c handle the touchpad. */ { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, + USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO), + QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, + { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) }, { } }; diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 5419a6c10907..a5b3a8ca2fcb 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -944,6 +944,15 @@ static int hid_scan_report(struct hid_device *hid) hid->group = HID_GROUP_GENERIC; /* + * In case we are re-scanning after a BPF has been loaded, + * we need to use the bpf report descriptor, not the original one. + */ + if (hid->bpf_rdesc && hid->bpf_rsize) { + start = hid->bpf_rdesc; + end = start + hid->bpf_rsize; + } + + /* * The parsing is simpler than the one in hid_open_report() as we should * be robust against hid errors. Those errors will be raised by * hid_open_report() anyway. @@ -2708,12 +2717,32 @@ static bool hid_check_device_match(struct hid_device *hdev, return !hid_ignore_special_drivers && !(hdev->quirks & HID_QUIRK_IGNORE_SPECIAL_DRIVER); } +static void hid_set_group(struct hid_device *hdev) +{ + int ret; + + if (hid_ignore_special_drivers) { + hdev->group = HID_GROUP_GENERIC; + } else if (!hdev->group && + !(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) { + ret = hid_scan_report(hdev); + if (ret) + hid_warn(hdev, "bad device descriptor (%d)\n", ret); + } +} + static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv) { const struct hid_device_id *id; int ret; if (!hdev->bpf_rsize) { + /* we keep a reference to the currently scanned report descriptor */ + const __u8 *original_rdesc = hdev->bpf_rdesc; + + if (!original_rdesc) + original_rdesc = hdev->dev_rdesc; + /* in case a bpf program gets detached, we need to free the old one */ hid_free_bpf_rdesc(hdev); @@ -2723,6 +2752,12 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv) /* call_hid_bpf_rdesc_fixup will always return a valid pointer */ hdev->bpf_rdesc = call_hid_bpf_rdesc_fixup(hdev, hdev->dev_rdesc, &hdev->bpf_rsize); + + /* the report descriptor changed, we need to re-scan it */ + if (original_rdesc != hdev->bpf_rdesc) { + hdev->group = 0; + hid_set_group(hdev); + } } if (!hid_check_device_match(hdev, hdrv, &id)) @@ -2903,14 +2938,7 @@ int hid_add_device(struct hid_device *hdev) /* * Scan generic devices for group information */ - if (hid_ignore_special_drivers) { - hdev->group = HID_GROUP_GENERIC; - } else if (!hdev->group && - !(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) { - ret = hid_scan_report(hdev); - if (ret) - hid_warn(hdev, "bad device descriptor (%d)\n", ret); - } + hid_set_group(hdev); hdev->id = atomic_inc_return(&id); diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c new file mode 100644 index 000000000000..aa090684c1f2 --- /dev/null +++ b/drivers/hid/hid-haptic.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID Haptic support for Linux + * + * Copyright (c) 2021 Angela Czubak <acz@semihalf.com> + */ + +#include <linux/input/mt.h> +#include <linux/module.h> + +#include "hid-haptic.h" + +void hid_haptic_feature_mapping(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_field *field, struct hid_usage *usage) +{ + u16 usage_hid; + + if (usage->hid == HID_HP_AUTOTRIGGER) { + if (usage->usage_index >= field->report_count) { + dev_err(&hdev->dev, + "HID_HP_AUTOTRIGGER out of range\n"); + return; + } + + hid_device_io_start(hdev); + hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); + hid_hw_wait(hdev); + hid_device_io_stop(hdev); + haptic->default_auto_trigger = + field->value[usage->usage_index]; + haptic->auto_trigger_report = field->report; + } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ORDINAL) { + usage_hid = usage->hid & HID_USAGE; + switch (field->logical) { + case HID_HP_WAVEFORMLIST: + if (usage_hid > haptic->max_waveform_id) + haptic->max_waveform_id = usage_hid; + break; + case HID_HP_DURATIONLIST: + if (usage_hid > haptic->max_duration_id) + haptic->max_duration_id = usage_hid; + break; + default: + break; + } + } +} +EXPORT_SYMBOL_GPL(hid_haptic_feature_mapping); + +bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic, + struct hid_input *hi, struct hid_field *field) +{ + if (field->unit == HID_UNIT_GRAM || field->unit == HID_UNIT_NEWTON) { + haptic->force_logical_minimum = field->logical_minimum; + haptic->force_physical_minimum = field->physical_minimum; + haptic->force_resolution = input_abs_get_res(hi->input, + ABS_MT_PRESSURE); + return true; + } + return false; +} +EXPORT_SYMBOL_GPL(hid_haptic_check_pressure_unit); + +int hid_haptic_input_mapping(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + if (usage->hid == HID_HP_MANUALTRIGGER) { + haptic->manual_trigger_report = field->report; + /* we don't really want to map these fields */ + return -1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(hid_haptic_input_mapping); + +int hid_haptic_input_configured(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_input *hi) +{ + + if (hi->application == HID_DG_TOUCHPAD) { + if (haptic->auto_trigger_report && + haptic->manual_trigger_report) { + __set_bit(INPUT_PROP_HAPTIC_TOUCHPAD, hi->input->propbit); + return 1; + } + return 0; + } + return -1; +} +EXPORT_SYMBOL_GPL(hid_haptic_input_configured); + +static void parse_auto_trigger_field(struct hid_haptic_device *haptic, + struct hid_field *field) +{ + int count = field->report_count; + int n; + u16 usage_hid; + + for (n = 0; n < count; n++) { + switch (field->usage[n].hid & HID_USAGE_PAGE) { + case HID_UP_ORDINAL: + usage_hid = field->usage[n].hid & HID_USAGE; + switch (field->logical) { + case HID_HP_WAVEFORMLIST: + haptic->hid_usage_map[usage_hid] = field->value[n]; + if (field->value[n] == + (HID_HP_WAVEFORMPRESS & HID_USAGE)) { + haptic->press_ordinal = usage_hid; + } else if (field->value[n] == + (HID_HP_WAVEFORMRELEASE & HID_USAGE)) { + haptic->release_ordinal = usage_hid; + } + break; + case HID_HP_DURATIONLIST: + haptic->duration_map[usage_hid] = + field->value[n]; + break; + default: + break; + } + break; + case HID_UP_HAPTIC: + switch (field->usage[n].hid) { + case HID_HP_WAVEFORMVENDORID: + haptic->vendor_id = field->value[n]; + break; + case HID_HP_WAVEFORMVENDORPAGE: + haptic->vendor_page = field->value[n]; + break; + default: + break; + } + break; + default: + /* Should not really happen */ + break; + } + } +} + +static void fill_effect_buf(struct hid_haptic_device *haptic, + struct ff_haptic_effect *effect, + struct hid_haptic_effect *haptic_effect, + int waveform_ordinal) +{ + struct hid_report *rep = haptic->manual_trigger_report; + struct hid_usage *usage; + struct hid_field *field; + s32 value; + int i, j; + u8 *buf = haptic_effect->report_buf; + + mutex_lock(&haptic->manual_trigger_mutex); + for (i = 0; i < rep->maxfield; i++) { + field = rep->field[i]; + /* Ignore if report count is out of bounds. */ + if (field->report_count < 1) + continue; + + for (j = 0; j < field->maxusage; j++) { + usage = &field->usage[j]; + + switch (usage->hid) { + case HID_HP_INTENSITY: + if (effect->intensity > 100) { + value = field->logical_maximum; + } else { + value = field->logical_minimum + + effect->intensity * + (field->logical_maximum - + field->logical_minimum) / 100; + } + break; + case HID_HP_REPEATCOUNT: + value = effect->repeat_count; + break; + case HID_HP_RETRIGGERPERIOD: + value = effect->retrigger_period; + break; + case HID_HP_MANUALTRIGGER: + value = waveform_ordinal; + break; + default: + break; + } + + field->value[j] = value; + } + } + + hid_output_report(rep, buf); + mutex_unlock(&haptic->manual_trigger_mutex); +} + +static void switch_mode(struct hid_device *hdev, struct hid_haptic_device *haptic, + int mode) +{ + struct hid_report *rep = haptic->auto_trigger_report; + struct hid_field *field; + s32 value; + int i, j; + + if (mode == HID_HAPTIC_MODE_HOST) + value = HID_HAPTIC_ORDINAL_WAVEFORMSTOP; + else + value = haptic->default_auto_trigger; + + mutex_lock(&haptic->auto_trigger_mutex); + for (i = 0; i < rep->maxfield; i++) { + field = rep->field[i]; + /* Ignore if report count is out of bounds. */ + if (field->report_count < 1) + continue; + + for (j = 0; j < field->maxusage; j++) { + if (field->usage[j].hid == HID_HP_AUTOTRIGGER) + field->value[j] = value; + } + } + + /* send the report */ + hid_hw_request(hdev, rep, HID_REQ_SET_REPORT); + mutex_unlock(&haptic->auto_trigger_mutex); + haptic->mode = mode; +} + +static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *effect, + struct ff_effect *old) +{ + struct hid_device *hdev = input_get_drvdata(dev); + struct ff_device *ff = dev->ff; + struct hid_haptic_device *haptic = ff->private; + int i, ordinal = 0; + bool switch_modes = false; + + /* If vendor range, check vendor id and page */ + if (effect->u.haptic.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) && + effect->u.haptic.hid_usage <= (HID_HP_VENDORWAVEFORMMAX & HID_USAGE) && + (effect->u.haptic.vendor_id != haptic->vendor_id || + effect->u.haptic.vendor_waveform_page != haptic->vendor_page)) + return -EINVAL; + + /* Check hid_usage */ + for (i = 1; i <= haptic->max_waveform_id; i++) { + if (haptic->hid_usage_map[i] == effect->u.haptic.hid_usage) { + ordinal = i; + break; + } + } + if (ordinal < 1) + return -EINVAL; + + /* Fill the buffer for the effect id */ + fill_effect_buf(haptic, &effect->u.haptic, &haptic->effect[effect->id], + ordinal); + + if (effect->u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE) || + effect->u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) + switch_modes = true; + + /* If device is in autonomous mode, and the uploaded effect signals userspace + * wants control of the device, change modes + */ + if (switch_modes && haptic->mode == HID_HAPTIC_MODE_DEVICE) + switch_mode(hdev, haptic, HID_HAPTIC_MODE_HOST); + + return 0; +} + +static int play_effect(struct hid_device *hdev, struct hid_haptic_device *haptic, + struct hid_haptic_effect *effect) +{ + int ret; + + ret = hid_hw_output_report(hdev, effect->report_buf, + haptic->manual_trigger_report_len); + if (ret < 0) { + ret = hid_hw_raw_request(hdev, + haptic->manual_trigger_report->id, + effect->report_buf, + haptic->manual_trigger_report_len, + HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); + } + + return ret; +} + +static void haptic_work_handler(struct work_struct *work) +{ + + struct hid_haptic_effect *effect = container_of(work, + struct hid_haptic_effect, + work); + struct input_dev *dev = effect->input_dev; + struct hid_device *hdev = input_get_drvdata(dev); + struct hid_haptic_device *haptic = dev->ff->private; + + mutex_lock(&haptic->manual_trigger_mutex); + if (effect != &haptic->stop_effect) + play_effect(hdev, haptic, &haptic->stop_effect); + + play_effect(hdev, haptic, effect); + mutex_unlock(&haptic->manual_trigger_mutex); + +} + +static int hid_haptic_playback(struct input_dev *dev, int effect_id, int value) +{ + struct hid_haptic_device *haptic = dev->ff->private; + + if (value) + queue_work(haptic->wq, &haptic->effect[effect_id].work); + else + queue_work(haptic->wq, &haptic->stop_effect.work); + + return 0; +} + +static void effect_set_default(struct ff_effect *effect) +{ + effect->type = FF_HAPTIC; + effect->id = -1; + effect->u.haptic.hid_usage = HID_HP_WAVEFORMNONE & HID_USAGE; + effect->u.haptic.intensity = 100; + effect->u.haptic.retrigger_period = 0; + effect->u.haptic.repeat_count = 0; +} + +static int hid_haptic_erase(struct input_dev *dev, int effect_id) +{ + struct hid_haptic_device *haptic = dev->ff->private; + struct hid_device *hdev = input_get_drvdata(dev); + struct ff_effect effect; + int ordinal; + + effect_set_default(&effect); + + if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) { + ordinal = haptic->release_ordinal; + if (!ordinal) { + ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE; + if (haptic->mode == HID_HAPTIC_MODE_HOST) + switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE); + } else + effect.u.haptic.hid_usage = HID_HP_WAVEFORMRELEASE & HID_USAGE; + + fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id], + ordinal); + } else if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE)) { + ordinal = haptic->press_ordinal; + if (!ordinal) { + ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE; + if (haptic->mode == HID_HAPTIC_MODE_HOST) + switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE); + } + else + effect.u.haptic.hid_usage = HID_HP_WAVEFORMPRESS & HID_USAGE; + + fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id], + ordinal); + } + + return 0; +} + +static void hid_haptic_destroy(struct ff_device *ff) +{ + struct hid_haptic_device *haptic = ff->private; + struct hid_device *hdev = haptic->hdev; + int r; + + if (hdev) + put_device(&hdev->dev); + + kfree(haptic->stop_effect.report_buf); + haptic->stop_effect.report_buf = NULL; + + if (haptic->effect) { + for (r = 0; r < ff->max_effects; r++) + kfree(haptic->effect[r].report_buf); + kfree(haptic->effect); + } + haptic->effect = NULL; + + destroy_workqueue(haptic->wq); + haptic->wq = NULL; + + kfree(haptic->duration_map); + haptic->duration_map = NULL; + + kfree(haptic->hid_usage_map); + haptic->hid_usage_map = NULL; + + module_put(THIS_MODULE); +} + +int hid_haptic_init(struct hid_device *hdev, + struct hid_haptic_device **haptic_ptr) +{ + struct hid_haptic_device *haptic = *haptic_ptr; + struct input_dev *dev = NULL; + struct hid_input *hidinput; + struct ff_device *ff; + int ret = 0, r; + struct ff_haptic_effect stop_effect = { + .hid_usage = HID_HP_WAVEFORMSTOP & HID_USAGE, + }; + const char *prefix = "hid-haptic"; + char *name; + int (*flush)(struct input_dev *dev, struct file *file); + int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); + + haptic->hdev = hdev; + haptic->max_waveform_id = max(2u, haptic->max_waveform_id); + haptic->max_duration_id = max(2u, haptic->max_duration_id); + + haptic->hid_usage_map = kcalloc(haptic->max_waveform_id + 1, + sizeof(u16), GFP_KERNEL); + if (!haptic->hid_usage_map) { + ret = -ENOMEM; + goto exit; + } + haptic->duration_map = kcalloc(haptic->max_duration_id + 1, + sizeof(u32), GFP_KERNEL); + if (!haptic->duration_map) { + ret = -ENOMEM; + goto usage_map; + } + + if (haptic->max_waveform_id != haptic->max_duration_id) + dev_warn(&hdev->dev, + "Haptic duration and waveform lists have different max id (%u and %u).\n", + haptic->max_duration_id, haptic->max_waveform_id); + + haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMNONE] = + HID_HP_WAVEFORMNONE & HID_USAGE; + haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMSTOP] = + HID_HP_WAVEFORMSTOP & HID_USAGE; + + mutex_init(&haptic->auto_trigger_mutex); + for (r = 0; r < haptic->auto_trigger_report->maxfield; r++) + parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]); + + list_for_each_entry(hidinput, &hdev->inputs, list) { + if (hidinput->application == HID_DG_TOUCHPAD) { + dev = hidinput->input; + break; + } + } + + if (!dev) { + dev_err(&hdev->dev, "Failed to find the input device\n"); + ret = -ENODEV; + goto duration_map; + } + + haptic->input_dev = dev; + haptic->manual_trigger_report_len = + hid_report_len(haptic->manual_trigger_report); + mutex_init(&haptic->manual_trigger_mutex); + name = kmalloc(strlen(prefix) + strlen(hdev->name) + 2, GFP_KERNEL); + if (name) { + sprintf(name, "%s %s", prefix, hdev->name); + haptic->wq = create_singlethread_workqueue(name); + kfree(name); + } + if (!haptic->wq) { + ret = -ENOMEM; + goto duration_map; + } + haptic->effect = kcalloc(FF_MAX_EFFECTS, + sizeof(struct hid_haptic_effect), GFP_KERNEL); + if (!haptic->effect) { + ret = -ENOMEM; + goto output_queue; + } + for (r = 0; r < FF_MAX_EFFECTS; r++) { + haptic->effect[r].report_buf = + hid_alloc_report_buf(haptic->manual_trigger_report, + GFP_KERNEL); + if (!haptic->effect[r].report_buf) { + dev_err(&hdev->dev, + "Failed to allocate a buffer for an effect.\n"); + ret = -ENOMEM; + goto buffer_free; + } + haptic->effect[r].input_dev = dev; + INIT_WORK(&haptic->effect[r].work, haptic_work_handler); + } + haptic->stop_effect.report_buf = + hid_alloc_report_buf(haptic->manual_trigger_report, + GFP_KERNEL); + if (!haptic->stop_effect.report_buf) { + dev_err(&hdev->dev, + "Failed to allocate a buffer for stop effect.\n"); + ret = -ENOMEM; + goto buffer_free; + } + haptic->stop_effect.input_dev = dev; + INIT_WORK(&haptic->stop_effect.work, haptic_work_handler); + fill_effect_buf(haptic, &stop_effect, &haptic->stop_effect, + HID_HAPTIC_ORDINAL_WAVEFORMSTOP); + + input_set_capability(dev, EV_FF, FF_HAPTIC); + + flush = dev->flush; + event = dev->event; + ret = input_ff_create(dev, FF_MAX_EFFECTS); + if (ret) { + dev_err(&hdev->dev, "Failed to create ff device.\n"); + goto stop_buffer_free; + } + + ff = dev->ff; + ff->private = haptic; + ff->upload = hid_haptic_upload_effect; + ff->playback = hid_haptic_playback; + ff->erase = hid_haptic_erase; + ff->destroy = hid_haptic_destroy; + if (!try_module_get(THIS_MODULE)) { + dev_err(&hdev->dev, "Failed to increase module count.\n"); + goto input_free; + } + if (!get_device(&hdev->dev)) { + dev_err(&hdev->dev, "Failed to get hdev device.\n"); + module_put(THIS_MODULE); + goto input_free; + } + return 0; + +input_free: + input_ff_destroy(dev); + /* Do not let double free happen, input_ff_destroy will call + * hid_haptic_destroy. + */ + *haptic_ptr = NULL; + /* Restore dev flush and event */ + dev->flush = flush; + dev->event = event; + return ret; +stop_buffer_free: + kfree(haptic->stop_effect.report_buf); + haptic->stop_effect.report_buf = NULL; +buffer_free: + while (--r >= 0) + kfree(haptic->effect[r].report_buf); + kfree(haptic->effect); + haptic->effect = NULL; +output_queue: + destroy_workqueue(haptic->wq); + haptic->wq = NULL; +duration_map: + kfree(haptic->duration_map); + haptic->duration_map = NULL; +usage_map: + kfree(haptic->hid_usage_map); + haptic->hid_usage_map = NULL; +exit: + return ret; +} +EXPORT_SYMBOL_GPL(hid_haptic_init); + +void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) +{ + haptic->pressure_sum = 0; +} +EXPORT_SYMBOL_GPL(hid_haptic_pressure_reset); + +void hid_haptic_pressure_increase(struct hid_haptic_device *haptic, + __s32 pressure) +{ + haptic->pressure_sum += pressure; +} +EXPORT_SYMBOL_GPL(hid_haptic_pressure_increase); diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h new file mode 100644 index 000000000000..c6539ac04c1d --- /dev/null +++ b/drivers/hid/hid-haptic.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * HID Haptic support for Linux + * + * Copyright (c) 2021 Angela Czubak <acz@semihalf.com> + */ + +#include <linux/hid.h> + +#define HID_HAPTIC_ORDINAL_WAVEFORMNONE 1 +#define HID_HAPTIC_ORDINAL_WAVEFORMSTOP 2 + +#define HID_HAPTIC_MODE_DEVICE 0 +#define HID_HAPTIC_MODE_HOST 1 + +struct hid_haptic_effect { + u8 *report_buf; + struct input_dev *input_dev; + struct work_struct work; + struct list_head control; + struct mutex control_mutex; +}; + +struct hid_haptic_effect_node { + struct list_head node; + struct file *file; +}; + +struct hid_haptic_device { + struct input_dev *input_dev; + struct hid_device *hdev; + struct hid_report *auto_trigger_report; + struct mutex auto_trigger_mutex; + struct workqueue_struct *wq; + struct hid_report *manual_trigger_report; + struct mutex manual_trigger_mutex; + size_t manual_trigger_report_len; + int pressed_state; + s32 pressure_sum; + s32 force_logical_minimum; + s32 force_physical_minimum; + s32 force_resolution; + u32 mode; + u32 default_auto_trigger; + u32 vendor_page; + u32 vendor_id; + u32 max_waveform_id; + u32 max_duration_id; + u16 *hid_usage_map; + u32 *duration_map; + u16 press_ordinal; + u16 release_ordinal; + struct hid_haptic_effect *effect; + struct hid_haptic_effect stop_effect; +}; + +#if IS_ENABLED(CONFIG_HID_HAPTIC) +void hid_haptic_feature_mapping(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_field *field, struct hid_usage + *usage); +bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic, + struct hid_input *hi, struct hid_field *field); +int hid_haptic_input_mapping(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max); +int hid_haptic_input_configured(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_input *hi); +int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr); +void hid_haptic_handle_press_release(struct hid_haptic_device *haptic); +void hid_haptic_pressure_reset(struct hid_haptic_device *haptic); +void hid_haptic_pressure_increase(struct hid_haptic_device *haptic, + __s32 pressure); +#else +static inline +void hid_haptic_feature_mapping(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_field *field, struct hid_usage + *usage) +{} +static inline +bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic, + struct hid_input *hi, struct hid_field *field) +{ + return false; +} +static inline +int hid_haptic_input_mapping(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + return 0; +} +static inline +int hid_haptic_input_configured(struct hid_device *hdev, + struct hid_haptic_device *haptic, + struct hid_input *hi) +{ + return 0; +} +static inline +void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic) +{} +static inline +int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr) +{ + return 0; +} +static inline +void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) {} +static inline +bool hid_haptic_handle_input(struct hid_haptic_device *haptic) +{ + return false; +} +static inline +void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) {} +static inline +void hid_haptic_pressure_increase(struct hid_haptic_device *haptic, + __s32 pressure) +{} +#endif diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 149798754570..5721b8414bbd 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -223,7 +223,7 @@ #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD 0x1866 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6 -#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3 0x1a30 +#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO 0x1a30 #define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR 0x18c6 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X 0x1b4c @@ -1296,6 +1296,8 @@ #define USB_VENDOR_ID_STEELSERIES 0x1038 #define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2 #define USB_VENDOR_ID_SUN 0x0430 #define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index f45f856a127f..5d7532d79d21 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -303,6 +303,19 @@ __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code) } break; + case ABS_PRESSURE: + case ABS_MT_PRESSURE: + if (field->unit == HID_UNIT_NEWTON) { + /* Convert to grams, 1 newton is 101.97 grams */ + prev = physical_extents; + physical_extents *= 10197; + if (physical_extents < prev) + return 0; + unit_exponent -= 2; + } else if (field->unit != HID_UNIT_GRAM) { + return 0; + } + break; default: return 0; } @@ -683,9 +696,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel if (field->report_count < 1) goto ignore; - /* only LED usages are supported in output fields */ + /* only LED and HAPTIC usages are supported in output fields */ if (field->report_type == HID_OUTPUT_REPORT && - (usage->hid & HID_USAGE_PAGE) != HID_UP_LED) { + (usage->hid & HID_USAGE_PAGE) != HID_UP_LED && + (usage->hid & HID_USAGE_PAGE) != HID_UP_HAPTIC) { goto ignore; } diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 22c6314a8843..2879e65cf303 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -49,6 +49,8 @@ MODULE_LICENSE("GPL"); #include "hid-ids.h" +#include "hid-haptic.h" + /* quirks to control the device */ #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) #define MT_QUIRK_SLOT_IS_CONTACTID BIT(1) @@ -168,11 +170,13 @@ struct mt_report_data { struct mt_device { struct mt_class mtclass; /* our mt device class */ struct timer_list release_timer; /* to release sticky fingers */ + struct hid_haptic_device *haptic; /* haptic related configuration */ struct hid_device *hdev; /* hid_device we're attached to */ unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_*) */ __u8 inputmode_value; /* InputMode HID feature value */ __u8 maxcontacts; bool is_buttonpad; /* is this device a button pad? */ + bool is_haptic_touchpad; /* is this device a haptic touchpad? */ bool serial_maybe; /* need to check for serial protocol */ struct list_head applications; @@ -533,6 +537,8 @@ static void mt_feature_mapping(struct hid_device *hdev, mt_get_feature(hdev, field->report); break; } + + hid_haptic_feature_mapping(hdev, td->haptic, field, usage); } static void set_abs(struct input_dev *input, unsigned int code, @@ -888,6 +894,9 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi, case HID_DG_TIPPRESSURE: set_abs(hi->input, ABS_MT_PRESSURE, field, cls->sn_pressure); + td->is_haptic_touchpad = + hid_haptic_check_pressure_unit(td->haptic, + hi, field); MT_STORE_FIELD(p); return 1; case HID_DG_SCANTIME: @@ -1008,6 +1017,8 @@ static void mt_sync_frame(struct mt_device *td, struct mt_application *app, app->num_received = 0; app->left_button_state = 0; + if (td->is_haptic_touchpad) + hid_haptic_pressure_reset(td->haptic); if (test_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags)) set_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags); @@ -1165,6 +1176,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input, minor = minor >> 1; } + if (td->is_haptic_touchpad) + hid_haptic_pressure_increase(td->haptic, *slot->p); + x = hdev->quirks & HID_QUIRK_X_INVERT ? input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x : *slot->x; @@ -1366,6 +1380,9 @@ static int mt_touch_input_configured(struct hid_device *hdev, if (cls->is_indirect) app->mt_flags |= INPUT_MT_POINTER; + if (td->is_haptic_touchpad) + app->mt_flags |= INPUT_MT_TOTAL_FORCE; + if (app->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP) app->mt_flags |= INPUT_MT_DROP_UNUSED; @@ -1401,6 +1418,7 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct mt_device *td = hid_get_drvdata(hdev); struct mt_application *application; struct mt_report_data *rdata; + int ret; rdata = mt_find_report_data(td, field->report); if (!rdata) { @@ -1463,6 +1481,11 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, if (field->physical == HID_DG_STYLUS) hi->application = HID_DG_STYLUS; + ret = hid_haptic_input_mapping(hdev, td->haptic, hi, field, usage, bit, + max); + if (ret != 0) + return ret; + /* let hid-core decide for the others */ return 0; } @@ -1685,6 +1708,14 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) struct hid_report *report; int ret; + if (td->is_haptic_touchpad && (td->mtclass.name == MT_CLS_WIN_8 || + td->mtclass.name == MT_CLS_WIN_8_FORCE_MULTI_INPUT)) { + if (hid_haptic_input_configured(hdev, td->haptic, hi) == 0) + td->is_haptic_touchpad = false; + } else { + td->is_haptic_touchpad = false; + } + list_for_each_entry(report, &hi->reports, hidinput_list) { rdata = mt_find_report_data(td, report); if (!rdata) { @@ -1827,6 +1858,11 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) dev_err(&hdev->dev, "cannot allocate multitouch data\n"); return -ENOMEM; } + td->haptic = devm_kzalloc(&hdev->dev, sizeof(*(td->haptic)), GFP_KERNEL); + if (!td->haptic) + return -ENOMEM; + + td->haptic->hdev = hdev; td->hdev = hdev; td->mtclass = *mtclass; td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; @@ -1895,6 +1931,17 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL); + if (td->is_haptic_touchpad) { + if (hid_haptic_init(hdev, &td->haptic)) { + dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n", + hdev->name); + td->is_haptic_touchpad = false; + devm_kfree(&hdev->dev, td->haptic); + } + } else { + devm_kfree(&hdev->dev, td->haptic); + } + return 0; } diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 1468fb11e39d..63f6eb9030d1 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -5,7 +5,9 @@ * Copyright (c) 2020-2022 Sony Interactive Entertainment */ +#include <linux/bitfield.h> #include <linux/bits.h> +#include <linux/cleanup.h> #include <linux/crc32.h> #include <linux/device.h> #include <linux/hid.h> @@ -36,19 +38,19 @@ enum PS_TYPE { struct ps_device { struct list_head list; struct hid_device *hdev; - spinlock_t lock; + spinlock_t lock; /* Sync between event handler and workqueue */ - uint32_t player_id; + u32 player_id; struct power_supply_desc battery_desc; struct power_supply *battery; - uint8_t battery_capacity; + u8 battery_capacity; int battery_status; const char *input_dev_name; /* Name of primary input device. */ - uint8_t mac_address[6]; /* Note: stored in little endian order. */ - uint32_t hw_version; - uint32_t fw_version; + u8 mac_address[6]; /* Note: stored in little endian order. */ + u32 hw_version; + u32 fw_version; int (*parse_report)(struct ps_device *dev, struct hid_report *report, u8 *data, int size); void (*remove)(struct ps_device *dev); @@ -110,41 +112,62 @@ struct ps_led_info { #define DS_BUTTONS2_TOUCHPAD BIT(1) #define DS_BUTTONS2_MIC_MUTE BIT(2) -/* Status field of DualSense input report. */ -#define DS_STATUS_BATTERY_CAPACITY GENMASK(3, 0) -#define DS_STATUS_CHARGING GENMASK(7, 4) -#define DS_STATUS_CHARGING_SHIFT 4 +/* Status fields of DualSense input report. */ +#define DS_STATUS0_BATTERY_CAPACITY GENMASK(3, 0) +#define DS_STATUS0_CHARGING GENMASK(7, 4) +#define DS_STATUS1_HP_DETECT BIT(0) +#define DS_STATUS1_MIC_DETECT BIT(1) +#define DS_STATUS1_JACK_DETECT (DS_STATUS1_HP_DETECT | DS_STATUS1_MIC_DETECT) +#define DS_STATUS1_MIC_MUTE BIT(2) /* Feature version from DualSense Firmware Info report. */ -#define DS_FEATURE_VERSION(major, minor) ((major & 0xff) << 8 | (minor & 0xff)) - +#define DS_FEATURE_VERSION_MINOR GENMASK(7, 0) +#define DS_FEATURE_VERSION_MAJOR GENMASK(15, 8) +#define DS_FEATURE_VERSION(major, minor) (FIELD_PREP(DS_FEATURE_VERSION_MAJOR, major) | \ + FIELD_PREP(DS_FEATURE_VERSION_MINOR, minor)) /* * Status of a DualSense touch point contact. * Contact IDs, with highest bit set are 'inactive' * and any associated data is then invalid. */ -#define DS_TOUCH_POINT_INACTIVE BIT(7) +#define DS_TOUCH_POINT_INACTIVE BIT(7) +#define DS_TOUCH_POINT_X_LO GENMASK(7, 0) +#define DS_TOUCH_POINT_X_HI GENMASK(11, 8) +#define DS_TOUCH_POINT_X(hi, lo) (FIELD_PREP(DS_TOUCH_POINT_X_HI, hi) | \ + FIELD_PREP(DS_TOUCH_POINT_X_LO, lo)) +#define DS_TOUCH_POINT_Y_LO GENMASK(3, 0) +#define DS_TOUCH_POINT_Y_HI GENMASK(11, 4) +#define DS_TOUCH_POINT_Y(hi, lo) (FIELD_PREP(DS_TOUCH_POINT_Y_HI, hi) | \ + FIELD_PREP(DS_TOUCH_POINT_Y_LO, lo)) /* Magic value required in tag field of Bluetooth output report. */ -#define DS_OUTPUT_TAG 0x10 +#define DS_OUTPUT_TAG 0x10 +#define DS_OUTPUT_SEQ_TAG GENMASK(3, 0) +#define DS_OUTPUT_SEQ_NO GENMASK(7, 4) /* Flags for DualSense output report. */ -#define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0) -#define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1) -#define DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE BIT(0) -#define DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE BIT(1) -#define DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE BIT(2) -#define DS_OUTPUT_VALID_FLAG1_RELEASE_LEDS BIT(3) -#define DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE BIT(4) -#define DS_OUTPUT_VALID_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1) -#define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 BIT(2) -#define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4) -#define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1) +#define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0) +#define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1) +#define DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE BIT(5) +#define DS_OUTPUT_VALID_FLAG0_MIC_VOLUME_ENABLE BIT(6) +#define DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE BIT(7) +#define DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE BIT(0) +#define DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE BIT(1) +#define DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE BIT(2) +#define DS_OUTPUT_VALID_FLAG1_RELEASE_LEDS BIT(3) +#define DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE BIT(4) +#define DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE BIT(7) +#define DS_OUTPUT_VALID_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1) +#define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 BIT(2) +#define DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL GENMASK(5, 4) +#define DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN GENMASK(2, 0) +#define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4) +#define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1) /* DualSense hardware limits */ #define DS_ACC_RES_PER_G 8192 -#define DS_ACC_RANGE (4*DS_ACC_RES_PER_G) +#define DS_ACC_RANGE (4 * DS_ACC_RES_PER_G) #define DS_GYRO_RES_PER_DEG_S 1024 -#define DS_GYRO_RANGE (2048*DS_GYRO_RES_PER_DEG_S) +#define DS_GYRO_RANGE (2048 * DS_GYRO_RES_PER_DEG_S) #define DS_TOUCHPAD_WIDTH 1920 #define DS_TOUCHPAD_HEIGHT 1080 @@ -153,9 +176,10 @@ struct dualsense { struct input_dev *gamepad; struct input_dev *sensors; struct input_dev *touchpad; + struct input_dev *jack; /* Update version is used as a feature/capability version. */ - uint16_t update_version; + u16 update_version; /* Calibration data for accelerometer and gyroscope. */ struct ps_calibration_data accel_calib_data[3]; @@ -163,21 +187,26 @@ struct dualsense { /* Timestamp for sensor data */ bool sensor_timestamp_initialized; - uint32_t prev_sensor_timestamp; - uint32_t sensor_timestamp_us; + u32 prev_sensor_timestamp; + u32 sensor_timestamp_us; /* Compatible rumble state */ bool use_vibration_v2; bool update_rumble; - uint8_t motor_left; - uint8_t motor_right; + u8 motor_left; + u8 motor_right; /* RGB lightbar */ struct led_classdev_mc lightbar; bool update_lightbar; - uint8_t lightbar_red; - uint8_t lightbar_green; - uint8_t lightbar_blue; + u8 lightbar_red; + u8 lightbar_green; + u8 lightbar_blue; + + /* Audio Jack plugged state */ + u8 plugged_state; + u8 prev_plugged_state; + bool prev_plugged_state_valid; /* Microphone */ bool update_mic_mute; @@ -186,90 +215,94 @@ struct dualsense { /* Player leds */ bool update_player_leds; - uint8_t player_leds_state; + u8 player_leds_state; struct led_classdev player_leds[5]; struct work_struct output_worker; bool output_worker_initialized; void *output_report_dmabuf; - uint8_t output_seq; /* Sequence number for output report. */ + u8 output_seq; /* Sequence number for output report. */ }; struct dualsense_touch_point { - uint8_t contact; - uint8_t x_lo; - uint8_t x_hi:4, y_lo:4; - uint8_t y_hi; + u8 contact; + u8 x_lo; + u8 x_hi:4, y_lo:4; + u8 y_hi; } __packed; static_assert(sizeof(struct dualsense_touch_point) == 4); /* Main DualSense input report excluding any BT/USB specific headers. */ struct dualsense_input_report { - uint8_t x, y; - uint8_t rx, ry; - uint8_t z, rz; - uint8_t seq_number; - uint8_t buttons[4]; - uint8_t reserved[4]; + u8 x, y; + u8 rx, ry; + u8 z, rz; + u8 seq_number; + u8 buttons[4]; + u8 reserved[4]; /* Motion sensors */ __le16 gyro[3]; /* x, y, z */ __le16 accel[3]; /* x, y, z */ __le32 sensor_timestamp; - uint8_t reserved2; + u8 reserved2; /* Touchpad */ struct dualsense_touch_point points[2]; - uint8_t reserved3[12]; - uint8_t status; - uint8_t reserved4[10]; + u8 reserved3[12]; + u8 status[3]; + u8 reserved4[8]; } __packed; /* Common input report size shared equals the size of the USB report minus 1 byte for ReportID. */ static_assert(sizeof(struct dualsense_input_report) == DS_INPUT_REPORT_USB_SIZE - 1); /* Common data between DualSense BT/USB main output report. */ struct dualsense_output_report_common { - uint8_t valid_flag0; - uint8_t valid_flag1; + u8 valid_flag0; + u8 valid_flag1; /* For DualShock 4 compatibility mode. */ - uint8_t motor_right; - uint8_t motor_left; + u8 motor_right; + u8 motor_left; /* Audio controls */ - uint8_t reserved[4]; - uint8_t mute_button_led; + u8 headphone_volume; /* 0x0 - 0x7f */ + u8 speaker_volume; /* 0x0 - 0xff */ + u8 mic_volume; /* 0x0 - 0x40 */ + u8 audio_control; + u8 mute_button_led; - uint8_t power_save_control; - uint8_t reserved2[28]; + u8 power_save_control; + u8 reserved2[27]; + u8 audio_control2; /* LEDs and lightbar */ - uint8_t valid_flag2; - uint8_t reserved3[2]; - uint8_t lightbar_setup; - uint8_t led_brightness; - uint8_t player_leds; - uint8_t lightbar_red; - uint8_t lightbar_green; - uint8_t lightbar_blue; + u8 valid_flag2; + u8 reserved3[2]; + u8 lightbar_setup; + u8 led_brightness; + u8 player_leds; + u8 lightbar_red; + u8 lightbar_green; + u8 lightbar_blue; } __packed; static_assert(sizeof(struct dualsense_output_report_common) == 47); struct dualsense_output_report_bt { - uint8_t report_id; /* 0x31 */ - uint8_t seq_tag; - uint8_t tag; + u8 report_id; /* 0x31 */ + u8 seq_tag; + u8 tag; struct dualsense_output_report_common common; - uint8_t reserved[24]; + u8 reserved[24]; __le32 crc32; } __packed; static_assert(sizeof(struct dualsense_output_report_bt) == DS_OUTPUT_REPORT_BT_SIZE); struct dualsense_output_report_usb { - uint8_t report_id; /* 0x02 */ + u8 report_id; /* 0x02 */ struct dualsense_output_report_common common; - uint8_t reserved[15]; + u8 reserved[15]; } __packed; static_assert(sizeof(struct dualsense_output_report_usb) == DS_OUTPUT_REPORT_USB_SIZE); @@ -279,8 +312,8 @@ static_assert(sizeof(struct dualsense_output_report_usb) == DS_OUTPUT_REPORT_USB * This structure hide the differences between the two to simplify sending output reports. */ struct dualsense_output_report { - uint8_t *data; /* Start of data */ - uint8_t len; /* Size of output report */ + u8 *data; /* Start of data */ + u8 len; /* Size of output report */ /* Points to Bluetooth data payload in case for a Bluetooth report else NULL. */ struct dualsense_output_report_bt *bt; @@ -315,7 +348,9 @@ struct dualsense_output_report { * Contact IDs, with highest bit set are 'inactive' * and any associated data is then invalid. */ -#define DS4_TOUCH_POINT_INACTIVE BIT(7) +#define DS4_TOUCH_POINT_INACTIVE BIT(7) +#define DS4_TOUCH_POINT_X(hi, lo) DS_TOUCH_POINT_X(hi, lo) +#define DS4_TOUCH_POINT_Y(hi, lo) DS_TOUCH_POINT_Y(hi, lo) /* Status field of DualShock4 input report. */ #define DS4_STATUS0_BATTERY_CAPACITY GENMASK(3, 0) @@ -323,7 +358,7 @@ struct dualsense_output_report { /* Battery status within batery_status field. */ #define DS4_BATTERY_STATUS_FULL 11 /* Status1 bit2 contains dongle connection state: - * 0 = connectd + * 0 = connected * 1 = disconnected */ #define DS4_STATUS1_DONGLE_STATE BIT(2) @@ -349,9 +384,9 @@ struct dualsense_output_report { /* DualShock4 hardware limits */ #define DS4_ACC_RES_PER_G 8192 -#define DS4_ACC_RANGE (4*DS_ACC_RES_PER_G) +#define DS4_ACC_RANGE (4 * DS_ACC_RES_PER_G) #define DS4_GYRO_RES_PER_DEG_S 1024 -#define DS4_GYRO_RANGE (2048*DS_GYRO_RES_PER_DEG_S) +#define DS4_GYRO_RANGE (2048 * DS_GYRO_RES_PER_DEG_S) #define DS4_LIGHTBAR_MAX_BLINK 255 /* 255 centiseconds */ #define DS4_TOUCHPAD_WIDTH 1920 #define DS4_TOUCHPAD_HEIGHT 942 @@ -380,26 +415,26 @@ struct dualshock4 { /* Timestamp for sensor data */ bool sensor_timestamp_initialized; - uint32_t prev_sensor_timestamp; - uint32_t sensor_timestamp_us; + u32 prev_sensor_timestamp; + u32 sensor_timestamp_us; /* Bluetooth poll interval */ bool update_bt_poll_interval; - uint8_t bt_poll_interval; + u8 bt_poll_interval; bool update_rumble; - uint8_t motor_left; - uint8_t motor_right; + u8 motor_left; + u8 motor_right; /* Lightbar leds */ bool update_lightbar; bool update_lightbar_blink; bool lightbar_enabled; /* For use by global LED control. */ - uint8_t lightbar_red; - uint8_t lightbar_green; - uint8_t lightbar_blue; - uint8_t lightbar_blink_on; /* In increments of 10ms. */ - uint8_t lightbar_blink_off; /* In increments of 10ms. */ + u8 lightbar_red; + u8 lightbar_green; + u8 lightbar_blue; + u8 lightbar_blink_on; /* In increments of 10ms. */ + u8 lightbar_blink_off; /* In increments of 10ms. */ struct led_classdev lightbar_leds[4]; struct work_struct output_worker; @@ -408,88 +443,88 @@ struct dualshock4 { }; struct dualshock4_touch_point { - uint8_t contact; - uint8_t x_lo; - uint8_t x_hi:4, y_lo:4; - uint8_t y_hi; + u8 contact; + u8 x_lo; + u8 x_hi:4, y_lo:4; + u8 y_hi; } __packed; static_assert(sizeof(struct dualshock4_touch_point) == 4); struct dualshock4_touch_report { - uint8_t timestamp; + u8 timestamp; struct dualshock4_touch_point points[2]; } __packed; static_assert(sizeof(struct dualshock4_touch_report) == 9); /* Main DualShock4 input report excluding any BT/USB specific headers. */ struct dualshock4_input_report_common { - uint8_t x, y; - uint8_t rx, ry; - uint8_t buttons[3]; - uint8_t z, rz; + u8 x, y; + u8 rx, ry; + u8 buttons[3]; + u8 z, rz; /* Motion sensors */ __le16 sensor_timestamp; - uint8_t sensor_temperature; + u8 sensor_temperature; __le16 gyro[3]; /* x, y, z */ __le16 accel[3]; /* x, y, z */ - uint8_t reserved2[5]; + u8 reserved2[5]; - uint8_t status[2]; - uint8_t reserved3; + u8 status[2]; + u8 reserved3; } __packed; static_assert(sizeof(struct dualshock4_input_report_common) == 32); struct dualshock4_input_report_usb { - uint8_t report_id; /* 0x01 */ + u8 report_id; /* 0x01 */ struct dualshock4_input_report_common common; - uint8_t num_touch_reports; + u8 num_touch_reports; struct dualshock4_touch_report touch_reports[3]; - uint8_t reserved[3]; + u8 reserved[3]; } __packed; static_assert(sizeof(struct dualshock4_input_report_usb) == DS4_INPUT_REPORT_USB_SIZE); struct dualshock4_input_report_bt { - uint8_t report_id; /* 0x11 */ - uint8_t reserved[2]; + u8 report_id; /* 0x11 */ + u8 reserved[2]; struct dualshock4_input_report_common common; - uint8_t num_touch_reports; + u8 num_touch_reports; struct dualshock4_touch_report touch_reports[4]; /* BT has 4 compared to 3 for USB */ - uint8_t reserved2[2]; + u8 reserved2[2]; __le32 crc32; } __packed; static_assert(sizeof(struct dualshock4_input_report_bt) == DS4_INPUT_REPORT_BT_SIZE); /* Common data between Bluetooth and USB DualShock4 output reports. */ struct dualshock4_output_report_common { - uint8_t valid_flag0; - uint8_t valid_flag1; + u8 valid_flag0; + u8 valid_flag1; - uint8_t reserved; + u8 reserved; - uint8_t motor_right; - uint8_t motor_left; + u8 motor_right; + u8 motor_left; - uint8_t lightbar_red; - uint8_t lightbar_green; - uint8_t lightbar_blue; - uint8_t lightbar_blink_on; - uint8_t lightbar_blink_off; + u8 lightbar_red; + u8 lightbar_green; + u8 lightbar_blue; + u8 lightbar_blink_on; + u8 lightbar_blink_off; } __packed; struct dualshock4_output_report_usb { - uint8_t report_id; /* 0x5 */ + u8 report_id; /* 0x5 */ struct dualshock4_output_report_common common; - uint8_t reserved[21]; + u8 reserved[21]; } __packed; static_assert(sizeof(struct dualshock4_output_report_usb) == DS4_OUTPUT_REPORT_USB_SIZE); struct dualshock4_output_report_bt { - uint8_t report_id; /* 0x11 */ - uint8_t hw_control; - uint8_t audio_control; + u8 report_id; /* 0x11 */ + u8 hw_control; + u8 audio_control; struct dualshock4_output_report_common common; - uint8_t reserved[61]; + u8 reserved[61]; __le32 crc32; } __packed; static_assert(sizeof(struct dualshock4_output_report_bt) == DS4_OUTPUT_REPORT_BT_SIZE); @@ -500,8 +535,8 @@ static_assert(sizeof(struct dualshock4_output_report_bt) == DS4_OUTPUT_REPORT_BT * This structure hide the differences between the two to simplify sending output reports. */ struct dualshock4_output_report { - uint8_t *data; /* Start of data */ - uint8_t len; /* Size of output report */ + u8 *data; /* Start of data */ + u8 len; /* Size of output report */ /* Points to Bluetooth data payload in case for a Bluetooth report else NULL. */ struct dualshock4_output_report_bt *bt; @@ -540,7 +575,7 @@ static const struct {int x; int y; } ps_gamepad_hat_mapping[] = { static int dualshock4_get_calibration_data(struct dualshock4 *ds4); static inline void dualsense_schedule_work(struct dualsense *ds); static inline void dualshock4_schedule_work(struct dualshock4 *ds4); -static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue); +static void dualsense_set_lightbar(struct dualsense *ds, u8 red, u8 green, u8 blue); static void dualshock4_set_default_lightbar_colors(struct dualshock4 *ds4); /* @@ -552,26 +587,25 @@ static int ps_devices_list_add(struct ps_device *dev) { struct ps_device *entry; - mutex_lock(&ps_devices_lock); + guard(mutex)(&ps_devices_lock); + list_for_each_entry(entry, &ps_devices_list, list) { if (!memcmp(entry->mac_address, dev->mac_address, sizeof(dev->mac_address))) { hid_err(dev->hdev, "Duplicate device found for MAC address %pMR.\n", - dev->mac_address); - mutex_unlock(&ps_devices_lock); + dev->mac_address); return -EEXIST; } } list_add_tail(&dev->list, &ps_devices_list); - mutex_unlock(&ps_devices_lock); return 0; } static int ps_devices_list_remove(struct ps_device *dev) { - mutex_lock(&ps_devices_lock); + guard(mutex)(&ps_devices_lock); + list_del(&dev->list); - mutex_unlock(&ps_devices_lock); return 0; } @@ -593,7 +627,8 @@ static void ps_device_release_player_id(struct ps_device *dev) dev->player_id = U32_MAX; } -static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const char *name_suffix) +static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, + const char *name_suffix) { struct input_dev *input_dev; @@ -608,8 +643,8 @@ static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const ch input_dev->uniq = hdev->uniq; if (name_suffix) { - input_dev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name, - name_suffix); + input_dev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", + hdev->name, name_suffix); if (!input_dev->name) return ERR_PTR(-ENOMEM); } else { @@ -629,19 +664,18 @@ static enum power_supply_property ps_power_supply_props[] = { }; static int ps_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) + enum power_supply_property psp, + union power_supply_propval *val) { struct ps_device *dev = power_supply_get_drvdata(psy); - uint8_t battery_capacity; + u8 battery_capacity; int battery_status; - unsigned long flags; int ret = 0; - spin_lock_irqsave(&dev->lock, flags); - battery_capacity = dev->battery_capacity; - battery_status = dev->battery_status; - spin_unlock_irqrestore(&dev->lock, flags); + scoped_guard(spinlock_irqsave, &dev->lock) { + battery_capacity = dev->battery_capacity; + battery_status = dev->battery_status; + } switch (psp) { case POWER_SUPPLY_PROP_STATUS: @@ -675,7 +709,7 @@ static int ps_device_register_battery(struct ps_device *dev) dev->battery_desc.num_properties = ARRAY_SIZE(ps_power_supply_props); dev->battery_desc.get_property = ps_battery_get_property; dev->battery_desc.name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL, - "ps-controller-battery-%pMR", dev->mac_address); + "ps-controller-battery-%pMR", dev->mac_address); if (!dev->battery_desc.name) return -ENOMEM; @@ -697,9 +731,9 @@ static int ps_device_register_battery(struct ps_device *dev) } /* Compute crc32 of HID data and compare against expected CRC. */ -static bool ps_check_crc32(uint8_t seed, uint8_t *data, size_t len, uint32_t report_crc) +static bool ps_check_crc32(u8 seed, u8 *data, size_t len, u32 report_crc) { - uint32_t crc; + u32 crc; crc = crc32_le(0xFFFFFFFF, &seed, 1); crc = ~crc32_le(crc, data, len); @@ -707,8 +741,9 @@ static bool ps_check_crc32(uint8_t seed, uint8_t *data, size_t len, uint32_t rep return crc == report_crc; } -static struct input_dev *ps_gamepad_create(struct hid_device *hdev, - int (*play_effect)(struct input_dev *, void *, struct ff_effect *)) +static struct input_dev * +ps_gamepad_create(struct hid_device *hdev, + int (*play_effect)(struct input_dev *, void *, struct ff_effect *)) { struct input_dev *gamepad; unsigned int i; @@ -745,8 +780,8 @@ static struct input_dev *ps_gamepad_create(struct hid_device *hdev, return gamepad; } -static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *buf, size_t size, - bool check_crc) +static int ps_get_report(struct hid_device *hdev, u8 report_id, u8 *buf, + size_t size, bool check_crc) { int ret; @@ -769,8 +804,8 @@ static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *bu if (hdev->bus == BUS_BLUETOOTH && check_crc) { /* Last 4 bytes contains crc32. */ - uint8_t crc_offset = size - 4; - uint32_t report_crc = get_unaligned_le32(&buf[crc_offset]); + u8 crc_offset = size - 4; + u32 report_crc = get_unaligned_le32(&buf[crc_offset]); if (!ps_check_crc32(PS_FEATURE_CRC32_SEED, buf, crc_offset, report_crc)) { hid_err(hdev, "CRC check failed for reportID=%d\n", report_id); @@ -782,17 +817,20 @@ static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *bu } static int ps_led_register(struct ps_device *ps_dev, struct led_classdev *led, - const struct ps_led_info *led_info) + const struct ps_led_info *led_info) { int ret; if (led_info->name) { - led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL, - "%s:%s:%s", ps_dev->input_dev_name, led_info->color, led_info->name); + led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL, "%s:%s:%s", + ps_dev->input_dev_name, led_info->color, + led_info->name); } else { - /* Backwards compatible mode for hid-sony, but not compliant with LED class spec. */ - led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL, - "%s:%s", ps_dev->input_dev_name, led_info->color); + /* Backwards compatible mode for hid-sony, but not compliant + * with LED class spec. + */ + led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL, "%s:%s", + ps_dev->input_dev_name, led_info->color); } if (!led->name) @@ -816,7 +854,7 @@ static int ps_led_register(struct ps_device *ps_dev, struct led_classdev *led, /* Register a DualSense/DualShock4 RGB lightbar represented by a multicolor LED. */ static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc *lightbar_mc_dev, - int (*brightness_set)(struct led_classdev *, enum led_brightness)) + int (*brightness_set)(struct led_classdev *, enum led_brightness)) { struct hid_device *hdev = ps_dev->hdev; struct mc_subled *mc_led_info; @@ -837,7 +875,7 @@ static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc led_cdev = &lightbar_mc_dev->led_cdev; led_cdev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s:rgb:indicator", - ps_dev->input_dev_name); + ps_dev->input_dev_name); if (!led_cdev->name) return -ENOMEM; led_cdev->brightness = 255; @@ -853,8 +891,8 @@ static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc return 0; } -static struct input_dev *ps_sensors_create(struct hid_device *hdev, int accel_range, int accel_res, - int gyro_range, int gyro_res) +static struct input_dev *ps_sensors_create(struct hid_device *hdev, int accel_range, + int accel_res, int gyro_range, int gyro_res) { struct input_dev *sensors; int ret; @@ -890,8 +928,8 @@ static struct input_dev *ps_sensors_create(struct hid_device *hdev, int accel_ra return sensors; } -static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width, int height, - unsigned int num_contacts) +static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width, + int height, unsigned int num_contacts) { struct input_dev *touchpad; int ret; @@ -918,9 +956,27 @@ static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width, return touchpad; } +static struct input_dev *ps_headset_jack_create(struct hid_device *hdev) +{ + struct input_dev *jack; + int ret; + + jack = ps_allocate_input_dev(hdev, "Headset Jack"); + if (IS_ERR(jack)) + return ERR_CAST(jack); + + input_set_capability(jack, EV_SW, SW_HEADPHONE_INSERT); + input_set_capability(jack, EV_SW, SW_MICROPHONE_INSERT); + + ret = input_register_device(jack); + if (ret) + return ERR_PTR(ret); + + return jack; +} + static ssize_t firmware_version_show(struct device *dev, - struct device_attribute - *attr, char *buf) + struct device_attribute *attr, char *buf) { struct hid_device *hdev = to_hid_device(dev); struct ps_device *ps_dev = hid_get_drvdata(hdev); @@ -931,8 +987,7 @@ static ssize_t firmware_version_show(struct device *dev, static DEVICE_ATTR_RO(firmware_version); static ssize_t hardware_version_show(struct device *dev, - struct device_attribute - *attr, char *buf) + struct device_attribute *attr, char *buf) { struct hid_device *hdev = to_hid_device(dev); struct ps_device *ps_dev = hid_get_drvdata(hdev); @@ -963,14 +1018,14 @@ static int dualsense_get_calibration_data(struct dualsense *ds) int range_2g; int ret = 0; int i; - uint8_t *buf; + u8 *buf; buf = kzalloc(DS_FEATURE_REPORT_CALIBRATION_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_CALIBRATION, buf, - DS_FEATURE_REPORT_CALIBRATION_SIZE, true); + DS_FEATURE_REPORT_CALIBRATION_SIZE, true); if (ret) { hid_err(ds->base.hdev, "Failed to retrieve DualSense calibration info: %d\n", ret); goto err_free; @@ -1001,19 +1056,19 @@ static int dualsense_get_calibration_data(struct dualsense *ds) speed_2x = (gyro_speed_plus + gyro_speed_minus); ds->gyro_calib_data[0].abs_code = ABS_RX; ds->gyro_calib_data[0].bias = 0; - ds->gyro_calib_data[0].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S; + ds->gyro_calib_data[0].sens_numer = speed_2x * DS_GYRO_RES_PER_DEG_S; ds->gyro_calib_data[0].sens_denom = abs(gyro_pitch_plus - gyro_pitch_bias) + abs(gyro_pitch_minus - gyro_pitch_bias); ds->gyro_calib_data[1].abs_code = ABS_RY; ds->gyro_calib_data[1].bias = 0; - ds->gyro_calib_data[1].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S; + ds->gyro_calib_data[1].sens_numer = speed_2x * DS_GYRO_RES_PER_DEG_S; ds->gyro_calib_data[1].sens_denom = abs(gyro_yaw_plus - gyro_yaw_bias) + abs(gyro_yaw_minus - gyro_yaw_bias); ds->gyro_calib_data[2].abs_code = ABS_RZ; ds->gyro_calib_data[2].bias = 0; - ds->gyro_calib_data[2].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S; + ds->gyro_calib_data[2].sens_numer = speed_2x * DS_GYRO_RES_PER_DEG_S; ds->gyro_calib_data[2].sens_denom = abs(gyro_roll_plus - gyro_roll_bias) + abs(gyro_roll_minus - gyro_roll_bias); @@ -1024,8 +1079,9 @@ static int dualsense_get_calibration_data(struct dualsense *ds) */ for (i = 0; i < ARRAY_SIZE(ds->gyro_calib_data); i++) { if (ds->gyro_calib_data[i].sens_denom == 0) { - hid_warn(hdev, "Invalid gyro calibration data for axis (%d), disabling calibration.", - ds->gyro_calib_data[i].abs_code); + hid_warn(hdev, + "Invalid gyro calibration data for axis (%d), disabling calibration.", + ds->gyro_calib_data[i].abs_code); ds->gyro_calib_data[i].bias = 0; ds->gyro_calib_data[i].sens_numer = DS_GYRO_RANGE; ds->gyro_calib_data[i].sens_denom = S16_MAX; @@ -1039,19 +1095,19 @@ static int dualsense_get_calibration_data(struct dualsense *ds) range_2g = acc_x_plus - acc_x_minus; ds->accel_calib_data[0].abs_code = ABS_X; ds->accel_calib_data[0].bias = acc_x_plus - range_2g / 2; - ds->accel_calib_data[0].sens_numer = 2*DS_ACC_RES_PER_G; + ds->accel_calib_data[0].sens_numer = 2 * DS_ACC_RES_PER_G; ds->accel_calib_data[0].sens_denom = range_2g; range_2g = acc_y_plus - acc_y_minus; ds->accel_calib_data[1].abs_code = ABS_Y; ds->accel_calib_data[1].bias = acc_y_plus - range_2g / 2; - ds->accel_calib_data[1].sens_numer = 2*DS_ACC_RES_PER_G; + ds->accel_calib_data[1].sens_numer = 2 * DS_ACC_RES_PER_G; ds->accel_calib_data[1].sens_denom = range_2g; range_2g = acc_z_plus - acc_z_minus; ds->accel_calib_data[2].abs_code = ABS_Z; ds->accel_calib_data[2].bias = acc_z_plus - range_2g / 2; - ds->accel_calib_data[2].sens_numer = 2*DS_ACC_RES_PER_G; + ds->accel_calib_data[2].sens_numer = 2 * DS_ACC_RES_PER_G; ds->accel_calib_data[2].sens_denom = range_2g; /* @@ -1061,8 +1117,9 @@ static int dualsense_get_calibration_data(struct dualsense *ds) */ for (i = 0; i < ARRAY_SIZE(ds->accel_calib_data); i++) { if (ds->accel_calib_data[i].sens_denom == 0) { - hid_warn(hdev, "Invalid accelerometer calibration data for axis (%d), disabling calibration.", - ds->accel_calib_data[i].abs_code); + hid_warn(hdev, + "Invalid accelerometer calibration data for axis (%d), disabling calibration.", + ds->accel_calib_data[i].abs_code); ds->accel_calib_data[i].bias = 0; ds->accel_calib_data[i].sens_numer = DS_ACC_RANGE; ds->accel_calib_data[i].sens_denom = S16_MAX; @@ -1074,10 +1131,9 @@ err_free: return ret; } - static int dualsense_get_firmware_info(struct dualsense *ds) { - uint8_t *buf; + u8 *buf; int ret; buf = kzalloc(DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, GFP_KERNEL); @@ -1085,7 +1141,7 @@ static int dualsense_get_firmware_info(struct dualsense *ds) return -ENOMEM; ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_FIRMWARE_INFO, buf, - DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, true); + DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, true); if (ret) { hid_err(ds->base.hdev, "Failed to retrieve DualSense firmware info: %d\n", ret); goto err_free; @@ -1110,7 +1166,7 @@ err_free: static int dualsense_get_mac_address(struct dualsense *ds) { - uint8_t *buf; + u8 *buf; int ret = 0; buf = kzalloc(DS_FEATURE_REPORT_PAIRING_INFO_SIZE, GFP_KERNEL); @@ -1118,7 +1174,7 @@ static int dualsense_get_mac_address(struct dualsense *ds) return -ENOMEM; ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_PAIRING_INFO, buf, - DS_FEATURE_REPORT_PAIRING_INFO_SIZE, true); + DS_FEATURE_REPORT_PAIRING_INFO_SIZE, true); if (ret) { hid_err(ds->base.hdev, "Failed to retrieve DualSense pairing info: %d\n", ret); goto err_free; @@ -1132,11 +1188,11 @@ err_free: } static int dualsense_lightbar_set_brightness(struct led_classdev *cdev, - enum led_brightness brightness) + enum led_brightness brightness) { struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); struct dualsense *ds = container_of(mc_cdev, struct dualsense, lightbar); - uint8_t red, green, blue; + u8 red, green, blue; led_mc_calc_color_components(mc_cdev, brightness); red = mc_cdev->subled_info[0].brightness; @@ -1159,27 +1215,25 @@ static int dualsense_player_led_set_brightness(struct led_classdev *led, enum le { struct hid_device *hdev = to_hid_device(led->dev->parent); struct dualsense *ds = hid_get_drvdata(hdev); - unsigned long flags; unsigned int led_index; - spin_lock_irqsave(&ds->base.lock, flags); - - led_index = led - ds->player_leds; - if (value == LED_OFF) - ds->player_leds_state &= ~BIT(led_index); - else - ds->player_leds_state |= BIT(led_index); + scoped_guard(spinlock_irqsave, &ds->base.lock) { + led_index = led - ds->player_leds; + if (value == LED_OFF) + ds->player_leds_state &= ~BIT(led_index); + else + ds->player_leds_state |= BIT(led_index); - ds->update_player_leds = true; - spin_unlock_irqrestore(&ds->base.lock, flags); + ds->update_player_leds = true; + } dualsense_schedule_work(ds); return 0; } -static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_output_report *rp, - void *buf) +static void dualsense_init_output_report(struct dualsense *ds, + struct dualsense_output_report *rp, void *buf) { struct hid_device *hdev = ds->base.hdev; @@ -1194,7 +1248,8 @@ static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_ * Highest 4-bit is a sequence number, which needs to be increased * every report. Lowest 4-bit is tag and can be zero for now. */ - bt->seq_tag = (ds->output_seq << 4) | 0x0; + bt->seq_tag = FIELD_PREP(DS_OUTPUT_SEQ_NO, ds->output_seq) | + FIELD_PREP(DS_OUTPUT_SEQ_TAG, 0x0); if (++ds->output_seq == 16) ds->output_seq = 0; @@ -1219,12 +1274,10 @@ static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_ static inline void dualsense_schedule_work(struct dualsense *ds) { - unsigned long flags; - - spin_lock_irqsave(&ds->base.lock, flags); - if (ds->output_worker_initialized) - schedule_work(&ds->output_worker); - spin_unlock_irqrestore(&ds->base.lock, flags); + /* Using scoped_guard() instead of guard() to make sparse happy */ + scoped_guard(spinlock_irqsave, &ds->base.lock) + if (ds->output_worker_initialized) + schedule_work(&ds->output_worker); } /* @@ -1232,14 +1285,14 @@ static inline void dualsense_schedule_work(struct dualsense *ds) * for Bluetooth reports. */ static void dualsense_send_output_report(struct dualsense *ds, - struct dualsense_output_report *report) + struct dualsense_output_report *report) { struct hid_device *hdev = ds->base.hdev; /* Bluetooth packets need to be signed with a CRC in the last 4 bytes. */ if (report->bt) { - uint32_t crc; - uint8_t seed = PS_OUTPUT_CRC32_SEED; + u32 crc; + u8 seed = PS_OUTPUT_CRC32_SEED; crc = crc32_le(0xFFFFFFFF, &seed, 1); crc = ~crc32_le(crc, report->data, report->len - 4); @@ -1255,74 +1308,125 @@ static void dualsense_output_worker(struct work_struct *work) struct dualsense *ds = container_of(work, struct dualsense, output_worker); struct dualsense_output_report report; struct dualsense_output_report_common *common; - unsigned long flags; dualsense_init_output_report(ds, &report, ds->output_report_dmabuf); common = report.common; - spin_lock_irqsave(&ds->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds->base.lock) { + if (ds->update_rumble) { + /* Select classic rumble style haptics and enable it. */ + common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT; + if (ds->use_vibration_v2) + common->valid_flag2 |= DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2; + else + common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION; + common->motor_left = ds->motor_left; + common->motor_right = ds->motor_right; + ds->update_rumble = false; + } - if (ds->update_rumble) { - /* Select classic rumble style haptics and enable it. */ - common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT; - if (ds->use_vibration_v2) - common->valid_flag2 |= DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2; - else - common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION; - common->motor_left = ds->motor_left; - common->motor_right = ds->motor_right; - ds->update_rumble = false; - } + if (ds->update_lightbar) { + common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE; + common->lightbar_red = ds->lightbar_red; + common->lightbar_green = ds->lightbar_green; + common->lightbar_blue = ds->lightbar_blue; - if (ds->update_lightbar) { - common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE; - common->lightbar_red = ds->lightbar_red; - common->lightbar_green = ds->lightbar_green; - common->lightbar_blue = ds->lightbar_blue; + ds->update_lightbar = false; + } - ds->update_lightbar = false; - } + if (ds->update_player_leds) { + common->valid_flag1 |= + DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE; + common->player_leds = ds->player_leds_state; - if (ds->update_player_leds) { - common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE; - common->player_leds = ds->player_leds_state; + ds->update_player_leds = false; + } - ds->update_player_leds = false; - } + if (ds->plugged_state != ds->prev_plugged_state) { + u8 val = ds->plugged_state & DS_STATUS1_HP_DETECT; + + if (val != (ds->prev_plugged_state & DS_STATUS1_HP_DETECT)) { + common->valid_flag0 = DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE; + /* + * _--------> Output path setup in audio_flag0 + * / _------> Headphone (HP) Left channel sink + * | / _----> Headphone (HP) Right channel sink + * | | / _--> Internal Speaker (SP) sink + * | | | / + * | | | | L/R - Left/Right channel source + * 0 L-R X X - Unrouted (muted) channel source + * 1 L-L X + * 2 L-L R + * 3 X-X R + */ + if (val) { + /* Mute SP and route L+R channels to HP */ + common->audio_control = 0; + } else { + /* Mute HP and route R channel to SP */ + common->audio_control = + FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL, + 0x3); + /* + * Set SP hardware volume to 100%. + * Note the accepted range seems to be [0x3d..0x64] + */ + common->valid_flag0 |= + DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE; + common->speaker_volume = 0x64; + /* Set SP preamp gain to +6dB */ + common->valid_flag1 = + DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE; + common->audio_control2 = + FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN, + 0x2); + } - if (ds->update_mic_mute) { - common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE; - common->mute_button_led = ds->mic_muted; + input_report_switch(ds->jack, SW_HEADPHONE_INSERT, val); + } - if (ds->mic_muted) { - /* Disable microphone */ - common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE; - common->power_save_control |= DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE; - } else { - /* Enable microphone */ - common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE; - common->power_save_control &= ~DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE; + val = ds->plugged_state & DS_STATUS1_MIC_DETECT; + if (val != (ds->prev_plugged_state & DS_STATUS1_MIC_DETECT)) + input_report_switch(ds->jack, SW_MICROPHONE_INSERT, val); + + input_sync(ds->jack); + ds->prev_plugged_state = ds->plugged_state; } - ds->update_mic_mute = false; - } + if (ds->update_mic_mute) { + common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE; + common->mute_button_led = ds->mic_muted; + + if (ds->mic_muted) { + /* Disable microphone */ + common->valid_flag1 |= + DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE; + common->power_save_control |= DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE; + } else { + /* Enable microphone */ + common->valid_flag1 |= + DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE; + common->power_save_control &= + ~DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE; + } - spin_unlock_irqrestore(&ds->base.lock, flags); + ds->update_mic_mute = false; + } + } dualsense_send_output_report(ds, &report); } static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *report, - u8 *data, int size) + u8 *data, int size) { struct hid_device *hdev = ps_dev->hdev; struct dualsense *ds = container_of(ps_dev, struct dualsense, base); struct dualsense_input_report *ds_report; - uint8_t battery_data, battery_capacity, charging_status, value; + u8 battery_data, battery_capacity, charging_status, value; int battery_status; - uint32_t sensor_timestamp; + u32 sensor_timestamp; bool btn_mic_state; - unsigned long flags; int i; /* @@ -1331,12 +1435,12 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r * the full report using reportID 49. */ if (hdev->bus == BUS_USB && report->id == DS_INPUT_REPORT_USB && - size == DS_INPUT_REPORT_USB_SIZE) { + size == DS_INPUT_REPORT_USB_SIZE) { ds_report = (struct dualsense_input_report *)&data[1]; } else if (hdev->bus == BUS_BLUETOOTH && report->id == DS_INPUT_REPORT_BT && - size == DS_INPUT_REPORT_BT_SIZE) { + size == DS_INPUT_REPORT_BT_SIZE) { /* Last 4 bytes of input report contain crc32 */ - uint32_t report_crc = get_unaligned_le32(&data[size - 4]); + u32 report_crc = get_unaligned_le32(&data[size - 4]); if (!ps_check_crc32(PS_INPUT_CRC32_SEED, data, size - 4, report_crc)) { hid_err(hdev, "DualSense input CRC's check failed\n"); @@ -1384,16 +1488,42 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r */ btn_mic_state = !!(ds_report->buttons[2] & DS_BUTTONS2_MIC_MUTE); if (btn_mic_state && !ds->last_btn_mic_state) { - spin_lock_irqsave(&ps_dev->lock, flags); - ds->update_mic_mute = true; - ds->mic_muted = !ds->mic_muted; /* toggle */ - spin_unlock_irqrestore(&ps_dev->lock, flags); + scoped_guard(spinlock_irqsave, &ps_dev->lock) { + ds->update_mic_mute = true; + ds->mic_muted = !ds->mic_muted; /* toggle */ + } /* Schedule updating of microphone state at hardware level. */ dualsense_schedule_work(ds); } ds->last_btn_mic_state = btn_mic_state; + /* + * Parse HP/MIC plugged state data for USB use case, since Bluetooth + * audio is currently not supported. + */ + if (hdev->bus == BUS_USB) { + value = ds_report->status[1] & DS_STATUS1_JACK_DETECT; + + if (!ds->prev_plugged_state_valid) { + /* Initial handling of the plugged state report */ + scoped_guard(spinlock_irqsave, &ps_dev->lock) { + ds->plugged_state = (~value) & DS_STATUS1_JACK_DETECT; + ds->prev_plugged_state_valid = true; + } + } + + if (value != ds->plugged_state) { + scoped_guard(spinlock_irqsave, &ps_dev->lock) { + ds->prev_plugged_state = ds->plugged_state; + ds->plugged_state = value; + } + + /* Schedule audio routing towards active endpoint. */ + dualsense_schedule_work(ds); + } + } + /* Parse and calibrate gyroscope data. */ for (i = 0; i < ARRAY_SIZE(ds_report->gyro); i++) { int raw_data = (short)le16_to_cpu(ds_report->gyro[i]); @@ -1419,7 +1549,7 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r ds->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp, 3); ds->sensor_timestamp_initialized = true; } else { - uint32_t delta; + u32 delta; if (ds->prev_sensor_timestamp > sensor_timestamp) delta = (U32_MAX - ds->prev_sensor_timestamp + sensor_timestamp + 1); @@ -1439,19 +1569,18 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r input_mt_report_slot_state(ds->touchpad, MT_TOOL_FINGER, active); if (active) { - int x = (point->x_hi << 8) | point->x_lo; - int y = (point->y_hi << 4) | point->y_lo; - - input_report_abs(ds->touchpad, ABS_MT_POSITION_X, x); - input_report_abs(ds->touchpad, ABS_MT_POSITION_Y, y); + input_report_abs(ds->touchpad, ABS_MT_POSITION_X, + DS_TOUCH_POINT_X(point->x_hi, point->x_lo)); + input_report_abs(ds->touchpad, ABS_MT_POSITION_Y, + DS_TOUCH_POINT_Y(point->y_hi, point->y_lo)); } } input_mt_sync_frame(ds->touchpad); input_report_key(ds->touchpad, BTN_LEFT, ds_report->buttons[2] & DS_BUTTONS2_TOUCHPAD); input_sync(ds->touchpad); - battery_data = ds_report->status & DS_STATUS_BATTERY_CAPACITY; - charging_status = (ds_report->status & DS_STATUS_CHARGING) >> DS_STATUS_CHARGING_SHIFT; + battery_data = FIELD_GET(DS_STATUS0_BATTERY_CAPACITY, ds_report->status[0]); + charging_status = FIELD_GET(DS_STATUS0_CHARGING, ds_report->status[0]); switch (charging_status) { case 0x0: @@ -1481,10 +1610,10 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r battery_status = POWER_SUPPLY_STATUS_UNKNOWN; } - spin_lock_irqsave(&ps_dev->lock, flags); - ps_dev->battery_capacity = battery_capacity; - ps_dev->battery_status = battery_status; - spin_unlock_irqrestore(&ps_dev->lock, flags); + scoped_guard(spinlock_irqsave, &ps_dev->lock) { + ps_dev->battery_capacity = battery_capacity; + ps_dev->battery_status = battery_status; + } return 0; } @@ -1493,16 +1622,15 @@ static int dualsense_play_effect(struct input_dev *dev, void *data, struct ff_ef { struct hid_device *hdev = input_get_drvdata(dev); struct dualsense *ds = hid_get_drvdata(hdev); - unsigned long flags; if (effect->type != FF_RUMBLE) return 0; - spin_lock_irqsave(&ds->base.lock, flags); - ds->update_rumble = true; - ds->motor_left = effect->u.rumble.strong_magnitude / 256; - ds->motor_right = effect->u.rumble.weak_magnitude / 256; - spin_unlock_irqrestore(&ds->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds->base.lock) { + ds->update_rumble = true; + ds->motor_left = effect->u.rumble.strong_magnitude / 256; + ds->motor_right = effect->u.rumble.weak_magnitude / 256; + } dualsense_schedule_work(ds); return 0; @@ -1511,11 +1639,9 @@ static int dualsense_play_effect(struct input_dev *dev, void *data, struct ff_ef static void dualsense_remove(struct ps_device *ps_dev) { struct dualsense *ds = container_of(ps_dev, struct dualsense, base); - unsigned long flags; - spin_lock_irqsave(&ds->base.lock, flags); - ds->output_worker_initialized = false; - spin_unlock_irqrestore(&ds->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds->base.lock) + ds->output_worker_initialized = false; cancel_work_sync(&ds->output_worker); } @@ -1523,9 +1649,9 @@ static void dualsense_remove(struct ps_device *ps_dev) static int dualsense_reset_leds(struct dualsense *ds) { struct dualsense_output_report report; - uint8_t *buf; + struct dualsense_output_report_bt *buf; - buf = kzalloc(sizeof(struct dualsense_output_report_bt), GFP_KERNEL); + buf = kzalloc(sizeof(*buf), GFP_KERNEL); if (!buf) return -ENOMEM; @@ -1545,16 +1671,14 @@ static int dualsense_reset_leds(struct dualsense *ds) return 0; } -static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue) +static void dualsense_set_lightbar(struct dualsense *ds, u8 red, u8 green, u8 blue) { - unsigned long flags; - - spin_lock_irqsave(&ds->base.lock, flags); - ds->update_lightbar = true; - ds->lightbar_red = red; - ds->lightbar_green = green; - ds->lightbar_blue = blue; - spin_unlock_irqrestore(&ds->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds->base.lock) { + ds->update_lightbar = true; + ds->lightbar_red = red; + ds->lightbar_green = green; + ds->lightbar_blue = blue; + } dualsense_schedule_work(ds); } @@ -1575,7 +1699,7 @@ static void dualsense_set_player_leds(struct dualsense *ds) BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0) }; - uint8_t player_id = ds->base.player_id % ARRAY_SIZE(player_ids); + u8 player_id = ds->base.player_id % ARRAY_SIZE(player_ids); ds->update_player_leds = true; ds->player_leds_state = player_ids[player_id]; @@ -1586,7 +1710,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) { struct dualsense *ds; struct ps_device *ps_dev; - uint8_t max_output_report_size; + u8 max_output_report_size; int i, ret; static const struct ps_led_info player_leds_info[] = { @@ -1675,7 +1799,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) ps_dev->input_dev_name = dev_name(&ds->gamepad->dev); ds->sensors = ps_sensors_create(hdev, DS_ACC_RANGE, DS_ACC_RES_PER_G, - DS_GYRO_RANGE, DS_GYRO_RES_PER_DEG_S); + DS_GYRO_RANGE, DS_GYRO_RES_PER_DEG_S); if (IS_ERR(ds->sensors)) { ret = PTR_ERR(ds->sensors); goto err; @@ -1687,6 +1811,15 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) goto err; } + /* Bluetooth audio is currently not supported. */ + if (hdev->bus == BUS_USB) { + ds->jack = ps_headset_jack_create(hdev); + if (IS_ERR(ds->jack)) { + ret = PTR_ERR(ds->jack); + goto err; + } + } + ret = ps_device_register_battery(ps_dev); if (ret) goto err; @@ -1729,7 +1862,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) * can change behavior. */ hid_info(hdev, "Registered DualSense controller hw_version=0x%08x fw_version=0x%08x\n", - ds->base.hw_version, ds->base.fw_version); + ds->base.hw_version, ds->base.fw_version); return &ds->base; @@ -1741,7 +1874,6 @@ err: static void dualshock4_dongle_calibration_work(struct work_struct *work) { struct dualshock4 *ds4 = container_of(work, struct dualshock4, dongle_hotplug_worker); - unsigned long flags; enum dualshock4_dongle_state dongle_state; int ret; @@ -1753,16 +1885,16 @@ static void dualshock4_dongle_calibration_work(struct work_struct *work) * DS4 hotplug is detect from sony_raw_event as any issues * are likely resolved then (the dongle is quite stupid). */ - hid_err(ds4->base.hdev, "DualShock 4 USB dongle: calibration failed, disabling device\n"); + hid_err(ds4->base.hdev, + "DualShock 4 USB dongle: calibration failed, disabling device\n"); dongle_state = DONGLE_DISABLED; } else { hid_info(ds4->base.hdev, "DualShock 4 USB dongle: calibration completed\n"); dongle_state = DONGLE_CONNECTED; } - spin_lock_irqsave(&ds4->base.lock, flags); - ds4->dongle_state = dongle_state; - spin_unlock_irqrestore(&ds4->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds4->base.lock) + ds4->dongle_state = dongle_state; } static int dualshock4_get_calibration_data(struct dualshock4 *ds4) @@ -1779,7 +1911,7 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) int range_2g; int ret = 0; int i; - uint8_t *buf; + u8 *buf; if (ds4->base.hdev->bus == BUS_USB) { int retries; @@ -1798,14 +1930,17 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) */ for (retries = 0; retries < 3; retries++) { ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION, buf, - DS4_FEATURE_REPORT_CALIBRATION_SIZE, true); + DS4_FEATURE_REPORT_CALIBRATION_SIZE, true); if (ret) { if (retries < 2) { - hid_warn(hdev, "Retrying DualShock 4 get calibration report (0x02) request\n"); + hid_warn(hdev, + "Retrying DualShock 4 get calibration report (0x02) request\n"); continue; } - hid_warn(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret); + hid_warn(hdev, + "Failed to retrieve DualShock4 calibration info: %d\n", + ret); ret = -EILSEQ; goto transfer_failed; } else { @@ -1820,7 +1955,7 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) } ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION_BT, buf, - DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, true); + DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, true); if (ret) { hid_warn(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret); @@ -1867,19 +2002,19 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) speed_2x = (gyro_speed_plus + gyro_speed_minus); ds4->gyro_calib_data[0].abs_code = ABS_RX; ds4->gyro_calib_data[0].bias = 0; - ds4->gyro_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S; + ds4->gyro_calib_data[0].sens_numer = speed_2x * DS4_GYRO_RES_PER_DEG_S; ds4->gyro_calib_data[0].sens_denom = abs(gyro_pitch_plus - gyro_pitch_bias) + abs(gyro_pitch_minus - gyro_pitch_bias); ds4->gyro_calib_data[1].abs_code = ABS_RY; ds4->gyro_calib_data[1].bias = 0; - ds4->gyro_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S; + ds4->gyro_calib_data[1].sens_numer = speed_2x * DS4_GYRO_RES_PER_DEG_S; ds4->gyro_calib_data[1].sens_denom = abs(gyro_yaw_plus - gyro_yaw_bias) + abs(gyro_yaw_minus - gyro_yaw_bias); ds4->gyro_calib_data[2].abs_code = ABS_RZ; ds4->gyro_calib_data[2].bias = 0; - ds4->gyro_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S; + ds4->gyro_calib_data[2].sens_numer = speed_2x * DS4_GYRO_RES_PER_DEG_S; ds4->gyro_calib_data[2].sens_denom = abs(gyro_roll_plus - gyro_roll_bias) + abs(gyro_roll_minus - gyro_roll_bias); @@ -1890,19 +2025,19 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) range_2g = acc_x_plus - acc_x_minus; ds4->accel_calib_data[0].abs_code = ABS_X; ds4->accel_calib_data[0].bias = acc_x_plus - range_2g / 2; - ds4->accel_calib_data[0].sens_numer = 2*DS4_ACC_RES_PER_G; + ds4->accel_calib_data[0].sens_numer = 2 * DS4_ACC_RES_PER_G; ds4->accel_calib_data[0].sens_denom = range_2g; range_2g = acc_y_plus - acc_y_minus; ds4->accel_calib_data[1].abs_code = ABS_Y; ds4->accel_calib_data[1].bias = acc_y_plus - range_2g / 2; - ds4->accel_calib_data[1].sens_numer = 2*DS4_ACC_RES_PER_G; + ds4->accel_calib_data[1].sens_numer = 2 * DS4_ACC_RES_PER_G; ds4->accel_calib_data[1].sens_denom = range_2g; range_2g = acc_z_plus - acc_z_minus; ds4->accel_calib_data[2].abs_code = ABS_Z; ds4->accel_calib_data[2].bias = acc_z_plus - range_2g / 2; - ds4->accel_calib_data[2].sens_numer = 2*DS4_ACC_RES_PER_G; + ds4->accel_calib_data[2].sens_numer = 2 * DS4_ACC_RES_PER_G; ds4->accel_calib_data[2].sens_denom = range_2g; transfer_failed: @@ -1914,8 +2049,9 @@ transfer_failed: for (i = 0; i < ARRAY_SIZE(ds4->gyro_calib_data); i++) { if (ds4->gyro_calib_data[i].sens_denom == 0) { ds4->gyro_calib_data[i].abs_code = ABS_RX + i; - hid_warn(hdev, "Invalid gyro calibration data for axis (%d), disabling calibration.", - ds4->gyro_calib_data[i].abs_code); + hid_warn(hdev, + "Invalid gyro calibration data for axis (%d), disabling calibration.", + ds4->gyro_calib_data[i].abs_code); ds4->gyro_calib_data[i].bias = 0; ds4->gyro_calib_data[i].sens_numer = DS4_GYRO_RANGE; ds4->gyro_calib_data[i].sens_denom = S16_MAX; @@ -1930,8 +2066,9 @@ transfer_failed: for (i = 0; i < ARRAY_SIZE(ds4->accel_calib_data); i++) { if (ds4->accel_calib_data[i].sens_denom == 0) { ds4->accel_calib_data[i].abs_code = ABS_X + i; - hid_warn(hdev, "Invalid accelerometer calibration data for axis (%d), disabling calibration.", - ds4->accel_calib_data[i].abs_code); + hid_warn(hdev, + "Invalid accelerometer calibration data for axis (%d), disabling calibration.", + ds4->accel_calib_data[i].abs_code); ds4->accel_calib_data[i].bias = 0; ds4->accel_calib_data[i].sens_numer = DS4_ACC_RANGE; ds4->accel_calib_data[i].sens_denom = S16_MAX; @@ -1943,7 +2080,7 @@ transfer_failed: static int dualshock4_get_firmware_info(struct dualshock4 *ds4) { - uint8_t *buf; + u8 *buf; int ret; buf = kzalloc(DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, GFP_KERNEL); @@ -1954,7 +2091,7 @@ static int dualshock4_get_firmware_info(struct dualshock4 *ds4) * lacks CRC support, so must be disabled in ps_get_report. */ ret = ps_get_report(ds4->base.hdev, DS4_FEATURE_REPORT_FIRMWARE_INFO, buf, - DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, false); + DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, false); if (ret) { hid_err(ds4->base.hdev, "Failed to retrieve DualShock4 firmware info: %d\n", ret); goto err_free; @@ -1971,7 +2108,7 @@ err_free: static int dualshock4_get_mac_address(struct dualshock4 *ds4) { struct hid_device *hdev = ds4->base.hdev; - uint8_t *buf; + u8 *buf; int ret = 0; if (hdev->bus == BUS_USB) { @@ -1980,7 +2117,7 @@ static int dualshock4_get_mac_address(struct dualshock4 *ds4) return -ENOMEM; ret = ps_get_report(hdev, DS4_FEATURE_REPORT_PAIRING_INFO, buf, - DS4_FEATURE_REPORT_PAIRING_INFO_SIZE, false); + DS4_FEATURE_REPORT_PAIRING_INFO_SIZE, false); if (ret) { hid_err(hdev, "Failed to retrieve DualShock4 pairing info: %d\n", ret); goto err_free; @@ -1993,9 +2130,9 @@ static int dualshock4_get_mac_address(struct dualshock4 *ds4) return -EINVAL; ret = sscanf(hdev->uniq, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", - &ds4->base.mac_address[5], &ds4->base.mac_address[4], - &ds4->base.mac_address[3], &ds4->base.mac_address[2], - &ds4->base.mac_address[1], &ds4->base.mac_address[0]); + &ds4->base.mac_address[5], &ds4->base.mac_address[4], + &ds4->base.mac_address[3], &ds4->base.mac_address[2], + &ds4->base.mac_address[1], &ds4->base.mac_address[0]); if (ret != sizeof(ds4->base.mac_address)) return -EINVAL; @@ -2030,28 +2167,27 @@ static enum led_brightness dualshock4_led_get_brightness(struct led_classdev *le } static int dualshock4_led_set_blink(struct led_classdev *led, unsigned long *delay_on, - unsigned long *delay_off) + unsigned long *delay_off) { struct hid_device *hdev = to_hid_device(led->dev->parent); struct dualshock4 *ds4 = hid_get_drvdata(hdev); - unsigned long flags; - spin_lock_irqsave(&ds4->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds4->base.lock) { + if (!*delay_on && !*delay_off) { + /* Default to 1 Hz (50 centiseconds on, 50 centiseconds off). */ + ds4->lightbar_blink_on = 50; + ds4->lightbar_blink_off = 50; + } else { + /* Blink delays in centiseconds. */ + ds4->lightbar_blink_on = min_t(unsigned long, *delay_on / 10, + DS4_LIGHTBAR_MAX_BLINK); + ds4->lightbar_blink_off = min_t(unsigned long, *delay_off / 10, + DS4_LIGHTBAR_MAX_BLINK); + } - if (!*delay_on && !*delay_off) { - /* Default to 1 Hz (50 centiseconds on, 50 centiseconds off). */ - ds4->lightbar_blink_on = 50; - ds4->lightbar_blink_off = 50; - } else { - /* Blink delays in centiseconds. */ - ds4->lightbar_blink_on = min_t(unsigned long, *delay_on/10, DS4_LIGHTBAR_MAX_BLINK); - ds4->lightbar_blink_off = min_t(unsigned long, *delay_off/10, DS4_LIGHTBAR_MAX_BLINK); + ds4->update_lightbar_blink = true; } - ds4->update_lightbar_blink = true; - - spin_unlock_irqrestore(&ds4->base.lock, flags); - dualshock4_schedule_work(ds4); /* Report scaled values back to LED subsystem */ @@ -2065,36 +2201,33 @@ static int dualshock4_led_set_brightness(struct led_classdev *led, enum led_brig { struct hid_device *hdev = to_hid_device(led->dev->parent); struct dualshock4 *ds4 = hid_get_drvdata(hdev); - unsigned long flags; unsigned int led_index; - spin_lock_irqsave(&ds4->base.lock, flags); - - led_index = led - ds4->lightbar_leds; - switch (led_index) { - case 0: - ds4->lightbar_red = value; - break; - case 1: - ds4->lightbar_green = value; - break; - case 2: - ds4->lightbar_blue = value; - break; - case 3: - ds4->lightbar_enabled = !!value; - - /* brightness = 0 also cancels blinking in Linux. */ - if (!ds4->lightbar_enabled) { - ds4->lightbar_blink_off = 0; - ds4->lightbar_blink_on = 0; - ds4->update_lightbar_blink = true; + scoped_guard(spinlock_irqsave, &ds4->base.lock) { + led_index = led - ds4->lightbar_leds; + switch (led_index) { + case 0: + ds4->lightbar_red = value; + break; + case 1: + ds4->lightbar_green = value; + break; + case 2: + ds4->lightbar_blue = value; + break; + case 3: + ds4->lightbar_enabled = !!value; + + /* brightness = 0 also cancels blinking in Linux. */ + if (!ds4->lightbar_enabled) { + ds4->lightbar_blink_off = 0; + ds4->lightbar_blink_on = 0; + ds4->update_lightbar_blink = true; + } } - } - ds4->update_lightbar = true; - - spin_unlock_irqrestore(&ds4->base.lock, flags); + ds4->update_lightbar = true; + } dualshock4_schedule_work(ds4); @@ -2102,7 +2235,7 @@ static int dualshock4_led_set_brightness(struct led_classdev *led, enum led_brig } static void dualshock4_init_output_report(struct dualshock4 *ds4, - struct dualshock4_output_report *rp, void *buf) + struct dualshock4_output_report *rp, void *buf) { struct hid_device *hdev = ds4->base.hdev; @@ -2136,66 +2269,63 @@ static void dualshock4_output_worker(struct work_struct *work) struct dualshock4 *ds4 = container_of(work, struct dualshock4, output_worker); struct dualshock4_output_report report; struct dualshock4_output_report_common *common; - unsigned long flags; dualshock4_init_output_report(ds4, &report, ds4->output_report_dmabuf); common = report.common; - spin_lock_irqsave(&ds4->base.lock, flags); - - /* - * Some 3rd party gamepads expect updates to rumble and lightbar - * together, and setting one may cancel the other. - * - * Let's maximise compatibility by always sending rumble and lightbar - * updates together, even when only one has been scheduled, resulting - * in: - * - * ds4->valid_flag0 >= 0x03 - * - * Hopefully this will maximise compatibility with third-party pads. - * - * Any further update bits, such as 0x04 for lightbar blinking, will - * be or'd on top of this like before. - */ - if (ds4->update_rumble || ds4->update_lightbar) { - ds4->update_rumble = true; /* 0x01 */ - ds4->update_lightbar = true; /* 0x02 */ - } + scoped_guard(spinlock_irqsave, &ds4->base.lock) { + /* + * Some 3rd party gamepads expect updates to rumble and lightbar + * together, and setting one may cancel the other. + * + * Let's maximise compatibility by always sending rumble and lightbar + * updates together, even when only one has been scheduled, resulting + * in: + * + * ds4->valid_flag0 >= 0x03 + * + * Hopefully this will maximise compatibility with third-party pads. + * + * Any further update bits, such as 0x04 for lightbar blinking, will + * be or'd on top of this like before. + */ + if (ds4->update_rumble || ds4->update_lightbar) { + ds4->update_rumble = true; /* 0x01 */ + ds4->update_lightbar = true; /* 0x02 */ + } - if (ds4->update_rumble) { - /* Select classic rumble style haptics and enable it. */ - common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_MOTOR; - common->motor_left = ds4->motor_left; - common->motor_right = ds4->motor_right; - ds4->update_rumble = false; - } + if (ds4->update_rumble) { + /* Select classic rumble style haptics and enable it. */ + common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_MOTOR; + common->motor_left = ds4->motor_left; + common->motor_right = ds4->motor_right; + ds4->update_rumble = false; + } - if (ds4->update_lightbar) { - common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED; - /* Comptabile behavior with hid-sony, which used a dummy global LED to - * allow enabling/disabling the lightbar. The global LED maps to - * lightbar_enabled. - */ - common->lightbar_red = ds4->lightbar_enabled ? ds4->lightbar_red : 0; - common->lightbar_green = ds4->lightbar_enabled ? ds4->lightbar_green : 0; - common->lightbar_blue = ds4->lightbar_enabled ? ds4->lightbar_blue : 0; - ds4->update_lightbar = false; - } + if (ds4->update_lightbar) { + common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED; + /* Compatible behavior with hid-sony, which used a dummy global LED to + * allow enabling/disabling the lightbar. The global LED maps to + * lightbar_enabled. + */ + common->lightbar_red = ds4->lightbar_enabled ? ds4->lightbar_red : 0; + common->lightbar_green = ds4->lightbar_enabled ? ds4->lightbar_green : 0; + common->lightbar_blue = ds4->lightbar_enabled ? ds4->lightbar_blue : 0; + ds4->update_lightbar = false; + } - if (ds4->update_lightbar_blink) { - common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED_BLINK; - common->lightbar_blink_on = ds4->lightbar_blink_on; - common->lightbar_blink_off = ds4->lightbar_blink_off; - ds4->update_lightbar_blink = false; + if (ds4->update_lightbar_blink) { + common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED_BLINK; + common->lightbar_blink_on = ds4->lightbar_blink_on; + common->lightbar_blink_off = ds4->lightbar_blink_off; + ds4->update_lightbar_blink = false; + } } - spin_unlock_irqrestore(&ds4->base.lock, flags); - /* Bluetooth packets need additional flags as well as a CRC in the last 4 bytes. */ if (report.bt) { - uint32_t crc; - uint8_t seed = PS_OUTPUT_CRC32_SEED; + u32 crc; + u8 seed = PS_OUTPUT_CRC32_SEED; /* Hardware control flags need to set to let the device know * there is HID data as well as CRC. @@ -2217,16 +2347,15 @@ static void dualshock4_output_worker(struct work_struct *work) } static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *report, - u8 *data, int size) + u8 *data, int size) { struct hid_device *hdev = ps_dev->hdev; struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base); struct dualshock4_input_report_common *ds4_report; struct dualshock4_touch_report *touch_reports; - uint8_t battery_capacity, num_touch_reports, value; + u8 battery_capacity, num_touch_reports, value; int battery_status, i, j; - uint16_t sensor_timestamp; - unsigned long flags; + u16 sensor_timestamp; bool is_minimal = false; /* @@ -2235,16 +2364,17 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * * the full report using reportID 17. */ if (hdev->bus == BUS_USB && report->id == DS4_INPUT_REPORT_USB && - size == DS4_INPUT_REPORT_USB_SIZE) { - struct dualshock4_input_report_usb *usb = (struct dualshock4_input_report_usb *)data; + size == DS4_INPUT_REPORT_USB_SIZE) { + struct dualshock4_input_report_usb *usb = + (struct dualshock4_input_report_usb *)data; ds4_report = &usb->common; num_touch_reports = usb->num_touch_reports; touch_reports = usb->touch_reports; } else if (hdev->bus == BUS_BLUETOOTH && report->id == DS4_INPUT_REPORT_BT && - size == DS4_INPUT_REPORT_BT_SIZE) { + size == DS4_INPUT_REPORT_BT_SIZE) { struct dualshock4_input_report_bt *bt = (struct dualshock4_input_report_bt *)data; - uint32_t report_crc = get_unaligned_le32(&bt->crc32); + u32 report_crc = get_unaligned_le32(&bt->crc32); /* Last 4 bytes of input report contains CRC. */ if (!ps_check_crc32(PS_INPUT_CRC32_SEED, data, size - 4, report_crc)) { @@ -2325,16 +2455,16 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * /* Convert timestamp (in 5.33us unit) to timestamp_us */ sensor_timestamp = le16_to_cpu(ds4_report->sensor_timestamp); if (!ds4->sensor_timestamp_initialized) { - ds4->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp*16, 3); + ds4->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp * 16, 3); ds4->sensor_timestamp_initialized = true; } else { - uint16_t delta; + u16 delta; if (ds4->prev_sensor_timestamp > sensor_timestamp) delta = (U16_MAX - ds4->prev_sensor_timestamp + sensor_timestamp + 1); else delta = sensor_timestamp - ds4->prev_sensor_timestamp; - ds4->sensor_timestamp_us += DIV_ROUND_CLOSEST(delta*16, 3); + ds4->sensor_timestamp_us += DIV_ROUND_CLOSEST(delta * 16, 3); } ds4->prev_sensor_timestamp = sensor_timestamp; input_event(ds4->sensors, EV_MSC, MSC_TIMESTAMP, ds4->sensor_timestamp_us); @@ -2351,11 +2481,10 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * input_mt_report_slot_state(ds4->touchpad, MT_TOOL_FINGER, active); if (active) { - int x = (point->x_hi << 8) | point->x_lo; - int y = (point->y_hi << 4) | point->y_lo; - - input_report_abs(ds4->touchpad, ABS_MT_POSITION_X, x); - input_report_abs(ds4->touchpad, ABS_MT_POSITION_Y, y); + input_report_abs(ds4->touchpad, ABS_MT_POSITION_X, + DS4_TOUCH_POINT_X(point->x_hi, point->x_lo)); + input_report_abs(ds4->touchpad, ABS_MT_POSITION_Y, + DS4_TOUCH_POINT_Y(point->y_hi, point->y_lo)); } } input_mt_sync_frame(ds4->touchpad); @@ -2374,7 +2503,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * * - 15: charge error */ if (ds4_report->status[0] & DS4_STATUS0_CABLE_STATE) { - uint8_t battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY; + u8 battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY; if (battery_data < 10) { /* Take the mid-point for each battery capacity value, @@ -2395,7 +2524,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * battery_status = POWER_SUPPLY_STATUS_UNKNOWN; } } else { - uint8_t battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY; + u8 battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY; if (battery_data < 10) battery_capacity = battery_data * 10 + 5; @@ -2405,16 +2534,16 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * battery_status = POWER_SUPPLY_STATUS_DISCHARGING; } - spin_lock_irqsave(&ps_dev->lock, flags); - ps_dev->battery_capacity = battery_capacity; - ps_dev->battery_status = battery_status; - spin_unlock_irqrestore(&ps_dev->lock, flags); + scoped_guard(spinlock_irqsave, &ps_dev->lock) { + ps_dev->battery_capacity = battery_capacity; + ps_dev->battery_status = battery_status; + } return 0; } static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_report *report, - u8 *data, int size) + u8 *data, int size) { struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base); bool connected = false; @@ -2425,8 +2554,8 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r * parsing code. */ if (data[0] == DS4_INPUT_REPORT_USB && size == DS4_INPUT_REPORT_USB_SIZE) { - struct dualshock4_input_report_common *ds4_report = (struct dualshock4_input_report_common *)&data[1]; - unsigned long flags; + struct dualshock4_input_report_common *ds4_report = + (struct dualshock4_input_report_common *)&data[1]; connected = ds4_report->status[1] & DS4_STATUS1_DONGLE_STATE ? false : true; @@ -2435,9 +2564,8 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r dualshock4_set_default_lightbar_colors(ds4); - spin_lock_irqsave(&ps_dev->lock, flags); - ds4->dongle_state = DONGLE_CALIBRATING; - spin_unlock_irqrestore(&ps_dev->lock, flags); + scoped_guard(spinlock_irqsave, &ps_dev->lock) + ds4->dongle_state = DONGLE_CALIBRATING; schedule_work(&ds4->dongle_hotplug_worker); @@ -2449,9 +2577,8 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r ds4->dongle_state == DONGLE_DISABLED) && !connected) { hid_info(ps_dev->hdev, "DualShock 4 USB dongle: controller disconnected\n"); - spin_lock_irqsave(&ps_dev->lock, flags); - ds4->dongle_state = DONGLE_DISCONNECTED; - spin_unlock_irqrestore(&ps_dev->lock, flags); + scoped_guard(spinlock_irqsave, &ps_dev->lock) + ds4->dongle_state = DONGLE_DISCONNECTED; /* Return 0, so hidraw can get the report. */ return 0; @@ -2473,16 +2600,15 @@ static int dualshock4_play_effect(struct input_dev *dev, void *data, struct ff_e { struct hid_device *hdev = input_get_drvdata(dev); struct dualshock4 *ds4 = hid_get_drvdata(hdev); - unsigned long flags; if (effect->type != FF_RUMBLE) return 0; - spin_lock_irqsave(&ds4->base.lock, flags); - ds4->update_rumble = true; - ds4->motor_left = effect->u.rumble.strong_magnitude / 256; - ds4->motor_right = effect->u.rumble.weak_magnitude / 256; - spin_unlock_irqrestore(&ds4->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds4->base.lock) { + ds4->update_rumble = true; + ds4->motor_left = effect->u.rumble.strong_magnitude / 256; + ds4->motor_right = effect->u.rumble.weak_magnitude / 256; + } dualshock4_schedule_work(ds4); return 0; @@ -2491,11 +2617,9 @@ static int dualshock4_play_effect(struct input_dev *dev, void *data, struct ff_e static void dualshock4_remove(struct ps_device *ps_dev) { struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base); - unsigned long flags; - spin_lock_irqsave(&ds4->base.lock, flags); - ds4->output_worker_initialized = false; - spin_unlock_irqrestore(&ds4->base.lock, flags); + scoped_guard(spinlock_irqsave, &ds4->base.lock) + ds4->output_worker_initialized = false; cancel_work_sync(&ds4->output_worker); @@ -2505,15 +2629,13 @@ static void dualshock4_remove(struct ps_device *ps_dev) static inline void dualshock4_schedule_work(struct dualshock4 *ds4) { - unsigned long flags; - - spin_lock_irqsave(&ds4->base.lock, flags); - if (ds4->output_worker_initialized) - schedule_work(&ds4->output_worker); - spin_unlock_irqrestore(&ds4->base.lock, flags); + /* Using scoped_guard() instead of guard() to make sparse happy */ + scoped_guard(spinlock_irqsave, &ds4->base.lock) + if (ds4->output_worker_initialized) + schedule_work(&ds4->output_worker); } -static void dualshock4_set_bt_poll_interval(struct dualshock4 *ds4, uint8_t interval) +static void dualshock4_set_bt_poll_interval(struct dualshock4 *ds4, u8 interval) { ds4->bt_poll_interval = interval; ds4->update_bt_poll_interval = true; @@ -2533,7 +2655,7 @@ static void dualshock4_set_default_lightbar_colors(struct dualshock4 *ds4) { 0x20, 0x00, 0x20 } /* Pink */ }; - uint8_t player_id = ds4->base.player_id % ARRAY_SIZE(player_colors); + u8 player_id = ds4->base.player_id % ARRAY_SIZE(player_colors); ds4->lightbar_enabled = true; ds4->lightbar_red = player_colors[player_id][0]; @@ -2548,7 +2670,7 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev) { struct dualshock4 *ds4; struct ps_device *ps_dev; - uint8_t max_output_report_size; + u8 max_output_report_size; int i, ret; /* The DualShock4 has an RGB lightbar, which the original hid-sony driver @@ -2561,11 +2683,14 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev) * existing applications (e.g. Android). Nothing matches against MAC address. */ static const struct ps_led_info lightbar_leds_info[] = { - { NULL, "red", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness }, - { NULL, "green", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness }, - { NULL, "blue", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness }, - { NULL, "global", 1, dualshock4_led_get_brightness, dualshock4_led_set_brightness, - dualshock4_led_set_blink }, + { NULL, "red", 255, dualshock4_led_get_brightness, + dualshock4_led_set_brightness }, + { NULL, "green", 255, dualshock4_led_get_brightness, + dualshock4_led_set_brightness }, + { NULL, "blue", 255, dualshock4_led_get_brightness, + dualshock4_led_set_brightness }, + { NULL, "global", 1, dualshock4_led_get_brightness, + dualshock4_led_set_brightness, dualshock4_led_set_blink }, }; ds4 = devm_kzalloc(&hdev->dev, sizeof(*ds4), GFP_KERNEL); @@ -2635,7 +2760,7 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev) ps_dev->input_dev_name = dev_name(&ds4->gamepad->dev); ds4->sensors = ps_sensors_create(hdev, DS4_ACC_RANGE, DS4_ACC_RES_PER_G, - DS4_GYRO_RANGE, DS4_GYRO_RES_PER_DEG_S); + DS4_GYRO_RANGE, DS4_GYRO_RES_PER_DEG_S); if (IS_ERR(ds4->sensors)) { ret = PTR_ERR(ds4->sensors); goto err; @@ -2674,7 +2799,7 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev) * can change behavior. */ hid_info(hdev, "Registered DualShock4 controller hw_version=0x%08x fw_version=0x%08x\n", - ds4->base.hw_version, ds4->base.fw_version); + ds4->base.hw_version, ds4->base.fw_version); return &ds4->base; err: @@ -2683,7 +2808,7 @@ err: } static int ps_raw_event(struct hid_device *hdev, struct hid_report *report, - u8 *data, int size) + u8 *data, int size) { struct ps_device *dev = hid_get_drvdata(hdev); diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index f619ed10535d..ffd034566e2e 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -695,6 +695,8 @@ static const struct hid_device_id hid_have_special_driver[] = { #endif #if IS_ENABLED(CONFIG_HID_STEELSERIES) { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) }, #endif #if IS_ENABLED(CONFIG_HID_SUNPLUS) { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) }, diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c index d4bd7848b8c6..f98435631aa1 100644 --- a/drivers/hid/hid-steelseries.c +++ b/drivers/hid/hid-steelseries.c @@ -249,11 +249,11 @@ static int steelseries_srws1_probe(struct hid_device *hdev, { int ret, i; struct led_classdev *led; + struct steelseries_srws1_data *drv_data; size_t name_sz; char *name; - struct steelseries_srws1_data *drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL); - + drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL); if (drv_data == NULL) { hid_err(hdev, "can't alloc SRW-S1 memory\n"); return -ENOMEM; @@ -264,18 +264,18 @@ static int steelseries_srws1_probe(struct hid_device *hdev, ret = hid_parse(hdev); if (ret) { hid_err(hdev, "parse failed\n"); - goto err_free; + goto err; } if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 16)) { ret = -ENODEV; - goto err_free; + goto err; } ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "hw start failed\n"); - goto err_free; + goto err; } /* register led subsystem */ @@ -288,10 +288,10 @@ static int steelseries_srws1_probe(struct hid_device *hdev, name_sz = strlen(hdev->uniq) + 16; /* 'ALL', for setting all LEDs simultaneously */ - led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); + led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL); if (!led) { hid_err(hdev, "can't allocate memory for LED ALL\n"); - goto err_led; + goto out; } name = (void *)(&led[1]); @@ -303,16 +303,18 @@ static int steelseries_srws1_probe(struct hid_device *hdev, led->brightness_set = steelseries_srws1_led_all_set_brightness; drv_data->led[SRWS1_NUMBER_LEDS] = led; - ret = led_classdev_register(&hdev->dev, led); - if (ret) - goto err_led; + ret = devm_led_classdev_register(&hdev->dev, led); + if (ret) { + hid_err(hdev, "failed to register LED %d. Aborting.\n", SRWS1_NUMBER_LEDS); + goto out; /* let the driver continue without LEDs */ + } /* Each individual LED */ for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { - led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); + led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL); if (!led) { hid_err(hdev, "can't allocate memory for LED %d\n", i); - goto err_led; + break; } name = (void *)(&led[1]); @@ -324,53 +326,18 @@ static int steelseries_srws1_probe(struct hid_device *hdev, led->brightness_set = steelseries_srws1_led_set_brightness; drv_data->led[i] = led; - ret = led_classdev_register(&hdev->dev, led); + ret = devm_led_classdev_register(&hdev->dev, led); if (ret) { hid_err(hdev, "failed to register LED %d. Aborting.\n", i); -err_led: - /* Deregister all LEDs (if any) */ - for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { - led = drv_data->led[i]; - drv_data->led[i] = NULL; - if (!led) - continue; - led_classdev_unregister(led); - kfree(led); - } - goto out; /* but let the driver continue without LEDs */ + break; /* but let the driver continue without LEDs */ } } out: return 0; -err_free: - kfree(drv_data); +err: return ret; } - -static void steelseries_srws1_remove(struct hid_device *hdev) -{ - int i; - struct led_classdev *led; - - struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev); - - if (drv_data) { - /* Deregister LEDs (if any) */ - for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { - led = drv_data->led[i]; - drv_data->led[i] = NULL; - if (!led) - continue; - led_classdev_unregister(led); - kfree(led); - } - - } - - hid_hw_stop(hdev); - kfree(drv_data); -} #endif #define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000 @@ -405,13 +372,12 @@ static int steelseries_headset_request_battery(struct hid_device *hdev, static void steelseries_headset_fetch_battery(struct hid_device *hdev) { - struct steelseries_device *sd = hid_get_drvdata(hdev); int ret = 0; - if (sd->quirks & STEELSERIES_ARCTIS_1) + if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) ret = steelseries_headset_request_battery(hdev, arctis_1_battery_request, sizeof(arctis_1_battery_request)); - else if (sd->quirks & STEELSERIES_ARCTIS_9) + else if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) ret = steelseries_headset_request_battery(hdev, arctis_9_battery_request, sizeof(arctis_9_battery_request)); @@ -567,14 +533,7 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id struct steelseries_device *sd; int ret; - sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL); - if (!sd) - return -ENOMEM; - hid_set_drvdata(hdev, sd); - sd->hdev = hdev; - sd->quirks = id->driver_data; - - if (sd->quirks & STEELSERIES_SRWS1) { + if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) { #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) return steelseries_srws1_probe(hdev, id); @@ -583,6 +542,13 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id #endif } + sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL); + if (!sd) + return -ENOMEM; + hid_set_drvdata(hdev, sd); + sd->hdev = hdev; + sd->quirks = id->driver_data; + ret = hid_parse(hdev); if (ret) return ret; @@ -610,17 +576,19 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id static void steelseries_remove(struct hid_device *hdev) { - struct steelseries_device *sd = hid_get_drvdata(hdev); + struct steelseries_device *sd; unsigned long flags; - if (sd->quirks & STEELSERIES_SRWS1) { + if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) { #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) - steelseries_srws1_remove(hdev); + hid_hw_stop(hdev); #endif return; } + sd = hid_get_drvdata(hdev); + spin_lock_irqsave(&sd->lock, flags); sd->removed = true; spin_unlock_irqrestore(&sd->lock, flags); @@ -667,10 +635,10 @@ static int steelseries_headset_raw_event(struct hid_device *hdev, unsigned long flags; /* Not a headset */ - if (sd->quirks & STEELSERIES_SRWS1) + if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) return 0; - if (sd->quirks & STEELSERIES_ARCTIS_1) { + if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) { hid_dbg(sd->hdev, "Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf); if (size < ARCTIS_1_BATTERY_RESPONSE_LEN || @@ -688,7 +656,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev, } } - if (sd->quirks & STEELSERIES_ARCTIS_9) { + if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) { hid_dbg(sd->hdev, "Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf); if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) { @@ -757,11 +725,11 @@ static const struct hid_device_id steelseries_devices[] = { .driver_data = STEELSERIES_SRWS1 }, { /* SteelSeries Arctis 1 Wireless for XBox */ - HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 0x12b6), - .driver_data = STEELSERIES_ARCTIS_1 }, + HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1), + .driver_data = STEELSERIES_ARCTIS_1 }, { /* SteelSeries Arctis 9 Wireless for XBox */ - HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 0x12c2), + HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9), .driver_data = STEELSERIES_ARCTIS_9 }, { } diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 4a17f7332c3f..ffa14a4621ef 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -20,6 +20,7 @@ #include <linux/ctype.h> #include <linux/string.h> #include <linux/unaligned.h> +#include <linux/string_choices.h> /** * uclogic_params_pen_inrange_to_str() - Convert a pen in-range reporting type @@ -59,7 +60,7 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev, size_t i; hid_dbg(hdev, "\t.usage_invalid = %s\n", - (pen->usage_invalid ? "true" : "false")); + str_true_false(pen->usage_invalid)); hid_dbg(hdev, "\t.desc_ptr = %p\n", pen->desc_ptr); hid_dbg(hdev, "\t.desc_size = %u\n", pen->desc_size); hid_dbg(hdev, "\t.id = %u\n", pen->id); @@ -74,9 +75,9 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev, hid_dbg(hdev, "\t.inrange = %s\n", uclogic_params_pen_inrange_to_str(pen->inrange)); hid_dbg(hdev, "\t.fragmented_hires = %s\n", - (pen->fragmented_hires ? "true" : "false")); + str_true_false(pen->fragmented_hires)); hid_dbg(hdev, "\t.tilt_y_flipped = %s\n", - (pen->tilt_y_flipped ? "true" : "false")); + str_true_false(pen->tilt_y_flipped)); } /** @@ -119,8 +120,7 @@ void uclogic_params_hid_dbg(const struct hid_device *hdev, { size_t i; - hid_dbg(hdev, ".invalid = %s\n", - params->invalid ? "true" : "false"); + hid_dbg(hdev, ".invalid = %s\n", str_true_false(params->invalid)); hid_dbg(hdev, ".desc_ptr = %p\n", params->desc_ptr); hid_dbg(hdev, ".desc_size = %u\n", params->desc_size); hid_dbg(hdev, ".pen = {\n"); diff --git a/drivers/hid/hid-universal-pidff.c b/drivers/hid/hid-universal-pidff.c index 554a6559aeb7..549dac555d40 100644 --- a/drivers/hid/hid-universal-pidff.c +++ b/drivers/hid/hid-universal-pidff.c @@ -8,12 +8,12 @@ * Copyright (c) 2024, 2025 Tomasz PakuÅ‚a */ +#include "hid-ids.h" +#include "usbhid/hid-pidff.h" #include <linux/device.h> #include <linux/hid.h> -#include <linux/module.h> #include <linux/input-event-codes.h> -#include "hid-ids.h" -#include "usbhid/hid-pidff.h" +#include <linux/module.h> #define JOY_RANGE (BTN_DEAD - BTN_JOYSTICK + 1) @@ -21,8 +21,10 @@ * Map buttons manually to extend the default joystick button limit */ static int universal_pidff_input_mapping(struct hid_device *hdev, - struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, - unsigned long **bit, int *max) + struct hid_input *hi, + struct hid_field *field, + struct hid_usage *usage, + unsigned long **bit, int *max) { if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON) return 0; @@ -126,65 +128,64 @@ static int universal_pidff_input_configured(struct hid_device *hdev, if (!test_bit(axis, input->absbit)) continue; - input_set_abs_params(input, axis, - input->absinfo[axis].minimum, - input->absinfo[axis].maximum, - axis == ABS_X ? 0 : 8, 0); + input_set_abs_params(input, axis, input->absinfo[axis].minimum, + input->absinfo[axis].maximum, + axis == ABS_X ? 0 : 8, 0); } /* Remove fuzz and deadzone from the second joystick axis */ if (hdev->vendor == USB_VENDOR_ID_FFBEAST && hdev->product == USB_DEVICE_ID_FFBEAST_JOYSTICK) input_set_abs_params(input, ABS_Y, - input->absinfo[ABS_Y].minimum, - input->absinfo[ABS_Y].maximum, 0, 0); + input->absinfo[ABS_Y].minimum, + input->absinfo[ABS_Y].maximum, 0, 0); return 0; } static const struct hid_device_id universal_pidff_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3), - .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3_2), - .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5), - .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5_2), - .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9), - .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9_2), - .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12), - .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12_2), - .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21), - .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21_2), - .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, + .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C5) }, { HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C12) }, { HID_USB_DEVICE(USB_VENDOR_ID_VRS, USB_DEVICE_ID_VRS_DFP), - .driver_data = HID_PIDFF_QUIRK_PERMISSIVE_CONTROL }, + .driver_data = HID_PIDFF_QUIRK_PERMISSIVE_CONTROL }, { HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_JOYSTICK), }, { HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_RUDDER), }, { HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_WHEEL) }, { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V10), - .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, + .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12), - .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, + .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE), - .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, + .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE_2), - .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, + .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_LITE_STAR_GT987), - .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, + .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY }, { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_INVICTA) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_FORTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_LA_PRIMA) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_TONY_KANAAN) }, - { } + {} }; MODULE_DEVICE_TABLE(hid, universal_pidff_devices); diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index c887f48756f4..bbd6f23bce78 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -394,27 +394,15 @@ static int hidraw_revoke(struct hidraw_list *list) return 0; } -static long hidraw_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) +static long hidraw_fixed_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd, + void __user *arg) { - struct inode *inode = file_inode(file); - unsigned int minor = iminor(inode); - long ret = 0; - struct hidraw *dev; - struct hidraw_list *list = file->private_data; - void __user *user_arg = (void __user*) arg; - - down_read(&minors_rwsem); - dev = hidraw_table[minor]; - if (!dev || !dev->exist || hidraw_is_revoked(list)) { - ret = -ENODEV; - goto out; - } + struct hid_device *hid = dev->hid; switch (cmd) { case HIDIOCGRDESCSIZE: - if (put_user(dev->hid->rsize, (int __user *)arg)) - ret = -EFAULT; + if (put_user(hid->rsize, (int __user *)arg)) + return -EFAULT; break; case HIDIOCGRDESC: @@ -422,113 +410,145 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd, __u32 len; if (get_user(len, (int __user *)arg)) - ret = -EFAULT; - else if (len > HID_MAX_DESCRIPTOR_SIZE - 1) - ret = -EINVAL; - else if (copy_to_user(user_arg + offsetof( - struct hidraw_report_descriptor, - value[0]), - dev->hid->rdesc, - min(dev->hid->rsize, len))) - ret = -EFAULT; + return -EFAULT; + + if (len > HID_MAX_DESCRIPTOR_SIZE - 1) + return -EINVAL; + + if (copy_to_user(arg + offsetof( + struct hidraw_report_descriptor, + value[0]), + hid->rdesc, + min(hid->rsize, len))) + return -EFAULT; + break; } case HIDIOCGRAWINFO: { struct hidraw_devinfo dinfo; - dinfo.bustype = dev->hid->bus; - dinfo.vendor = dev->hid->vendor; - dinfo.product = dev->hid->product; - if (copy_to_user(user_arg, &dinfo, sizeof(dinfo))) - ret = -EFAULT; + dinfo.bustype = hid->bus; + dinfo.vendor = hid->vendor; + dinfo.product = hid->product; + if (copy_to_user(arg, &dinfo, sizeof(dinfo))) + return -EFAULT; break; } case HIDIOCREVOKE: { - if (user_arg) - ret = -EINVAL; - else - ret = hidraw_revoke(list); - break; + struct hidraw_list *list = file->private_data; + + if (arg) + return -EINVAL; + + return hidraw_revoke(list); } default: - { - struct hid_device *hid = dev->hid; - if (_IOC_TYPE(cmd) != 'H') { - ret = -EINVAL; - break; - } + /* + * None of the above ioctls can return -EAGAIN, so + * use it as a marker that we need to check variable + * length ioctls. + */ + return -EAGAIN; + } - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) { - int len = _IOC_SIZE(cmd); - ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); - break; - } - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) { - int len = _IOC_SIZE(cmd); - ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); - break; - } + return 0; +} - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSINPUT(0))) { - int len = _IOC_SIZE(cmd); - ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT); - break; - } - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGINPUT(0))) { - int len = _IOC_SIZE(cmd); - ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT); - break; - } +static long hidraw_rw_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd, + void __user *user_arg) +{ + int len = _IOC_SIZE(cmd); + + switch (cmd & ~IOCSIZE_MASK) { + case HIDIOCSFEATURE(0): + return hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); + case HIDIOCGFEATURE(0): + return hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); + case HIDIOCSINPUT(0): + return hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT); + case HIDIOCGINPUT(0): + return hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT); + case HIDIOCSOUTPUT(0): + return hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT); + case HIDIOCGOUTPUT(0): + return hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT); + } - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSOUTPUT(0))) { - int len = _IOC_SIZE(cmd); - ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT); - break; - } - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGOUTPUT(0))) { - int len = _IOC_SIZE(cmd); - ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT); - break; - } + return -EINVAL; +} - /* Begin Read-only ioctls. */ - if (_IOC_DIR(cmd) != _IOC_READ) { - ret = -EINVAL; - break; - } +static long hidraw_ro_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd, + void __user *user_arg) +{ + struct hid_device *hid = dev->hid; + int len = _IOC_SIZE(cmd); + int field_len; + + switch (cmd & ~IOCSIZE_MASK) { + case HIDIOCGRAWNAME(0): + field_len = strlen(hid->name) + 1; + if (len > field_len) + len = field_len; + return copy_to_user(user_arg, hid->name, len) ? -EFAULT : len; + case HIDIOCGRAWPHYS(0): + field_len = strlen(hid->phys) + 1; + if (len > field_len) + len = field_len; + return copy_to_user(user_arg, hid->phys, len) ? -EFAULT : len; + case HIDIOCGRAWUNIQ(0): + field_len = strlen(hid->uniq) + 1; + if (len > field_len) + len = field_len; + return copy_to_user(user_arg, hid->uniq, len) ? -EFAULT : len; + } - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) { - int len = strlen(hid->name) + 1; - if (len > _IOC_SIZE(cmd)) - len = _IOC_SIZE(cmd); - ret = copy_to_user(user_arg, hid->name, len) ? - -EFAULT : len; - break; - } + return -EINVAL; +} - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) { - int len = strlen(hid->phys) + 1; - if (len > _IOC_SIZE(cmd)) - len = _IOC_SIZE(cmd); - ret = copy_to_user(user_arg, hid->phys, len) ? - -EFAULT : len; - break; - } +static long hidraw_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct inode *inode = file_inode(file); + unsigned int minor = iminor(inode); + struct hidraw *dev; + struct hidraw_list *list = file->private_data; + void __user *user_arg = (void __user *)arg; + int ret; - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWUNIQ(0))) { - int len = strlen(hid->uniq) + 1; - if (len > _IOC_SIZE(cmd)) - len = _IOC_SIZE(cmd); - ret = copy_to_user(user_arg, hid->uniq, len) ? - -EFAULT : len; - break; - } - } + down_read(&minors_rwsem); + dev = hidraw_table[minor]; + if (!dev || !dev->exist || hidraw_is_revoked(list)) { + ret = -ENODEV; + goto out; + } + + if (_IOC_TYPE(cmd) != 'H') { + ret = -EINVAL; + goto out; + } + if (_IOC_NR(cmd) > HIDIOCTL_LAST || _IOC_NR(cmd) == 0) { ret = -ENOTTY; + goto out; } + + ret = hidraw_fixed_size_ioctl(file, dev, cmd, user_arg); + if (ret != -EAGAIN) + goto out; + + switch (_IOC_DIR(cmd)) { + case (_IOC_READ | _IOC_WRITE): + ret = hidraw_rw_variable_size_ioctl(file, dev, cmd, user_arg); + break; + case _IOC_READ: + ret = hidraw_ro_variable_size_ioctl(file, dev, cmd, user_arg); + break; + default: + /* Any other IOC_DIR is wrong */ + ret = -EINVAL; + } + out: up_read(&minors_rwsem); return ret; diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c index 1b49243adb16..abd700a101f4 100644 --- a/drivers/hid/i2c-hid/i2c-hid-acpi.c +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c @@ -76,6 +76,13 @@ static int i2c_hid_acpi_get_descriptor(struct i2c_hid_acpi *ihid_acpi) return hid_descriptor_address; } +static void i2c_hid_acpi_restore_sequence(struct i2chid_ops *ops) +{ + struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops); + + i2c_hid_acpi_get_descriptor(ihid_acpi); +} + static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops) { struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops); @@ -96,6 +103,7 @@ static int i2c_hid_acpi_probe(struct i2c_client *client) ihid_acpi->adev = ACPI_COMPANION(dev); ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail; + ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence; ret = i2c_hid_acpi_get_descriptor(ihid_acpi); if (ret < 0) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 30ebde1273be..63f46a2e5788 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -961,6 +961,14 @@ static void i2c_hid_core_shutdown_tail(struct i2c_hid *ihid) ihid->ops->shutdown_tail(ihid->ops); } +static void i2c_hid_core_restore_sequence(struct i2c_hid *ihid) +{ + if (!ihid->ops->restore_sequence) + return; + + ihid->ops->restore_sequence(ihid->ops); +} + static int i2c_hid_core_suspend(struct i2c_hid *ihid, bool force_poweroff) { struct i2c_client *client = ihid->client; @@ -1370,8 +1378,26 @@ static int i2c_hid_core_pm_resume(struct device *dev) return i2c_hid_core_resume(ihid); } +static int i2c_hid_core_pm_restore(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_hid *ihid = i2c_get_clientdata(client); + + if (ihid->is_panel_follower) + return 0; + + i2c_hid_core_restore_sequence(ihid); + + return i2c_hid_core_resume(ihid); +} + const struct dev_pm_ops i2c_hid_core_pm = { - SYSTEM_SLEEP_PM_OPS(i2c_hid_core_pm_suspend, i2c_hid_core_pm_resume) + .suspend = pm_sleep_ptr(i2c_hid_core_pm_suspend), + .resume = pm_sleep_ptr(i2c_hid_core_pm_resume), + .freeze = pm_sleep_ptr(i2c_hid_core_pm_suspend), + .thaw = pm_sleep_ptr(i2c_hid_core_pm_resume), + .poweroff = pm_sleep_ptr(i2c_hid_core_pm_suspend), + .restore = pm_sleep_ptr(i2c_hid_core_pm_restore), }; EXPORT_SYMBOL_GPL(i2c_hid_core_pm); diff --git a/drivers/hid/i2c-hid/i2c-hid.h b/drivers/hid/i2c-hid/i2c-hid.h index 2c7b66d5caa0..1724a435c783 100644 --- a/drivers/hid/i2c-hid/i2c-hid.h +++ b/drivers/hid/i2c-hid/i2c-hid.h @@ -27,11 +27,13 @@ static inline u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product) * @power_up: do sequencing to power up the device. * @power_down: do sequencing to power down the device. * @shutdown_tail: called at the end of shutdown. + * @restore_sequence: hibernation restore sequence. */ struct i2chid_ops { int (*power_up)(struct i2chid_ops *ops); void (*power_down)(struct i2chid_ops *ops); void (*shutdown_tail)(struct i2chid_ops *ops); + void (*restore_sequence)(struct i2chid_ops *ops); }; int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops, diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c index 4c861119e97a..3ddaa2cd39d5 100644 --- a/drivers/hid/intel-ish-hid/ipc/ipc.c +++ b/drivers/hid/intel-ish-hid/ipc/ipc.c @@ -498,6 +498,7 @@ static int ish_fw_reset_handler(struct ishtp_device *dev) { uint32_t reset_id; unsigned long flags; + int ret; /* Read reset ID */ reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF; @@ -510,12 +511,11 @@ static int ish_fw_reset_handler(struct ishtp_device *dev) /* ISHTP notification in IPC_RESET */ ishtp_reset_handler(dev); - if (!ish_is_input_ready(dev)) - timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY, - TIME_SLICE_FOR_INPUT_RDY_MS, TIMEOUT_FOR_INPUT_RDY_MS); - + ret = timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY, + TIME_SLICE_FOR_INPUT_RDY_MS, + TIMEOUT_FOR_INPUT_RDY_MS); /* ISH FW is dead */ - if (!ish_is_input_ready(dev)) + if (ret) return -EPIPE; /* Send clock sync at once after reset */ @@ -531,9 +531,10 @@ static int ish_fw_reset_handler(struct ishtp_device *dev) sizeof(uint32_t)); /* Wait for ISH FW'es ILUP and ISHTP_READY */ - timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY, - TIME_SLICE_FOR_FW_RDY_MS, TIMEOUT_FOR_FW_RDY_MS); - if (!ishtp_fw_is_ready(dev)) { + ret = timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY, + TIME_SLICE_FOR_FW_RDY_MS, + TIMEOUT_FOR_FW_RDY_MS); + if (ret) { /* ISH FW is dead */ uint32_t ish_status; diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c index a2643ae790d6..8433a991e7f4 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -23,6 +23,7 @@ static struct quicki2c_ddata ptl_ddata = { .max_detect_size = MAX_RX_DETECT_SIZE_PTL, + .max_interrupt_delay = MAX_RX_INTERRUPT_DELAY, }; /* THC QuickI2C ACPI method to get device properties */ @@ -200,6 +201,21 @@ static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev) return -EOPNOTSUPP; } + if (qcdev->ddata) { + qcdev->i2c_max_frame_size_enable = i2c_config.FSEN; + qcdev->i2c_int_delay_enable = i2c_config.INDE; + + if (i2c_config.FSVL <= qcdev->ddata->max_detect_size) + qcdev->i2c_max_frame_size = i2c_config.FSVL; + else + qcdev->i2c_max_frame_size = qcdev->ddata->max_detect_size; + + if (i2c_config.INDV <= qcdev->ddata->max_interrupt_delay) + qcdev->i2c_int_delay = i2c_config.INDV; + else + qcdev->i2c_int_delay = qcdev->ddata->max_interrupt_delay; + } + return 0; } @@ -441,17 +457,24 @@ static void quicki2c_dma_adv_enable(struct quicki2c_device *qcdev) * max input length <= THC detect capability, enable the feature with device * max input length. */ - if (qcdev->ddata->max_detect_size >= - le16_to_cpu(qcdev->dev_desc.max_input_len)) { - thc_i2c_set_rx_max_size(qcdev->thc_hw, - le16_to_cpu(qcdev->dev_desc.max_input_len)); + if (qcdev->i2c_max_frame_size_enable) { + if (qcdev->i2c_max_frame_size >= + le16_to_cpu(qcdev->dev_desc.max_input_len)) { + thc_i2c_set_rx_max_size(qcdev->thc_hw, + le16_to_cpu(qcdev->dev_desc.max_input_len)); + } else { + dev_warn(qcdev->dev, + "Max frame size is smaller than hid max input length!"); + thc_i2c_set_rx_max_size(qcdev->thc_hw, + le16_to_cpu(qcdev->i2c_max_frame_size)); + } thc_i2c_rx_max_size_enable(qcdev->thc_hw, true); } /* If platform supports interrupt delay feature, enable it with given delay */ - if (qcdev->ddata->interrupt_delay) { + if (qcdev->i2c_int_delay_enable) { thc_i2c_set_rx_int_delay(qcdev->thc_hw, - qcdev->ddata->interrupt_delay); + qcdev->i2c_int_delay * 10); thc_i2c_rx_int_delay_enable(qcdev->thc_hw, true); } } @@ -464,10 +487,10 @@ static void quicki2c_dma_adv_enable(struct quicki2c_device *qcdev) */ static void quicki2c_dma_adv_disable(struct quicki2c_device *qcdev) { - if (qcdev->ddata->max_detect_size) + if (qcdev->i2c_max_frame_size_enable) thc_i2c_rx_max_size_enable(qcdev->thc_hw, false); - if (qcdev->ddata->interrupt_delay) + if (qcdev->i2c_int_delay_enable) thc_i2c_rx_int_delay_enable(qcdev->thc_hw, false); } diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h index 4e60a7de4727..2cb5471a8133 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h @@ -40,6 +40,8 @@ /* PTL Max packet size detection capability is 255 Bytes */ #define MAX_RX_DETECT_SIZE_PTL 255 +/* Max interrupt delay capability is 2.56ms */ +#define MAX_RX_INTERRUPT_DELAY 256 /* Default interrupt delay is 1ms, suitable for most devices */ #define DEFAULT_INTERRUPT_DELAY_US (1 * USEC_PER_MSEC) @@ -103,6 +105,10 @@ struct quicki2c_subip_acpi_parameter { * @HMTD: High Speed Mode Plus (3.4Mbits/sec) Serial Data Line Transmit HOLD Period * @HMRD: High Speed Mode Plus (3.4Mbits/sec) Serial Data Line Receive HOLD Period * @HMSL: Maximum length (in ic_clk_cycles) of suppressed spikes in High Speed Mode + * @FSEN: Maximum Frame Size Feature Enable Control + * @FSVL: Maximum Frame Size Value (unit in Bytes) + * @INDE: Interrupt Delay Feature Enable Control + * @INDV: Interrupt Delay Value (unit in 10 us) * * Those properties get from QUICKI2C_ACPI_METHOD_NAME_ISUB method, used for * I2C timing configure. @@ -129,17 +135,22 @@ struct quicki2c_subip_acpi_config { u64 HMTD; u64 HMRD; u64 HMSL; + + u64 FSEN; + u64 FSVL; + u64 INDE; + u64 INDV; u8 reserved; }; /** * struct quicki2c_ddata - Driver specific data for quicki2c device * @max_detect_size: Identify max packet size detect for rx - * @interrupt_delay: Identify interrupt detect delay for rx + * @interrupt_delay: Identify max interrupt detect delay for rx */ struct quicki2c_ddata { u32 max_detect_size; - u32 interrupt_delay; + u32 max_interrupt_delay; }; struct device; @@ -172,6 +183,10 @@ struct acpi_device; * @report_len: The length of input/output report packet * @reset_ack_wq: Workqueue for waiting reset response from device * @reset_ack: Indicate reset response received or not + * @i2c_max_frame_size_enable: Indicate max frame size feature enabled or not + * @i2c_max_frame_size: Max RX frame size (unit in Bytes) + * @i2c_int_delay_enable: Indicate interrupt delay feature enabled or not + * @i2c_int_delay: Interrupt detection delay value (unit in 10 us) */ struct quicki2c_device { struct device *dev; @@ -202,6 +217,11 @@ struct quicki2c_device { wait_queue_head_t reset_ack_wq; bool reset_ack; + + u32 i2c_max_frame_size_enable; + u32 i2c_max_frame_size; + u32 i2c_int_delay_enable; + u32 i2c_int_delay; }; #endif /* _QUICKI2C_DEV_H_ */ diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c index e1cb9b117ebc..636a68306501 100644 --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c @@ -4,6 +4,7 @@ #include <linux/bitfield.h> #include <linux/math.h> #include <linux/regmap.h> +#include <linux/string_choices.h> #include "intel-thc-dev.h" #include "intel-thc-hw.h" @@ -664,7 +665,7 @@ int thc_interrupt_quiesce(const struct thc_device *dev, bool int_quiesce) if (ret) { dev_err_once(dev->dev, "Timeout while waiting THC idle, target quiesce state = %s\n", - int_quiesce ? "true" : "false"); + str_true_false(int_quiesce)); return ret; } diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index 614a20b62023..edd61ef50e16 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -9,12 +9,11 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include "hid-pidff.h" +#include <linux/hid.h> #include <linux/input.h> +#include <linux/minmax.h> #include <linux/slab.h> #include <linux/usb.h> -#include <linux/hid.h> -#include <linux/minmax.h> - #define PID_EFFECTS_MAX 64 #define PID_INFINITE U16_MAX @@ -33,7 +32,7 @@ #define PID_DEVICE_CONTROL 6 #define PID_CREATE_NEW_EFFECT 7 -#define PID_REQUIRED_REPORTS 7 +#define PID_REQUIRED_REPORTS 8 #define PID_SET_ENVELOPE 8 #define PID_SET_CONDITION 9 @@ -51,6 +50,7 @@ static const u8 pidff_reports[] = { /* PID special fields */ #define PID_EFFECT_TYPE 0x25 +#define PID_AXES_ENABLE 0x55 #define PID_DIRECTION 0x57 #define PID_EFFECT_OPERATION_ARRAY 0x78 #define PID_BLOCK_LOAD_STATUS 0x8b @@ -141,37 +141,74 @@ static const u8 pidff_effect_types[] = { #define PID_BLOCK_LOAD_SUCCESS 0 #define PID_BLOCK_LOAD_FULL 1 #define PID_BLOCK_LOAD_ERROR 2 -static const u8 pidff_block_load_status[] = { 0x8c, 0x8d, 0x8e}; +static const u8 pidff_block_load_status[] = { 0x8c, 0x8d, 0x8e }; #define PID_EFFECT_START 0 #define PID_EFFECT_STOP 1 static const u8 pidff_effect_operation_status[] = { 0x79, 0x7b }; -/* Polar direction 90 degrees (East) */ -#define PIDFF_FIXED_WHEEL_DIRECTION 0x4000 +#define PID_DIRECTION_NORTH 0x0000 +#define PID_DIRECTION_EAST 0x4000 +#define PID_DIRECTION_SOUTH 0x8000 +#define PID_DIRECTION_WEST 0xc000 + +#define PIDFF_FIXED_WHEEL_DIRECTION PID_DIRECTION_EAST + +/* AXES_ENABLE and DIRECTION axes */ +enum pid_axes { + PID_AXIS_X, + PID_AXIS_Y, + PID_AXIS_Z, + PID_AXIS_RX, + PID_AXIS_RY, + PID_AXIS_RZ, + PID_AXIS_SLIDER, + PID_AXIS_DIAL, + PID_AXIS_WHEEL, + PID_AXES_COUNT, +}; +static const u8 pidff_direction_axis[] = { + HID_USAGE & HID_GD_X, + HID_USAGE & HID_GD_Y, + HID_USAGE & HID_GD_Z, + HID_USAGE & HID_GD_RX, + HID_USAGE & HID_GD_RY, + HID_USAGE & HID_GD_RZ, + HID_USAGE & HID_GD_SLIDER, + HID_USAGE & HID_GD_DIAL, + HID_USAGE & HID_GD_WHEEL, +}; struct pidff_usage { struct hid_field *field; s32 *value; }; +struct pidff_effect { + int pid_id; + int is_infinite; + unsigned int loop_count; +}; + struct pidff_device { struct hid_device *hid; - struct hid_report *reports[sizeof(pidff_reports)]; + struct hid_report *reports[ARRAY_SIZE(pidff_reports)]; - struct pidff_usage set_effect[sizeof(pidff_set_effect)]; - struct pidff_usage set_envelope[sizeof(pidff_set_envelope)]; - struct pidff_usage set_condition[sizeof(pidff_set_condition)]; - struct pidff_usage set_periodic[sizeof(pidff_set_periodic)]; - struct pidff_usage set_constant[sizeof(pidff_set_constant)]; - struct pidff_usage set_ramp[sizeof(pidff_set_ramp)]; + struct pidff_usage set_effect[ARRAY_SIZE(pidff_set_effect)]; + struct pidff_usage set_envelope[ARRAY_SIZE(pidff_set_envelope)]; + struct pidff_usage set_condition[ARRAY_SIZE(pidff_set_condition)]; + struct pidff_usage set_periodic[ARRAY_SIZE(pidff_set_periodic)]; + struct pidff_usage set_constant[ARRAY_SIZE(pidff_set_constant)]; + struct pidff_usage set_ramp[ARRAY_SIZE(pidff_set_ramp)]; - struct pidff_usage device_gain[sizeof(pidff_device_gain)]; - struct pidff_usage block_load[sizeof(pidff_block_load)]; - struct pidff_usage pool[sizeof(pidff_pool)]; - struct pidff_usage effect_operation[sizeof(pidff_effect_operation)]; - struct pidff_usage block_free[sizeof(pidff_block_free)]; + struct pidff_usage device_gain[ARRAY_SIZE(pidff_device_gain)]; + struct pidff_usage block_load[ARRAY_SIZE(pidff_block_load)]; + struct pidff_usage pool[ARRAY_SIZE(pidff_pool)]; + struct pidff_usage effect_operation[ARRAY_SIZE(pidff_effect_operation)]; + struct pidff_usage block_free[ARRAY_SIZE(pidff_block_free)]; + + struct pidff_effect effect[PID_EFFECTS_MAX]; /* * Special field is a field that is not composed of @@ -184,6 +221,7 @@ struct pidff_device { /* Special fields in set_effect */ struct hid_field *set_effect_type; struct hid_field *effect_direction; + struct hid_field *axes_enable; /* Special field in device_control */ struct hid_field *device_control; @@ -194,17 +232,86 @@ struct pidff_device { /* Special field in effect_operation */ struct hid_field *effect_operation_status; - int control_id[sizeof(pidff_device_control)]; - int type_id[sizeof(pidff_effect_types)]; - int status_id[sizeof(pidff_block_load_status)]; - int operation_id[sizeof(pidff_effect_operation_status)]; - - int pid_id[PID_EFFECTS_MAX]; + int control_id[ARRAY_SIZE(pidff_device_control)]; + int type_id[ARRAY_SIZE(pidff_effect_types)]; + int status_id[ARRAY_SIZE(pidff_block_load_status)]; + int operation_id[ARRAY_SIZE(pidff_effect_operation_status)]; + int direction_axis_id[ARRAY_SIZE(pidff_direction_axis)]; u32 quirks; u8 effect_count; + u8 axis_count; }; +static int pidff_is_effect_conditional(struct ff_effect *effect) +{ + return effect->type == FF_SPRING || + effect->type == FF_DAMPER || + effect->type == FF_INERTIA || + effect->type == FF_FRICTION; +} + +static int pidff_is_duration_infinite(u16 duration) +{ + return duration == FF_INFINITE || duration == PID_INFINITE; +} + +/* + * Get PID effect index from FF effect type. + * Return 0 if invalid. + */ +static int pidff_effect_ff_to_pid(struct ff_effect *effect) +{ + switch (effect->type) { + case FF_CONSTANT: + return PID_CONSTANT; + case FF_RAMP: + return PID_RAMP; + case FF_SPRING: + return PID_SPRING; + case FF_DAMPER: + return PID_DAMPER; + case FF_INERTIA: + return PID_INERTIA; + case FF_FRICTION: + return PID_FRICTION; + case FF_PERIODIC: + switch (effect->u.periodic.waveform) { + case FF_SQUARE: + return PID_SQUARE; + case FF_TRIANGLE: + return PID_TRIANGLE; + case FF_SINE: + return PID_SINE; + case FF_SAW_UP: + return PID_SAW_UP; + case FF_SAW_DOWN: + return PID_SAW_DOWN; + } + } + pr_err("invalid effect type\n"); + return -EINVAL; +} + +/* + * Get effect id in the device descriptor. + * Return 0 if invalid. + */ +static int pidff_get_effect_type_id(struct pidff_device *pidff, + struct ff_effect *effect) +{ + int id = pidff_effect_ff_to_pid(effect); + + if (id < 0) + return 0; + + if (effect->type == FF_PERIODIC && + pidff->quirks & HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY) + id = PID_SINE; + + return pidff->type_id[id]; +} + /* * Clamp value for a given field */ @@ -219,7 +326,7 @@ static s32 pidff_clamp(s32 i, struct hid_field *field) static int pidff_rescale(int i, int max, struct hid_field *field) { return i * (field->logical_maximum - field->logical_minimum) / max + - field->logical_minimum; + field->logical_minimum; } /* @@ -265,28 +372,24 @@ static void pidff_set_signed(struct pidff_usage *usage, s16 value) else { if (value < 0) usage->value[0] = - pidff_rescale(-value, -S16_MIN, usage->field); + pidff_rescale(-value, -S16_MIN, usage->field); else usage->value[0] = - pidff_rescale(value, S16_MAX, usage->field); + pidff_rescale(value, S16_MAX, usage->field); } pr_debug("calculated from %d to %d\n", value, usage->value[0]); } static void pidff_set_time(struct pidff_usage *usage, u16 time) { - usage->value[0] = pidff_clamp( - pidff_rescale_time(time, usage->field), usage->field); + usage->value[0] = pidff_clamp(pidff_rescale_time(time, usage->field), + usage->field); } static void pidff_set_duration(struct pidff_usage *usage, u16 duration) { - /* Infinite value conversion from Linux API -> PID */ - if (duration == FF_INFINITE) - duration = PID_INFINITE; - /* PID defines INFINITE as the max possible value for duration field */ - if (duration == PID_INFINITE) { + if (pidff_is_duration_infinite(duration)) { usage->value[0] = (1U << usage->field->report_size) - 1; return; } @@ -294,6 +397,43 @@ static void pidff_set_duration(struct pidff_usage *usage, u16 duration) pidff_set_time(usage, duration); } +static void pidff_set_effect_direction(struct pidff_device *pidff, + struct ff_effect *effect) +{ + u16 direction = effect->direction; + int direction_enable = 1; + + /* Use fixed direction if needed */ + if (pidff->quirks & HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION && + pidff_is_effect_conditional(effect)) + direction = PIDFF_FIXED_WHEEL_DIRECTION; + + pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = direction_enable; + pidff->effect_direction->value[0] = + pidff_rescale(direction, U16_MAX, pidff->effect_direction); + + if (direction_enable) + return; + + /* + * For use with improved FFB API + * We want to read the selected axes and their direction from the effect + * struct and only enable those. For now, enable all axes. + * + */ + for (int i = 0; i < PID_AXES_COUNT; i++) { + /* HID index starts with 1 */ + int index = pidff->direction_axis_id[i] - 1; + + if (index < 0) + continue; + + pidff->axes_enable->value[index] = 1; + pidff->effect_direction->value[index] = pidff_rescale( + direction, U16_MAX, pidff->effect_direction); + } +} + /* * Send envelope report to the device */ @@ -313,16 +453,12 @@ static void pidff_set_envelope_report(struct pidff_device *pidff, pidff->set_envelope[PID_FADE_LEVEL].field); pidff_set_time(&pidff->set_envelope[PID_ATTACK_TIME], - envelope->attack_length); + envelope->attack_length); pidff_set_time(&pidff->set_envelope[PID_FADE_TIME], - envelope->fade_length); - - hid_dbg(pidff->hid, "attack %u => %d\n", - envelope->attack_level, - pidff->set_envelope[PID_ATTACK_LEVEL].value[0]); + envelope->fade_length); hid_hw_request(pidff->hid, pidff->reports[PID_SET_ENVELOPE], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -331,7 +467,7 @@ static void pidff_set_envelope_report(struct pidff_device *pidff, static int pidff_needs_set_envelope(struct ff_envelope *envelope, struct ff_envelope *old) { - bool needs_new_envelope; + int needs_new_envelope; needs_new_envelope = envelope->attack_level != 0 || envelope->fade_level != 0 || @@ -339,8 +475,7 @@ static int pidff_needs_set_envelope(struct ff_envelope *envelope, envelope->fade_length != 0; if (!needs_new_envelope) - return false; - + return 0; if (!old) return needs_new_envelope; @@ -353,8 +488,8 @@ static int pidff_needs_set_envelope(struct ff_envelope *envelope, /* * Send constant force report to the device */ -static void pidff_set_constant_force_report(struct pidff_device *pidff, - struct ff_effect *effect) +static void pidff_set_constant_report(struct pidff_device *pidff, + struct ff_effect *effect) { pidff->set_constant[PID_EFFECT_BLOCK_INDEX].value[0] = pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; @@ -362,7 +497,7 @@ static void pidff_set_constant_force_report(struct pidff_device *pidff, effect->u.constant.level); hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONSTANT], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -386,28 +521,23 @@ static void pidff_set_effect_report(struct pidff_device *pidff, pidff->create_new_effect_type->value[0]; pidff_set_duration(&pidff->set_effect[PID_DURATION], - effect->replay.length); + effect->replay.length); pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = effect->trigger.button; pidff_set_time(&pidff->set_effect[PID_TRIGGER_REPEAT_INT], - effect->trigger.interval); + effect->trigger.interval); pidff->set_effect[PID_GAIN].value[0] = pidff->set_effect[PID_GAIN].field->logical_maximum; - pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1; - /* Use fixed direction if needed */ - pidff->effect_direction->value[0] = pidff_rescale( - pidff->quirks & HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION ? - PIDFF_FIXED_WHEEL_DIRECTION : effect->direction, - U16_MAX, pidff->effect_direction); + pidff_set_effect_direction(pidff, effect); /* Omit setting delay field if it's missing */ if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_DELAY)) pidff_set_time(&pidff->set_effect[PID_START_DELAY], - effect->replay.delay); + effect->replay.delay); hid_hw_request(pidff->hid, pidff->reports[PID_SET_EFFECT], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -437,10 +567,10 @@ static void pidff_set_periodic_report(struct pidff_device *pidff, effect->u.periodic.offset); pidff_set(&pidff->set_periodic[PID_PHASE], effect->u.periodic.phase); pidff_set_time(&pidff->set_periodic[PID_PERIOD], - effect->u.periodic.period); + effect->u.periodic.period); hid_hw_request(pidff->hid, pidff->reports[PID_SET_PERIODIC], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -487,7 +617,7 @@ static void pidff_set_condition_report(struct pidff_device *pidff, pidff_set(&pidff->set_condition[PID_DEAD_BAND], effect->u.condition[i].deadband); hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONDITION], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } } @@ -518,8 +648,8 @@ static int pidff_needs_set_condition(struct ff_effect *effect, /* * Send ramp force report to the device */ -static void pidff_set_ramp_force_report(struct pidff_device *pidff, - struct ff_effect *effect) +static void pidff_set_ramp_report(struct pidff_device *pidff, + struct ff_effect *effect) { pidff->set_ramp[PID_EFFECT_BLOCK_INDEX].value[0] = pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; @@ -528,7 +658,7 @@ static void pidff_set_ramp_force_report(struct pidff_device *pidff, pidff_set_signed(&pidff->set_ramp[PID_RAMP_END], effect->u.ramp.end_level); hid_hw_request(pidff->hid, pidff->reports[PID_SET_RAMP], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -550,7 +680,7 @@ static void pidff_set_gain_report(struct pidff_device *pidff, u16 gain) pidff_set(&pidff->device_gain[PID_DEVICE_GAIN_FIELD], gain); hid_hw_request(pidff->hid, pidff->reports[PID_DEVICE_GAIN], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -558,8 +688,7 @@ static void pidff_set_gain_report(struct pidff_device *pidff, u16 gain) */ static void pidff_set_device_control(struct pidff_device *pidff, int field) { - int i, index; - int field_index = pidff->control_id[field]; + const int field_index = pidff->control_id[field]; if (field_index < 1) return; @@ -569,8 +698,9 @@ static void pidff_set_device_control(struct pidff_device *pidff, int field) hid_dbg(pidff->hid, "DEVICE_CONTROL is a bitmask\n"); /* Clear current bitmask */ - for (i = 0; i < sizeof(pidff_device_control); i++) { - index = pidff->control_id[i]; + for (int i = 0; i < ARRAY_SIZE(pidff_device_control); i++) { + int index = pidff->control_id[i]; + if (index < 1) continue; @@ -585,16 +715,8 @@ static void pidff_set_device_control(struct pidff_device *pidff, int field) hid_hw_request(pidff->hid, pidff->reports[PID_DEVICE_CONTROL], HID_REQ_SET_REPORT); hid_hw_wait(pidff->hid); -} - -/* - * Modify actuators state - */ -static void pidff_set_actuators(struct pidff_device *pidff, bool enable) -{ - hid_dbg(pidff->hid, "%s actuators\n", enable ? "Enable" : "Disable"); - pidff_set_device_control(pidff, - enable ? PID_ENABLE_ACTUATORS : PID_DISABLE_ACTUATORS); + hid_dbg(pidff->hid, "Device control command 0x%02x sent", + pidff_device_control[field]); } /* @@ -608,7 +730,7 @@ static void pidff_reset(struct pidff_device *pidff) pidff->effect_count = 0; pidff_set_device_control(pidff, PID_STOP_ALL_EFFECTS); - pidff_set_actuators(pidff, 1); + pidff_set_device_control(pidff, PID_ENABLE_ACTUATORS); } /* @@ -644,32 +766,25 @@ static void pidff_fetch_pool(struct pidff_device *pidff) */ static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum) { - int j; - - if (!pidff->effect_count) - pidff_reset(pidff); - pidff->create_new_effect_type->value[0] = efnum; hid_hw_request(pidff->hid, pidff->reports[PID_CREATE_NEW_EFFECT], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); hid_dbg(pidff->hid, "create_new_effect sent, type: %d\n", efnum); pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0; pidff->block_load_status->value[0] = 0; hid_hw_wait(pidff->hid); - for (j = 0; j < 60; j++) { + for (int i = 0; i < 60; i++) { hid_dbg(pidff->hid, "pid_block_load requested\n"); hid_hw_request(pidff->hid, pidff->reports[PID_BLOCK_LOAD], - HID_REQ_GET_REPORT); + HID_REQ_GET_REPORT); hid_hw_wait(pidff->hid); if (pidff->block_load_status->value[0] == pidff->status_id[PID_BLOCK_LOAD_SUCCESS]) { hid_dbg(pidff->hid, "device reported free memory: %d bytes\n", pidff->block_load[PID_RAM_POOL_AVAILABLE].value ? pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1); - - pidff->effect_count++; return 0; } if (pidff->block_load_status->value[0] == @@ -689,6 +804,12 @@ static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum) return -EIO; } +static int pidff_needs_playback(struct pidff_device *pidff, int effect_id, int n) +{ + return pidff->effect[effect_id].is_infinite || + pidff->effect[effect_id].loop_count != n; +} + /* * Play the effect with PID id n times */ @@ -696,6 +817,9 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n) { pidff->effect_operation[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id; + hid_dbg(pidff->hid, "%s PID effect %d", n == 0 ? "stopping" : "playing", + pid_id); + if (n == 0) { pidff->effect_operation_status->value[0] = pidff->operation_id[PID_EFFECT_STOP]; @@ -707,7 +831,7 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n) } hid_hw_request(pidff->hid, pidff->reports[PID_EFFECT_OPERATION], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -717,7 +841,14 @@ static int pidff_playback(struct input_dev *dev, int effect_id, int value) { struct pidff_device *pidff = dev->ff->private; - pidff_playback_pid(pidff, pidff->pid_id[effect_id], value); + if (!pidff_needs_playback(pidff, effect_id, value)) + return 0; + + hid_dbg(pidff->hid, "requesting %s on FF effect %d", + value == 0 ? "stop" : "playback", effect_id); + + pidff->effect[effect_id].loop_count = value; + pidff_playback_pid(pidff, pidff->effect[effect_id].pid_id, value); return 0; } @@ -729,10 +860,7 @@ static void pidff_erase_pid(struct pidff_device *pidff, int pid_id) { pidff->block_free[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id; hid_hw_request(pidff->hid, pidff->reports[PID_BLOCK_FREE], - HID_REQ_SET_REPORT); - - if (pidff->effect_count > 0) - pidff->effect_count--; + HID_REQ_SET_REPORT); } /* @@ -741,10 +869,9 @@ static void pidff_erase_pid(struct pidff_device *pidff, int pid_id) static int pidff_erase_effect(struct input_dev *dev, int effect_id) { struct pidff_device *pidff = dev->ff->private; - int pid_id = pidff->pid_id[effect_id]; + int pid_id = pidff->effect[effect_id].pid_id; - hid_dbg(pidff->hid, "starting to erase %d/%d\n", - effect_id, pidff->pid_id[effect_id]); + hid_dbg(pidff->hid, "starting to erase %d/%d\n", effect_id, pid_id); /* * Wait for the queue to clear. We do not want @@ -754,139 +881,83 @@ static int pidff_erase_effect(struct input_dev *dev, int effect_id) pidff_playback_pid(pidff, pid_id, 0); pidff_erase_pid(pidff, pid_id); + if (pidff->effect_count > 0) + pidff->effect_count--; + + hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count); return 0; } +#define PIDFF_SET_REPORT_IF_NEEDED(type, effect, old) \ + ({ if (!old || pidff_needs_set_## type(effect, old)) \ + pidff_set_ ##type## _report(pidff, effect); }) + +#define PIDFF_SET_ENVELOPE_IF_NEEDED(type, effect, old) \ + ({ if (pidff_needs_set_envelope(&effect->u.type.envelope, \ + old ? &old->u.type.envelope : NULL)) \ + pidff_set_envelope_report(pidff, &effect->u.type.envelope); }) + /* * Effect upload handler */ -static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect, +static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *new, struct ff_effect *old) { struct pidff_device *pidff = dev->ff->private; - int type_id; - int error; + const int type_id = pidff_get_effect_type_id(pidff, new); - pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0; - if (old) { - pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = - pidff->pid_id[effect->id]; + if (!type_id) { + hid_err(pidff->hid, "effect type not supported\n"); + return -EINVAL; } - switch (effect->type) { + if (!pidff->effect_count) + pidff_reset(pidff); + + if (!old) { + int error = pidff_request_effect_upload(pidff, type_id); + + if (error) + return error; + + pidff->effect_count++; + hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count); + pidff->effect[new->id].loop_count = 0; + pidff->effect[new->id].pid_id = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + } + + pidff->effect[new->id].is_infinite = + pidff_is_duration_infinite(new->replay.length); + + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->effect[new->id].pid_id; + + PIDFF_SET_REPORT_IF_NEEDED(effect, new, old); + switch (new->type) { case FF_CONSTANT: - if (!old) { - error = pidff_request_effect_upload(pidff, - pidff->type_id[PID_CONSTANT]); - if (error) - return error; - } - if (!old || pidff_needs_set_effect(effect, old)) - pidff_set_effect_report(pidff, effect); - if (!old || pidff_needs_set_constant(effect, old)) - pidff_set_constant_force_report(pidff, effect); - if (pidff_needs_set_envelope(&effect->u.constant.envelope, - old ? &old->u.constant.envelope : NULL)) - pidff_set_envelope_report(pidff, &effect->u.constant.envelope); + PIDFF_SET_REPORT_IF_NEEDED(constant, new, old); + PIDFF_SET_ENVELOPE_IF_NEEDED(constant, new, old); break; case FF_PERIODIC: - if (!old) { - switch (effect->u.periodic.waveform) { - case FF_SQUARE: - type_id = PID_SQUARE; - break; - case FF_TRIANGLE: - type_id = PID_TRIANGLE; - break; - case FF_SINE: - type_id = PID_SINE; - break; - case FF_SAW_UP: - type_id = PID_SAW_UP; - break; - case FF_SAW_DOWN: - type_id = PID_SAW_DOWN; - break; - default: - hid_err(pidff->hid, "invalid waveform\n"); - return -EINVAL; - } - - if (pidff->quirks & HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY) - type_id = PID_SINE; - - error = pidff_request_effect_upload(pidff, - pidff->type_id[type_id]); - if (error) - return error; - } - if (!old || pidff_needs_set_effect(effect, old)) - pidff_set_effect_report(pidff, effect); - if (!old || pidff_needs_set_periodic(effect, old)) - pidff_set_periodic_report(pidff, effect); - if (pidff_needs_set_envelope(&effect->u.periodic.envelope, - old ? &old->u.periodic.envelope : NULL)) - pidff_set_envelope_report(pidff, &effect->u.periodic.envelope); + PIDFF_SET_REPORT_IF_NEEDED(periodic, new, old); + PIDFF_SET_ENVELOPE_IF_NEEDED(periodic, new, old); break; case FF_RAMP: - if (!old) { - error = pidff_request_effect_upload(pidff, - pidff->type_id[PID_RAMP]); - if (error) - return error; - } - if (!old || pidff_needs_set_effect(effect, old)) - pidff_set_effect_report(pidff, effect); - if (!old || pidff_needs_set_ramp(effect, old)) - pidff_set_ramp_force_report(pidff, effect); - if (pidff_needs_set_envelope(&effect->u.ramp.envelope, - old ? &old->u.ramp.envelope : NULL)) - pidff_set_envelope_report(pidff, &effect->u.ramp.envelope); + PIDFF_SET_REPORT_IF_NEEDED(ramp, new, old); + PIDFF_SET_ENVELOPE_IF_NEEDED(ramp, new, old); break; case FF_SPRING: case FF_DAMPER: case FF_INERTIA: case FF_FRICTION: - if (!old) { - switch (effect->type) { - case FF_SPRING: - type_id = PID_SPRING; - break; - case FF_DAMPER: - type_id = PID_DAMPER; - break; - case FF_INERTIA: - type_id = PID_INERTIA; - break; - case FF_FRICTION: - type_id = PID_FRICTION; - break; - } - error = pidff_request_effect_upload(pidff, - pidff->type_id[type_id]); - if (error) - return error; - } - if (!old || pidff_needs_set_effect(effect, old)) - pidff_set_effect_report(pidff, effect); - if (!old || pidff_needs_set_condition(effect, old)) - pidff_set_condition_report(pidff, effect); + PIDFF_SET_REPORT_IF_NEEDED(condition, new, old); break; - - default: - hid_err(pidff->hid, "invalid type\n"); - return -EINVAL; } - - if (!old) - pidff->pid_id[effect->id] = - pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; - hid_dbg(pidff->hid, "uploaded\n"); - return 0; } @@ -924,7 +995,7 @@ static void pidff_autocenter(struct pidff_device *pidff, u16 magnitude) pidff->set_effect[PID_START_DELAY].value[0] = 0; hid_hw_request(pidff->hid, pidff->reports[PID_SET_EFFECT], - HID_REQ_SET_REPORT); + HID_REQ_SET_REPORT); } /* @@ -936,56 +1007,85 @@ static void pidff_set_autocenter(struct input_dev *dev, u16 magnitude) } /* + * Find specific usage in a given hid_field + */ +static int pidff_find_usage(struct hid_field *fld, unsigned int usage_code) +{ + for (int i = 0; i < fld->maxusage; i++) { + if (fld->usage[i].hid == usage_code) + return i; + } + return -1; +} + +/* + * Find hid_field with a specific usage. Return the usage index as well + */ +static int pidff_find_field_with_usage(int *usage_index, + struct hid_report *report, + unsigned int usage_code) +{ + for (int i = 0; i < report->maxfield; i++) { + struct hid_field *fld = report->field[i]; + + if (fld->maxusage != fld->report_count) { + pr_debug("maxusage and report_count do not match, skipping\n"); + continue; + } + + int index = pidff_find_usage(fld, usage_code); + + if (index >= 0) { + *usage_index = index; + return i; + } + } + return -1; +} + +/* * Find fields from a report and fill a pidff_usage */ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table, - struct hid_report *report, int count, int strict) + struct hid_report *report, int count, int strict, + u32 *quirks) { + const u8 block_offset = pidff_set_condition[PID_PARAM_BLOCK_OFFSET]; + const u8 delay = pidff_set_effect[PID_START_DELAY]; + if (!report) { pr_debug("%s, null report\n", __func__); return -1; } - int i, j, k, found; - int return_value = 0; + for (int i = 0; i < count; i++) { + int index; + int found = pidff_find_field_with_usage(&index, report, + HID_UP_PID | table[i]); - for (k = 0; k < count; k++) { - found = 0; - for (i = 0; i < report->maxfield; i++) { - if (report->field[i]->maxusage != - report->field[i]->report_count) { - pr_debug("maxusage and report_count do not match, skipping\n"); - continue; - } - for (j = 0; j < report->field[i]->maxusage; j++) { - if (report->field[i]->usage[j].hid == - (HID_UP_PID | table[k])) { - pr_debug("found %d at %d->%d\n", - k, i, j); - usage[k].field = report->field[i]; - usage[k].value = - &report->field[i]->value[j]; - found = 1; - break; - } - } - if (found) - break; + if (found >= 0) { + pr_debug("found %d at %d->%d\n", i, found, index); + usage[i].field = report->field[found]; + usage[i].value = &report->field[found]->value[index]; + continue; } - if (!found && table[k] == pidff_set_effect[PID_START_DELAY]) { + + if (table[i] == delay) { pr_debug("Delay field not found, but that's OK\n"); pr_debug("Setting MISSING_DELAY quirk\n"); - return_value |= HID_PIDFF_QUIRK_MISSING_DELAY; - } else if (!found && table[k] == pidff_set_condition[PID_PARAM_BLOCK_OFFSET]) { + *quirks |= HID_PIDFF_QUIRK_MISSING_DELAY; + + } else if (table[i] == block_offset) { pr_debug("PBO field not found, but that's OK\n"); pr_debug("Setting MISSING_PBO quirk\n"); - return_value |= HID_PIDFF_QUIRK_MISSING_PBO; - } else if (!found && strict) { - pr_debug("failed to locate %d\n", k); + *quirks |= HID_PIDFF_QUIRK_MISSING_PBO; + + } else if (strict) { + pr_debug("failed to locate %d\n", i); return -1; } } - return return_value; + return 0; } /* @@ -995,7 +1095,7 @@ static int pidff_check_usage(int usage) { int i; - for (i = 0; i < sizeof(pidff_reports); i++) + for (i = 0; i < ARRAY_SIZE(pidff_reports); i++) if (usage == (HID_UP_PID | pidff_reports[i])) return i; @@ -1050,9 +1150,7 @@ static void pidff_find_reports(struct hid_device *hid, int report_type, */ static int pidff_reports_ok(struct pidff_device *pidff) { - int i; - - for (i = 0; i <= PID_REQUIRED_REPORTS; i++) { + for (int i = 0; i < PID_REQUIRED_REPORTS; i++) { if (!pidff->reports[i]) { hid_dbg(pidff->hid, "%d missing\n", i); return 0; @@ -1073,9 +1171,7 @@ static struct hid_field *pidff_find_special_field(struct hid_report *report, return NULL; } - int i; - - for (i = 0; i < report->maxfield; i++) { + for (int i = 0; i < report->maxfield; i++) { if (report->field[i]->logical == (HID_UP_PID | usage) && report->field[i]->report_count > 0) { if (!enforce_min || @@ -1093,27 +1189,29 @@ static struct hid_field *pidff_find_special_field(struct hid_report *report, * Fill a pidff->*_id struct table */ static int pidff_find_special_keys(int *keys, struct hid_field *fld, - const u8 *usagetable, int count) + const u8 *usagetable, int count, + unsigned int usage_page) { - - int i, j; int found = 0; - for (i = 0; i < count; i++) { - for (j = 0; j < fld->maxusage; j++) { - if (fld->usage[j].hid == (HID_UP_PID | usagetable[i])) { - keys[i] = j + 1; - found++; - break; - } - } + if (!fld) + return 0; + + for (int i = 0; i < count; i++) { + keys[i] = pidff_find_usage(fld, usage_page | usagetable[i]) + 1; + if (keys[i]) + found++; } return found; } #define PIDFF_FIND_SPECIAL_KEYS(keys, field, name) \ pidff_find_special_keys(pidff->keys, pidff->field, pidff_ ## name, \ - sizeof(pidff_ ## name)) + ARRAY_SIZE(pidff_ ## name), HID_UP_PID) + +#define PIDFF_FIND_GENERAL_DESKTOP(keys, field, name) \ + pidff_find_special_keys(pidff->keys, pidff->field, pidff_ ## name, \ + ARRAY_SIZE(pidff_ ## name), HID_UP_GENDESK) /* * Find and check the special fields @@ -1128,13 +1226,24 @@ static int pidff_find_special_fields(struct pidff_device *pidff) pidff->set_effect_type = pidff_find_special_field(pidff->reports[PID_SET_EFFECT], PID_EFFECT_TYPE, 1); + pidff->axes_enable = + pidff_find_special_field(pidff->reports[PID_SET_EFFECT], + PID_AXES_ENABLE, 0); pidff->effect_direction = pidff_find_special_field(pidff->reports[PID_SET_EFFECT], PID_DIRECTION, 0); pidff->device_control = pidff_find_special_field(pidff->reports[PID_DEVICE_CONTROL], - PID_DEVICE_CONTROL_ARRAY, - !(pidff->quirks & HID_PIDFF_QUIRK_PERMISSIVE_CONTROL)); + PID_DEVICE_CONTROL_ARRAY, 1); + + /* Detect and set permissive control quirk */ + if (!pidff->device_control) { + pr_debug("Setting PERMISSIVE_CONTROL quirk\n"); + pidff->quirks |= HID_PIDFF_QUIRK_PERMISSIVE_CONTROL; + pidff->device_control = pidff_find_special_field( + pidff->reports[PID_DEVICE_CONTROL], + PID_DEVICE_CONTROL_ARRAY, 0); + } pidff->block_load_status = pidff_find_special_field(pidff->reports[PID_BLOCK_LOAD], @@ -1180,7 +1289,7 @@ static int pidff_find_special_fields(struct pidff_device *pidff) if (PIDFF_FIND_SPECIAL_KEYS(status_id, block_load_status, block_load_status) != - sizeof(pidff_block_load_status)) { + ARRAY_SIZE(pidff_block_load_status)) { hid_err(pidff->hid, "block load status identifiers not found\n"); return -1; @@ -1188,11 +1297,37 @@ static int pidff_find_special_fields(struct pidff_device *pidff) if (PIDFF_FIND_SPECIAL_KEYS(operation_id, effect_operation_status, effect_operation_status) != - sizeof(pidff_effect_operation_status)) { + ARRAY_SIZE(pidff_effect_operation_status)) { hid_err(pidff->hid, "effect operation identifiers not found\n"); return -1; } + if (!pidff->axes_enable) { + hid_info(pidff->hid, "axes enable field not found!\n"); + return 0; + } + + hid_dbg(pidff->hid, "axes enable report count: %u\n", + pidff->axes_enable->report_count); + + uint found = PIDFF_FIND_GENERAL_DESKTOP(direction_axis_id, axes_enable, + direction_axis); + + pidff->axis_count = found; + hid_dbg(pidff->hid, "found direction axes: %u", found); + + for (int i = 0; i < ARRAY_SIZE(pidff_direction_axis); i++) { + if (!pidff->direction_axis_id[i]) + continue; + + hid_dbg(pidff->hid, "axis %d, usage: 0x%04x, index: %d", i + 1, + pidff_direction_axis[i], pidff->direction_axis_id[i]); + } + + if (pidff->axes_enable && found != pidff->axes_enable->report_count) + hid_warn(pidff->hid, "axes_enable: %u != direction axes: %u", + pidff->axes_enable->report_count, found); + return 0; } @@ -1204,7 +1339,7 @@ static int pidff_find_effects(struct pidff_device *pidff, { int i; - for (i = 0; i < sizeof(pidff_effect_types); i++) { + for (i = 0; i < ARRAY_SIZE(pidff_effect_types); i++) { int pidff_type = pidff->type_id[i]; if (pidff->set_effect_type->usage[pidff_type].hid != @@ -1254,26 +1389,17 @@ static int pidff_find_effects(struct pidff_device *pidff, #define PIDFF_FIND_FIELDS(name, report, strict) \ pidff_find_fields(pidff->name, pidff_ ## name, \ pidff->reports[report], \ - sizeof(pidff_ ## name), strict) + ARRAY_SIZE(pidff_ ## name), strict, &pidff->quirks) /* * Fill and check the pidff_usages */ static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev) { - int status = 0; - - /* Save info about the device not having the DELAY ffb field. */ - status = PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1); - if (status == -1) { + if (PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1)) { hid_err(pidff->hid, "unknown set_effect report layout\n"); return -ENODEV; } - pidff->quirks |= status; - - if (status & HID_PIDFF_QUIRK_MISSING_DELAY) - hid_dbg(pidff->hid, "Adding MISSING_DELAY quirk\n"); - PIDFF_FIND_FIELDS(block_load, PID_BLOCK_LOAD, 0); if (!pidff->block_load[PID_EFFECT_BLOCK_INDEX].value) { @@ -1307,39 +1433,25 @@ static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev) "has periodic effect but no envelope\n"); } - if (test_bit(FF_CONSTANT, dev->ffbit) && - PIDFF_FIND_FIELDS(set_constant, PID_SET_CONSTANT, 1)) { + if (PIDFF_FIND_FIELDS(set_constant, PID_SET_CONSTANT, 1) && + test_and_clear_bit(FF_CONSTANT, dev->ffbit)) hid_warn(pidff->hid, "unknown constant effect layout\n"); - clear_bit(FF_CONSTANT, dev->ffbit); - } - if (test_bit(FF_RAMP, dev->ffbit) && - PIDFF_FIND_FIELDS(set_ramp, PID_SET_RAMP, 1)) { + if (PIDFF_FIND_FIELDS(set_ramp, PID_SET_RAMP, 1) && + test_and_clear_bit(FF_RAMP, dev->ffbit)) hid_warn(pidff->hid, "unknown ramp effect layout\n"); - clear_bit(FF_RAMP, dev->ffbit); - } - - if (test_bit(FF_SPRING, dev->ffbit) || - test_bit(FF_DAMPER, dev->ffbit) || - test_bit(FF_FRICTION, dev->ffbit) || - test_bit(FF_INERTIA, dev->ffbit)) { - status = PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1); - if (status < 0) { + if (PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1)) { + if (test_and_clear_bit(FF_SPRING, dev->ffbit) || + test_and_clear_bit(FF_DAMPER, dev->ffbit) || + test_and_clear_bit(FF_FRICTION, dev->ffbit) || + test_and_clear_bit(FF_INERTIA, dev->ffbit)) hid_warn(pidff->hid, "unknown condition effect layout\n"); - clear_bit(FF_SPRING, dev->ffbit); - clear_bit(FF_DAMPER, dev->ffbit); - clear_bit(FF_FRICTION, dev->ffbit); - clear_bit(FF_INERTIA, dev->ffbit); - } - pidff->quirks |= status; } - if (test_bit(FF_PERIODIC, dev->ffbit) && - PIDFF_FIND_FIELDS(set_periodic, PID_SET_PERIODIC, 1)) { + if (PIDFF_FIND_FIELDS(set_periodic, PID_SET_PERIODIC, 1) && + test_and_clear_bit(FF_PERIODIC, dev->ffbit)) hid_warn(pidff->hid, "unknown periodic effect layout\n"); - clear_bit(FF_PERIODIC, dev->ffbit); - } PIDFF_FIND_FIELDS(pool, PID_POOL, 0); @@ -1392,8 +1504,8 @@ static int pidff_check_autocenter(struct pidff_device *pidff, int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks) { struct pidff_device *pidff; - struct hid_input *hidinput = list_entry(hid->inputs.next, - struct hid_input, list); + struct hid_input *hidinput = + list_entry(hid->inputs.next, struct hid_input, list); struct input_dev *dev = hidinput->input; struct ff_device *ff; int max_effects; @@ -1473,14 +1585,14 @@ int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks) ff->set_autocenter = pidff_set_autocenter; ff->playback = pidff_playback; - hid_info(dev, "Force feedback for USB HID PID devices by Anssi Hannula <anssi.hannula@gmail.com>\n"); - hid_dbg(dev, "Active quirks mask: 0x%x\n", pidff->quirks); + hid_info(dev, "Force feedback for USB HID PID devices by Anssi Hannula\n"); + hid_dbg(dev, "Active quirks mask: 0x%08x\n", pidff->quirks); hid_device_io_stop(hid); return 0; - fail: +fail: hid_device_io_stop(hid); kfree(pidff); diff --git a/drivers/hid/usbhid/hid-pidff.h b/drivers/hid/usbhid/hid-pidff.h index a53a8b436baa..f321f675e131 100644 --- a/drivers/hid/usbhid/hid-pidff.h +++ b/drivers/hid/usbhid/hid-pidff.h @@ -16,7 +16,7 @@ #define HID_PIDFF_QUIRK_PERMISSIVE_CONTROL BIT(2) /* Use fixed 0x4000 direction during SET_EFFECT report upload */ -#define HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION BIT(3) +#define HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION BIT(3) /* Force all periodic effects to be uploaded as SINE */ #define HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY BIT(4) diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index f064e3d172b3..6a4239ebb582 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -268,4 +268,16 @@ config CORESIGHT_KUNIT_TESTS Enable Coresight unit tests. Only useful for development and not intended for production. +config CORESIGHT_TNOC + tristate "Coresight Trace Network On Chip driver" + help + This driver provides support for Trace Network On Chip (TNOC) component. + TNOC is an interconnect used to collect traces from various subsystems + and transport to a coresight trace sink. It sits in the different + tiles of SOC and aggregates the trace local to the tile and transports + it another tile or to coresight trace sink eventually. + + To compile this driver as a module, choose M here: the module will be + called coresight-tnoc. + endif diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index 4e7cc3c5bf99..ab16d06783a5 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_CORESIGHT_SINK_TPIU) += coresight-tpiu.o obj-$(CONFIG_CORESIGHT_SINK_ETBV10) += coresight-etb10.o obj-$(CONFIG_CORESIGHT_LINKS_AND_SINKS) += coresight-funnel.o \ coresight-replicator.o +obj-$(CONFIG_CORESIGHT_TNOC) += coresight-tnoc.o obj-$(CONFIG_CORESIGHT_SOURCE_ETM3X) += coresight-etm3x.o coresight-etm3x-y := coresight-etm3x-core.o coresight-etm-cp14.o \ coresight-etm3x-sysfs.o diff --git a/drivers/hwtracing/coresight/coresight-catu.c b/drivers/hwtracing/coresight/coresight-catu.c index 5058432233da..a3ccb7034ae1 100644 --- a/drivers/hwtracing/coresight/coresight-catu.c +++ b/drivers/hwtracing/coresight/coresight-catu.c @@ -515,11 +515,21 @@ static int __catu_probe(struct device *dev, struct resource *res) { int ret = 0; u32 dma_mask; - struct catu_drvdata *drvdata = dev_get_drvdata(dev); + struct catu_drvdata *drvdata; struct coresight_desc catu_desc; struct coresight_platform_data *pdata = NULL; void __iomem *base; + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + dev_set_drvdata(dev, drvdata); + + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; + catu_desc.name = coresight_alloc_device_name(&catu_devs, dev); if (!catu_desc.name) return -ENOMEM; @@ -576,14 +586,8 @@ out: static int catu_probe(struct amba_device *adev, const struct amba_id *id) { - struct catu_drvdata *drvdata; int ret; - drvdata = devm_kzalloc(&adev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - amba_set_drvdata(adev, drvdata); ret = __catu_probe(&adev->dev, &adev->res); if (!ret) pm_runtime_put(&adev->dev); @@ -623,29 +627,16 @@ static struct amba_driver catu_driver = { static int catu_platform_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - struct catu_drvdata *drvdata; int ret = 0; - drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; - pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); - dev_set_drvdata(&pdev->dev, drvdata); ret = __catu_probe(&pdev->dev, res); pm_runtime_put(&pdev->dev); - if (ret) { + if (ret) pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); - } return ret; } @@ -659,8 +650,6 @@ static void catu_platform_remove(struct platform_device *pdev) __catu_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } #ifdef CONFIG_PM @@ -668,18 +657,26 @@ static int catu_runtime_suspend(struct device *dev) { struct catu_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); + return 0; } static int catu_runtime_resume(struct device *dev) { struct catu_drvdata *drvdata = dev_get_drvdata(dev); + int ret; - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; + + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + + return ret; } #endif diff --git a/drivers/hwtracing/coresight/coresight-catu.h b/drivers/hwtracing/coresight/coresight-catu.h index 755776cd19c5..6e6b7aac206d 100644 --- a/drivers/hwtracing/coresight/coresight-catu.h +++ b/drivers/hwtracing/coresight/coresight-catu.h @@ -62,6 +62,7 @@ struct catu_drvdata { struct clk *pclk; + struct clk *atclk; void __iomem *base; struct coresight_device *csdev; int irq; diff --git a/drivers/hwtracing/coresight/coresight-core.c b/drivers/hwtracing/coresight/coresight-core.c index fa758cc21827..3267192f0c1c 100644 --- a/drivers/hwtracing/coresight/coresight-core.c +++ b/drivers/hwtracing/coresight/coresight-core.c @@ -3,6 +3,8 @@ * Copyright (c) 2012, The Linux Foundation. All rights reserved. */ +#include <linux/acpi.h> +#include <linux/bitfield.h> #include <linux/build_bug.h> #include <linux/kernel.h> #include <linux/init.h> @@ -1374,8 +1376,9 @@ struct coresight_device *coresight_register(struct coresight_desc *desc) goto out_unlock; } - if (csdev->type == CORESIGHT_DEV_TYPE_SINK || - csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) { + if ((csdev->type == CORESIGHT_DEV_TYPE_SINK || + csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) && + sink_ops(csdev)->alloc_buffer) { ret = etm_perf_add_symlink_sink(csdev); if (ret) { @@ -1698,6 +1701,53 @@ int coresight_etm_get_trace_id(struct coresight_device *csdev, enum cs_mode mode } EXPORT_SYMBOL_GPL(coresight_etm_get_trace_id); +/* + * Attempt to find and enable programming clock (pclk) and trace clock (atclk) + * for the given device. + * + * For ACPI devices, clocks are controlled by firmware, so bail out early in + * this case. Also, skip enabling pclk if the clock is managed by the AMBA + * bus driver instead. + * + * atclk is an optional clock, it will be only enabled when it is existed. + * Otherwise, a NULL pointer will be returned to caller. + * + * Returns: '0' on Success; Error code otherwise. + */ +int coresight_get_enable_clocks(struct device *dev, struct clk **pclk, + struct clk **atclk) +{ + WARN_ON(!pclk); + + if (has_acpi_companion(dev)) + return 0; + + if (!dev_is_amba(dev)) { + /* + * "apb_pclk" is the default clock name for an Arm Primecell + * peripheral, while "apb" is used only by the CTCU driver. + * + * For easier maintenance, CoreSight drivers should use + * "apb_pclk" as the programming clock name. + */ + *pclk = devm_clk_get_optional_enabled(dev, "apb_pclk"); + if (!*pclk) + *pclk = devm_clk_get_optional_enabled(dev, "apb"); + if (IS_ERR(*pclk)) + return PTR_ERR(*pclk); + } + + /* Initialization of atclk is skipped if it is a NULL pointer. */ + if (atclk) { + *atclk = devm_clk_get_optional_enabled(dev, "atclk"); + if (IS_ERR(*atclk)) + return PTR_ERR(*atclk); + } + + return 0; +} +EXPORT_SYMBOL_GPL(coresight_get_enable_clocks); + MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Pratik Patel <pratikp@codeaurora.org>"); MODULE_AUTHOR("Mathieu Poirier <mathieu.poirier@linaro.org>"); diff --git a/drivers/hwtracing/coresight/coresight-cpu-debug.c b/drivers/hwtracing/coresight/coresight-cpu-debug.c index a871d997330b..5f21366406aa 100644 --- a/drivers/hwtracing/coresight/coresight-cpu-debug.c +++ b/drivers/hwtracing/coresight/coresight-cpu-debug.c @@ -562,10 +562,20 @@ static void debug_func_exit(void) static int __debug_probe(struct device *dev, struct resource *res) { - struct debug_drvdata *drvdata = dev_get_drvdata(dev); + struct debug_drvdata *drvdata; void __iomem *base; int ret; + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + dev_set_drvdata(dev, drvdata); + + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, NULL); + if (ret) + return ret; + drvdata->cpu = coresight_get_cpu(dev); if (drvdata->cpu < 0) return drvdata->cpu; @@ -625,13 +635,6 @@ err: static int debug_probe(struct amba_device *adev, const struct amba_id *id) { - struct debug_drvdata *drvdata; - - drvdata = devm_kzalloc(&adev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - amba_set_drvdata(adev, drvdata); return __debug_probe(&adev->dev, &adev->res); } @@ -690,18 +693,8 @@ static struct amba_driver debug_driver = { static int debug_platform_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - struct debug_drvdata *drvdata; int ret = 0; - drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; - - dev_set_drvdata(&pdev->dev, drvdata); pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); @@ -710,8 +703,6 @@ static int debug_platform_probe(struct platform_device *pdev) if (ret) { pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } return ret; } @@ -725,8 +716,6 @@ static void debug_platform_remove(struct platform_device *pdev) __debug_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } #ifdef CONFIG_ACPI @@ -742,8 +731,8 @@ static int debug_runtime_suspend(struct device *dev) { struct debug_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); + clk_disable_unprepare(drvdata->pclk); + return 0; } @@ -751,9 +740,7 @@ static int debug_runtime_resume(struct device *dev) { struct debug_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + return clk_prepare_enable(drvdata->pclk); } #endif diff --git a/drivers/hwtracing/coresight/coresight-ctcu-core.c b/drivers/hwtracing/coresight/coresight-ctcu-core.c index c6bafc96db96..c586495e9a08 100644 --- a/drivers/hwtracing/coresight/coresight-ctcu-core.c +++ b/drivers/hwtracing/coresight/coresight-ctcu-core.c @@ -188,7 +188,7 @@ static int ctcu_probe(struct platform_device *pdev) const struct ctcu_config *cfgs; struct ctcu_drvdata *drvdata; void __iomem *base; - int i; + int i, ret; desc.name = coresight_alloc_device_name(&ctcu_devs, dev); if (!desc.name) @@ -207,9 +207,9 @@ static int ctcu_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); - drvdata->apb_clk = coresight_get_enable_apb_pclk(dev); - if (IS_ERR(drvdata->apb_clk)) - return -ENODEV; + ret = coresight_get_enable_clocks(dev, &drvdata->apb_clk, NULL); + if (ret) + return ret; cfgs = of_device_get_match_data(dev); if (cfgs) { @@ -233,12 +233,8 @@ static int ctcu_probe(struct platform_device *pdev) desc.access = CSDEV_ACCESS_IOMEM(base); drvdata->csdev = coresight_register(&desc); - if (IS_ERR(drvdata->csdev)) { - if (!IS_ERR_OR_NULL(drvdata->apb_clk)) - clk_put(drvdata->apb_clk); - + if (IS_ERR(drvdata->csdev)) return PTR_ERR(drvdata->csdev); - } return 0; } @@ -275,8 +271,6 @@ static void ctcu_platform_remove(struct platform_device *pdev) ctcu_remove(pdev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->apb_clk)) - clk_put(drvdata->apb_clk); } #ifdef CONFIG_PM @@ -284,8 +278,7 @@ static int ctcu_runtime_suspend(struct device *dev) { struct ctcu_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR_OR_NULL(drvdata->apb_clk)) - clk_disable_unprepare(drvdata->apb_clk); + clk_disable_unprepare(drvdata->apb_clk); return 0; } @@ -294,10 +287,7 @@ static int ctcu_runtime_resume(struct device *dev) { struct ctcu_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR_OR_NULL(drvdata->apb_clk)) - clk_prepare_enable(drvdata->apb_clk); - - return 0; + return clk_prepare_enable(drvdata->apb_clk); } #endif diff --git a/drivers/hwtracing/coresight/coresight-etb10.c b/drivers/hwtracing/coresight/coresight-etb10.c index d5efb085b30d..35db1b6093d1 100644 --- a/drivers/hwtracing/coresight/coresight-etb10.c +++ b/drivers/hwtracing/coresight/coresight-etb10.c @@ -730,12 +730,10 @@ static int etb_probe(struct amba_device *adev, const struct amba_id *id) if (!drvdata) return -ENOMEM; - drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */ - if (!IS_ERR(drvdata->atclk)) { - ret = clk_prepare_enable(drvdata->atclk); - if (ret) - return ret; - } + drvdata->atclk = devm_clk_get_optional_enabled(dev, "atclk"); + if (IS_ERR(drvdata->atclk)) + return PTR_ERR(drvdata->atclk); + dev_set_drvdata(dev, drvdata); /* validity for the resource is already checked by the AMBA core */ @@ -811,8 +809,7 @@ static int etb_runtime_suspend(struct device *dev) { struct etb_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->atclk); return 0; } @@ -821,10 +818,7 @@ static int etb_runtime_resume(struct device *dev) { struct etb_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_prepare_enable(drvdata->atclk); - - return 0; + return clk_prepare_enable(drvdata->atclk); } #endif diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c index f1551c08ecb2..f677c08233ba 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.c +++ b/drivers/hwtracing/coresight/coresight-etm-perf.c @@ -851,7 +851,7 @@ static ssize_t etm_perf_sink_name_show(struct device *dev, struct dev_ext_attribute *ea; ea = container_of(dattr, struct dev_ext_attribute, attr); - return scnprintf(buf, PAGE_SIZE, "0x%lx\n", (unsigned long)(ea->var)); + return scnprintf(buf, PAGE_SIZE, "0x%px\n", ea->var); } static struct dev_ext_attribute * @@ -943,7 +943,7 @@ static ssize_t etm_perf_cscfg_event_show(struct device *dev, struct dev_ext_attribute *ea; ea = container_of(dattr, struct dev_ext_attribute, attr); - return scnprintf(buf, PAGE_SIZE, "configid=0x%lx\n", (unsigned long)(ea->var)); + return scnprintf(buf, PAGE_SIZE, "configid=0x%px\n", ea->var); } int etm_perf_add_symlink_cscfg(struct device *dev, struct cscfg_config_desc *config_desc) diff --git a/drivers/hwtracing/coresight/coresight-etm3x-core.c b/drivers/hwtracing/coresight/coresight-etm3x-core.c index 1c6204e14422..45630a1cd32f 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-core.c @@ -832,12 +832,9 @@ static int etm_probe(struct amba_device *adev, const struct amba_id *id) spin_lock_init(&drvdata->spinlock); - drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */ - if (!IS_ERR(drvdata->atclk)) { - ret = clk_prepare_enable(drvdata->atclk); - if (ret) - return ret; - } + drvdata->atclk = devm_clk_get_optional_enabled(dev, "atclk"); + if (IS_ERR(drvdata->atclk)) + return PTR_ERR(drvdata->atclk); drvdata->cpu = coresight_get_cpu(dev); if (drvdata->cpu < 0) @@ -928,8 +925,7 @@ static int etm_runtime_suspend(struct device *dev) { struct etm_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->atclk); return 0; } @@ -938,10 +934,7 @@ static int etm_runtime_resume(struct device *dev) { struct etm_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_prepare_enable(drvdata->atclk); - - return 0; + return clk_prepare_enable(drvdata->atclk); } #endif diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index 42e5d37403ad..020f070bf17d 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -4,6 +4,7 @@ */ #include <linux/acpi.h> +#include <linux/bitfield.h> #include <linux/bitops.h> #include <linux/kernel.h> #include <linux/kvm_host.h> @@ -528,7 +529,8 @@ static int etm4_enable_hw(struct etmv4_drvdata *drvdata) etm4x_relaxed_write32(csa, config->seq_rst, TRCSEQRSTEVR); etm4x_relaxed_write32(csa, config->seq_state, TRCSEQSTR); } - etm4x_relaxed_write32(csa, config->ext_inp, TRCEXTINSELR); + 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)); @@ -1423,6 +1425,7 @@ static void etm4_init_arch_data(void *info) 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); /* TRACEIDSIZE, bits[21:16] indicates the trace ID width */ drvdata->trcid_size = FIELD_GET(TRCIDR5_TRACEIDSIZE_MASK, etmidr5); /* ATBTRIG, bit[22] implementation can support ATB triggers? */ @@ -1852,7 +1855,9 @@ static int __etm4_cpu_save(struct etmv4_drvdata *drvdata) state->trcseqrstevr = etm4x_read32(csa, TRCSEQRSTEVR); state->trcseqstr = etm4x_read32(csa, TRCSEQSTR); } - state->trcextinselr = etm4x_read32(csa, TRCEXTINSELR); + + if (drvdata->numextinsel) + state->trcextinselr = etm4x_read32(csa, TRCEXTINSELR); for (i = 0; i < drvdata->nr_cntr; i++) { state->trccntrldvr[i] = etm4x_read32(csa, TRCCNTRLDVRn(i)); @@ -1984,7 +1989,8 @@ static void __etm4_cpu_restore(struct etmv4_drvdata *drvdata) etm4x_relaxed_write32(csa, state->trcseqrstevr, TRCSEQRSTEVR); etm4x_relaxed_write32(csa, state->trcseqstr, TRCSEQSTR); } - etm4x_relaxed_write32(csa, state->trcextinselr, TRCEXTINSELR); + if (drvdata->numextinsel) + etm4x_relaxed_write32(csa, state->trcextinselr, TRCEXTINSELR); for (i = 0; i < drvdata->nr_cntr; i++) { etm4x_relaxed_write32(csa, state->trccntrldvr[i], TRCCNTRLDVRn(i)); @@ -2211,10 +2217,15 @@ static int etm4_probe(struct device *dev) struct csdev_access access = { 0 }; struct etm4_init_arg init_arg = { 0 }; struct etm4_init_arg *delayed; + int ret; if (WARN_ON(!drvdata)) return -ENOMEM; + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; + if (pm_save_enable == PARAM_PM_SAVE_FIRMWARE) pm_save_enable = coresight_loses_context_with_cpu(dev) ? PARAM_PM_SAVE_SELF_HOSTED : PARAM_PM_SAVE_NEVER; @@ -2297,16 +2308,10 @@ static int etm4_probe_platform_dev(struct platform_device *pdev) if (!drvdata) return -ENOMEM; - drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; - if (res) { drvdata->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(drvdata->base)) { - clk_put(drvdata->pclk); + if (IS_ERR(drvdata->base)) return PTR_ERR(drvdata->base); - } } dev_set_drvdata(&pdev->dev, drvdata); @@ -2413,9 +2418,6 @@ static void etm4_remove_platform_dev(struct platform_device *pdev) if (drvdata) etm4_remove_dev(drvdata); pm_runtime_disable(&pdev->dev); - - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } static const struct amba_id etm4_ids[] = { @@ -2463,8 +2465,8 @@ static int etm4_runtime_suspend(struct device *dev) { struct etmv4_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata->pclk && !IS_ERR(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); return 0; } @@ -2472,11 +2474,17 @@ static int etm4_runtime_suspend(struct device *dev) static int etm4_runtime_resume(struct device *dev) { struct etmv4_drvdata *drvdata = dev_get_drvdata(dev); + int ret; - if (drvdata->pclk && !IS_ERR(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; - return 0; + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + + return ret; } #endif diff --git a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c index ab251865b893..e9eeea6240d5 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c @@ -4,6 +4,7 @@ * Author: Mathieu Poirier <mathieu.poirier@linaro.org> */ +#include <linux/bitfield.h> #include <linux/coresight.h> #include <linux/pid_namespace.h> #include <linux/pm_runtime.h> diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h index ac649515054d..13ec9ecef46f 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.h +++ b/drivers/hwtracing/coresight/coresight-etm4x.h @@ -162,6 +162,7 @@ #define TRCIDR4_NUMVMIDC_MASK GENMASK(31, 28) #define TRCIDR5_NUMEXTIN_MASK GENMASK(8, 0) +#define TRCIDR5_NUMEXTINSEL_MASK GENMASK(11, 9) #define TRCIDR5_TRACEIDSIZE_MASK GENMASK(21, 16) #define TRCIDR5_ATBTRIG BIT(22) #define TRCIDR5_LPOVERRIDE BIT(23) @@ -919,7 +920,8 @@ struct etmv4_save_state { /** * struct etm4_drvdata - specifics associated to an ETM component - * @pclk APB clock if present, otherwise NULL + * @pclk: APB clock if present, otherwise NULL + * @atclk: Optional clock for the core parts of the ETMv4. * @base: Memory mapped base address for this component. * @csdev: Component vitals needed by the framework. * @spinlock: Only one at a time pls. @@ -988,6 +990,7 @@ struct etmv4_save_state { */ struct etmv4_drvdata { struct clk *pclk; + struct clk *atclk; void __iomem *base; struct coresight_device *csdev; raw_spinlock_t spinlock; @@ -999,6 +1002,7 @@ struct etmv4_drvdata { u8 nr_cntr; u8 nr_ext_inp; u8 numcidc; + u8 numextinsel; u8 numvmidc; u8 nrseqstate; u8 nr_event; diff --git a/drivers/hwtracing/coresight/coresight-funnel.c b/drivers/hwtracing/coresight/coresight-funnel.c index b1922dbe9292..3b248e54471a 100644 --- a/drivers/hwtracing/coresight/coresight-funnel.c +++ b/drivers/hwtracing/coresight/coresight-funnel.c @@ -213,11 +213,11 @@ ATTRIBUTE_GROUPS(coresight_funnel); static int funnel_probe(struct device *dev, struct resource *res) { - int ret; void __iomem *base; struct coresight_platform_data *pdata = NULL; struct funnel_drvdata *drvdata; struct coresight_desc desc = { 0 }; + int ret; if (is_of_node(dev_fwnode(dev)) && of_device_is_compatible(dev->of_node, "arm,coresight-funnel")) @@ -231,16 +231,9 @@ static int funnel_probe(struct device *dev, struct resource *res) if (!drvdata) return -ENOMEM; - drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */ - if (!IS_ERR(drvdata->atclk)) { - ret = clk_prepare_enable(drvdata->atclk); - if (ret) - return ret; - } - - drvdata->pclk = coresight_get_enable_apb_pclk(dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; /* * Map the device base for dynamic-funnel, which has been @@ -248,10 +241,8 @@ static int funnel_probe(struct device *dev, struct resource *res) */ if (res) { base = devm_ioremap_resource(dev, res); - if (IS_ERR(base)) { - ret = PTR_ERR(base); - goto out_disable_clk; - } + if (IS_ERR(base)) + return PTR_ERR(base); drvdata->base = base; desc.groups = coresight_funnel_groups; desc.access = CSDEV_ACCESS_IOMEM(base); @@ -261,10 +252,9 @@ static int funnel_probe(struct device *dev, struct resource *res) dev_set_drvdata(dev, drvdata); pdata = coresight_get_platform_data(dev); - if (IS_ERR(pdata)) { - ret = PTR_ERR(pdata); - goto out_disable_clk; - } + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + dev->platform_data = pdata; raw_spin_lock_init(&drvdata->spinlock); @@ -274,19 +264,10 @@ static int funnel_probe(struct device *dev, struct resource *res) desc.pdata = pdata; desc.dev = dev; drvdata->csdev = coresight_register(&desc); - if (IS_ERR(drvdata->csdev)) { - ret = PTR_ERR(drvdata->csdev); - goto out_disable_clk; - } + if (IS_ERR(drvdata->csdev)) + return PTR_ERR(drvdata->csdev); - ret = 0; - -out_disable_clk: - if (ret && !IS_ERR_OR_NULL(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); - if (ret && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); - return ret; + return 0; } static int funnel_remove(struct device *dev) @@ -303,11 +284,8 @@ static int funnel_runtime_suspend(struct device *dev) { struct funnel_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); - - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); return 0; } @@ -315,13 +293,17 @@ static int funnel_runtime_suspend(struct device *dev) static int funnel_runtime_resume(struct device *dev) { struct funnel_drvdata *drvdata = dev_get_drvdata(dev); + int ret; - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_prepare_enable(drvdata->atclk); + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + + return ret; } #endif @@ -355,8 +337,6 @@ static void funnel_platform_remove(struct platform_device *pdev) funnel_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } static const struct of_device_id funnel_match[] = { diff --git a/drivers/hwtracing/coresight/coresight-replicator.c b/drivers/hwtracing/coresight/coresight-replicator.c index 06efd2b01a0f..e6472658235d 100644 --- a/drivers/hwtracing/coresight/coresight-replicator.c +++ b/drivers/hwtracing/coresight/coresight-replicator.c @@ -219,11 +219,11 @@ static const struct attribute_group *replicator_groups[] = { static int replicator_probe(struct device *dev, struct resource *res) { - int ret = 0; struct coresight_platform_data *pdata = NULL; struct replicator_drvdata *drvdata; struct coresight_desc desc = { 0 }; void __iomem *base; + int ret; if (is_of_node(dev_fwnode(dev)) && of_device_is_compatible(dev->of_node, "arm,coresight-replicator")) @@ -238,16 +238,9 @@ static int replicator_probe(struct device *dev, struct resource *res) if (!drvdata) return -ENOMEM; - drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */ - if (!IS_ERR(drvdata->atclk)) { - ret = clk_prepare_enable(drvdata->atclk); - if (ret) - return ret; - } - - drvdata->pclk = coresight_get_enable_apb_pclk(dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; /* * Map the device base for dynamic-replicator, which has been @@ -255,10 +248,8 @@ static int replicator_probe(struct device *dev, struct resource *res) */ if (res) { base = devm_ioremap_resource(dev, res); - if (IS_ERR(base)) { - ret = PTR_ERR(base); - goto out_disable_clk; - } + if (IS_ERR(base)) + return PTR_ERR(base); drvdata->base = base; desc.groups = replicator_groups; desc.access = CSDEV_ACCESS_IOMEM(base); @@ -272,10 +263,8 @@ static int replicator_probe(struct device *dev, struct resource *res) dev_set_drvdata(dev, drvdata); pdata = coresight_get_platform_data(dev); - if (IS_ERR(pdata)) { - ret = PTR_ERR(pdata); - goto out_disable_clk; - } + if (IS_ERR(pdata)) + return PTR_ERR(pdata); dev->platform_data = pdata; raw_spin_lock_init(&drvdata->spinlock); @@ -286,19 +275,11 @@ static int replicator_probe(struct device *dev, struct resource *res) desc.dev = dev; drvdata->csdev = coresight_register(&desc); - if (IS_ERR(drvdata->csdev)) { - ret = PTR_ERR(drvdata->csdev); - goto out_disable_clk; - } + if (IS_ERR(drvdata->csdev)) + return PTR_ERR(drvdata->csdev); replicator_reset(drvdata); - -out_disable_clk: - if (ret && !IS_ERR_OR_NULL(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); - if (ret && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); - return ret; + return 0; } static int replicator_remove(struct device *dev) @@ -335,8 +316,6 @@ static void replicator_platform_remove(struct platform_device *pdev) replicator_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } #ifdef CONFIG_PM @@ -344,24 +323,26 @@ static int replicator_runtime_suspend(struct device *dev) { struct replicator_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); return 0; } static int replicator_runtime_resume(struct device *dev) { struct replicator_drvdata *drvdata = dev_get_drvdata(dev); + int ret; - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_prepare_enable(drvdata->atclk); + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + + return ret; } #endif diff --git a/drivers/hwtracing/coresight/coresight-stm.c b/drivers/hwtracing/coresight/coresight-stm.c index e45c6c7204b4..e68529bf89c9 100644 --- a/drivers/hwtracing/coresight/coresight-stm.c +++ b/drivers/hwtracing/coresight/coresight-stm.c @@ -342,7 +342,7 @@ static int stm_generic_link(struct stm_data *stm_data, { struct stm_drvdata *drvdata = container_of(stm_data, struct stm_drvdata, stm); - if (!drvdata || !drvdata->csdev) + if (!drvdata->csdev) return -EINVAL; return coresight_enable_sysfs(drvdata->csdev); @@ -353,7 +353,7 @@ static void stm_generic_unlink(struct stm_data *stm_data, { struct stm_drvdata *drvdata = container_of(stm_data, struct stm_drvdata, stm); - if (!drvdata || !drvdata->csdev) + if (!drvdata->csdev) return; coresight_disable_sysfs(drvdata->csdev); @@ -384,7 +384,7 @@ static long stm_generic_set_options(struct stm_data *stm_data, { struct stm_drvdata *drvdata = container_of(stm_data, struct stm_drvdata, stm); - if (!(drvdata && coresight_get_mode(drvdata->csdev))) + if (!coresight_get_mode(drvdata->csdev)) return -EINVAL; if (channel >= drvdata->numsp) @@ -419,7 +419,7 @@ static ssize_t notrace stm_generic_packet(struct stm_data *stm_data, struct stm_drvdata, stm); unsigned int stm_flags; - if (!(drvdata && coresight_get_mode(drvdata->csdev))) + if (!coresight_get_mode(drvdata->csdev)) return -EACCES; if (channel >= drvdata->numsp) @@ -842,16 +842,10 @@ static int __stm_probe(struct device *dev, struct resource *res) if (!drvdata) return -ENOMEM; - drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */ - if (!IS_ERR(drvdata->atclk)) { - ret = clk_prepare_enable(drvdata->atclk); - if (ret) - return ret; - } + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; - drvdata->pclk = coresight_get_enable_apb_pclk(dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; dev_set_drvdata(dev, drvdata); base = devm_ioremap_resource(dev, res); @@ -963,24 +957,26 @@ static int stm_runtime_suspend(struct device *dev) { struct stm_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); return 0; } static int stm_runtime_resume(struct device *dev) { struct stm_drvdata *drvdata = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_prepare_enable(drvdata->atclk); + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + return ret; } #endif @@ -1033,8 +1029,6 @@ static void stm_platform_remove(struct platform_device *pdev) __stm_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } #ifdef CONFIG_ACPI diff --git a/drivers/hwtracing/coresight/coresight-syscfg.c b/drivers/hwtracing/coresight/coresight-syscfg.c index 83dad24e0116..6836b05986e8 100644 --- a/drivers/hwtracing/coresight/coresight-syscfg.c +++ b/drivers/hwtracing/coresight/coresight-syscfg.c @@ -395,7 +395,7 @@ static void cscfg_remove_owned_csdev_configs(struct coresight_device *csdev, voi if (list_empty(&csdev->config_csdev_list)) return; - guard(raw_spinlock_irqsave)(&csdev->cscfg_csdev_lock); + guard(raw_spinlock_irqsave)(&csdev->cscfg_csdev_lock); list_for_each_entry_safe(config_csdev, tmp, &csdev->config_csdev_list, node) { if (config_csdev->config_desc->load_owner == load_owner) diff --git a/drivers/hwtracing/coresight/coresight-sysfs.c b/drivers/hwtracing/coresight/coresight-sysfs.c index feadaf065b53..5e52324aa9ac 100644 --- a/drivers/hwtracing/coresight/coresight-sysfs.c +++ b/drivers/hwtracing/coresight/coresight-sysfs.c @@ -7,6 +7,7 @@ #include <linux/device.h> #include <linux/idr.h> #include <linux/kernel.h> +#include <linux/property.h> #include "coresight-priv.h" #include "coresight-trace-id.h" @@ -371,17 +372,81 @@ static ssize_t enable_source_store(struct device *dev, } static DEVICE_ATTR_RW(enable_source); +static ssize_t label_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + const char *str; + int ret; + + ret = fwnode_property_read_string(dev_fwnode(dev), "label", &str); + if (ret == 0) + return sysfs_emit(buf, "%s\n", str); + else + return ret; +} +static DEVICE_ATTR_RO(label); + +static umode_t label_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = kobj_to_dev(kobj); + + if (attr == &dev_attr_label.attr) { + if (fwnode_property_present(dev_fwnode(dev), "label")) + return attr->mode; + else + return 0; + } + + return attr->mode; +} + static struct attribute *coresight_sink_attrs[] = { &dev_attr_enable_sink.attr, + &dev_attr_label.attr, NULL, }; -ATTRIBUTE_GROUPS(coresight_sink); + +static struct attribute_group coresight_sink_group = { + .attrs = coresight_sink_attrs, + .is_visible = label_is_visible, +}; +__ATTRIBUTE_GROUPS(coresight_sink); static struct attribute *coresight_source_attrs[] = { &dev_attr_enable_source.attr, + &dev_attr_label.attr, NULL, }; -ATTRIBUTE_GROUPS(coresight_source); + +static struct attribute_group coresight_source_group = { + .attrs = coresight_source_attrs, + .is_visible = label_is_visible, +}; +__ATTRIBUTE_GROUPS(coresight_source); + +static struct attribute *coresight_link_attrs[] = { + &dev_attr_label.attr, + NULL, +}; + +static struct attribute_group coresight_link_group = { + .attrs = coresight_link_attrs, + .is_visible = label_is_visible, +}; +__ATTRIBUTE_GROUPS(coresight_link); + +static struct attribute *coresight_helper_attrs[] = { + &dev_attr_label.attr, + NULL, +}; + +static struct attribute_group coresight_helper_group = { + .attrs = coresight_helper_attrs, + .is_visible = label_is_visible, +}; +__ATTRIBUTE_GROUPS(coresight_helper); const struct device_type coresight_dev_type[] = { [CORESIGHT_DEV_TYPE_SINK] = { @@ -390,6 +455,7 @@ const struct device_type coresight_dev_type[] = { }, [CORESIGHT_DEV_TYPE_LINK] = { .name = "link", + .groups = coresight_link_groups, }, [CORESIGHT_DEV_TYPE_LINKSINK] = { .name = "linksink", @@ -401,6 +467,7 @@ const struct device_type coresight_dev_type[] = { }, [CORESIGHT_DEV_TYPE_HELPER] = { .name = "helper", + .groups = coresight_helper_groups, } }; /* Ensure the enum matches the names and groups */ diff --git a/drivers/hwtracing/coresight/coresight-tmc-core.c b/drivers/hwtracing/coresight/coresight-tmc-core.c index 88afb16bb6be..36599c431be6 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-core.c +++ b/drivers/hwtracing/coresight/coresight-tmc-core.c @@ -24,6 +24,7 @@ #include <linux/pm_runtime.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/of_reserved_mem.h> #include <linux/coresight.h> #include <linux/amba/bus.h> #include <linux/platform_device.h> @@ -634,25 +635,14 @@ static int of_tmc_get_reserved_resource_by_name(struct device *dev, const char *name, struct resource *res) { - int index, rc = -ENODEV; - struct device_node *node; + int rc = -ENODEV; - if (!is_of_node(dev->fwnode)) - return -ENODEV; - - index = of_property_match_string(dev->of_node, "memory-region-names", - name); - if (index < 0) - return rc; - - node = of_parse_phandle(dev->of_node, "memory-region", index); - if (!node) + rc = of_reserved_mem_region_to_resource_byname(dev->of_node, name, res); + if (rc < 0) return rc; - if (!of_address_to_resource(node, 0, res) && - res->start != 0 && resource_size(res) != 0) - rc = 0; - of_node_put(node); + if (res->start == 0 || resource_size(res) == 0) + rc = -ENODEV; return rc; } @@ -785,10 +775,20 @@ static int __tmc_probe(struct device *dev, struct resource *res) u32 devid; void __iomem *base; struct coresight_platform_data *pdata = NULL; - struct tmc_drvdata *drvdata = dev_get_drvdata(dev); + struct tmc_drvdata *drvdata; struct coresight_desc desc = { 0 }; struct coresight_dev_list *dev_list = NULL; + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + dev_set_drvdata(dev, drvdata); + + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; + ret = -ENOMEM; /* Validity for the resource is already checked by the AMBA core */ @@ -894,14 +894,8 @@ out: static int tmc_probe(struct amba_device *adev, const struct amba_id *id) { - struct tmc_drvdata *drvdata; int ret; - drvdata = devm_kzalloc(&adev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - amba_set_drvdata(adev, drvdata); ret = __tmc_probe(&adev->dev, &adev->res); if (!ret) pm_runtime_put(&adev->dev); @@ -978,18 +972,8 @@ static struct amba_driver tmc_driver = { static int tmc_platform_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - struct tmc_drvdata *drvdata; int ret = 0; - drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; - - dev_set_drvdata(&pdev->dev, drvdata); pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); @@ -1011,8 +995,6 @@ static void tmc_platform_remove(struct platform_device *pdev) __tmc_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } #ifdef CONFIG_PM @@ -1020,18 +1002,26 @@ static int tmc_runtime_suspend(struct device *dev) { struct tmc_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); + return 0; } static int tmc_runtime_resume(struct device *dev) { struct tmc_drvdata *drvdata = dev_get_drvdata(dev); + int ret; - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; + + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + + return ret; } #endif diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index 6541a27a018e..cbb4ba439158 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -210,6 +210,7 @@ struct tmc_resrv_buf { /** * struct tmc_drvdata - specifics associated to an TMC component + * @atclk: optional clock for the core parts of the TMC. * @pclk: APB clock if present, otherwise NULL * @base: memory mapped base address for this component. * @csdev: component vitals needed by the framework. @@ -244,6 +245,7 @@ struct tmc_resrv_buf { * Used by ETR/ETF. */ struct tmc_drvdata { + struct clk *atclk; struct clk *pclk; void __iomem *base; struct coresight_device *csdev; diff --git a/drivers/hwtracing/coresight/coresight-tnoc.c b/drivers/hwtracing/coresight/coresight-tnoc.c new file mode 100644 index 000000000000..ff9a0a9cfe96 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-tnoc.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + + #include <linux/amba/bus.h> + #include <linux/coresight.h> + #include <linux/device.h> + #include <linux/io.h> + #include <linux/kernel.h> + #include <linux/module.h> + #include <linux/of.h> + #include <linux/platform_device.h> + +#include "coresight-priv.h" +#include "coresight-trace-id.h" + +#define TRACE_NOC_CTRL 0x008 +#define TRACE_NOC_XLD 0x010 +#define TRACE_NOC_FREQVAL 0x018 +#define TRACE_NOC_SYNCR 0x020 + +/* Enable generation of output ATB traffic.*/ +#define TRACE_NOC_CTRL_PORTEN BIT(0) +/* Sets the type of issued ATB FLAG packets.*/ +#define TRACE_NOC_CTRL_FLAGTYPE BIT(7) +/* Sets the type of issued ATB FREQ packet*/ +#define TRACE_NOC_CTRL_FREQTYPE BIT(8) + +#define TRACE_NOC_SYNC_INTERVAL 0xFFFF + +/* + * struct trace_noc_drvdata - specifics associated to a trace noc component + * @base: memory mapped base address for this component. + * @dev: device node for trace_noc_drvdata. + * @csdev: component vitals needed by the framework. + * @spinlock: serialize enable/disable operation. + * @atid: id for the trace packet. + */ +struct trace_noc_drvdata { + void __iomem *base; + struct device *dev; + struct coresight_device *csdev; + spinlock_t spinlock; + u32 atid; +}; + +DEFINE_CORESIGHT_DEVLIST(trace_noc_devs, "traceNoc"); + +static void trace_noc_enable_hw(struct trace_noc_drvdata *drvdata) +{ + u32 val; + + /* Set ATID */ + writel_relaxed(drvdata->atid, drvdata->base + TRACE_NOC_XLD); + + /* Set the data word count between 'SYNC' packets */ + writel_relaxed(TRACE_NOC_SYNC_INTERVAL, drvdata->base + TRACE_NOC_SYNCR); + + /* Set the Control register: + * - Set the FLAG packets to 'FLAG' packets + * - Set the FREQ packets to 'FREQ_TS' packets + * - Enable generation of output ATB traffic + */ + + val = readl_relaxed(drvdata->base + TRACE_NOC_CTRL); + + val &= ~TRACE_NOC_CTRL_FLAGTYPE; + val |= TRACE_NOC_CTRL_FREQTYPE; + val |= TRACE_NOC_CTRL_PORTEN; + + writel(val, drvdata->base + TRACE_NOC_CTRL); +} + +static int trace_noc_enable(struct coresight_device *csdev, struct coresight_connection *inport, + struct coresight_connection *outport) +{ + struct trace_noc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + scoped_guard(spinlock, &drvdata->spinlock) { + if (csdev->refcnt == 0) + trace_noc_enable_hw(drvdata); + + csdev->refcnt++; + } + + dev_dbg(drvdata->dev, "Trace NOC is enabled\n"); + return 0; +} + +static void trace_noc_disable(struct coresight_device *csdev, struct coresight_connection *inport, + struct coresight_connection *outport) +{ + struct trace_noc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + scoped_guard(spinlock, &drvdata->spinlock) { + if (--csdev->refcnt == 0) + writel(0x0, drvdata->base + TRACE_NOC_CTRL); + } + dev_dbg(drvdata->dev, "Trace NOC is disabled\n"); +} + +static int trace_noc_id(struct coresight_device *csdev, __maybe_unused enum cs_mode mode, + __maybe_unused struct coresight_device *sink) +{ + struct trace_noc_drvdata *drvdata; + + drvdata = dev_get_drvdata(csdev->dev.parent); + + return drvdata->atid; +} + +static const struct coresight_ops_link trace_noc_link_ops = { + .enable = trace_noc_enable, + .disable = trace_noc_disable, +}; + +static const struct coresight_ops trace_noc_cs_ops = { + .trace_id = trace_noc_id, + .link_ops = &trace_noc_link_ops, +}; + +static int trace_noc_init_default_data(struct trace_noc_drvdata *drvdata) +{ + int atid; + + atid = coresight_trace_id_get_system_id(); + if (atid < 0) + return atid; + + drvdata->atid = atid; + + return 0; +} + +static ssize_t traceid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->atid; + return sprintf(buf, "%#lx\n", val); +} +static DEVICE_ATTR_RO(traceid); + +static struct attribute *coresight_tnoc_attrs[] = { + &dev_attr_traceid.attr, + NULL, +}; + +static const struct attribute_group coresight_tnoc_group = { + .attrs = coresight_tnoc_attrs, +}; + +static const struct attribute_group *coresight_tnoc_groups[] = { + &coresight_tnoc_group, + NULL, +}; + +static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id) +{ + struct device *dev = &adev->dev; + struct coresight_platform_data *pdata; + struct trace_noc_drvdata *drvdata; + struct coresight_desc desc = { 0 }; + int ret; + + desc.name = coresight_alloc_device_name(&trace_noc_devs, dev); + if (!desc.name) + return -ENOMEM; + + pdata = coresight_get_platform_data(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + adev->dev.platform_data = pdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->dev = &adev->dev; + dev_set_drvdata(dev, drvdata); + + drvdata->base = devm_ioremap_resource(dev, &adev->res); + if (IS_ERR(drvdata->base)) + return PTR_ERR(drvdata->base); + + spin_lock_init(&drvdata->spinlock); + + ret = trace_noc_init_default_data(drvdata); + if (ret) + return ret; + + desc.ops = &trace_noc_cs_ops; + desc.type = CORESIGHT_DEV_TYPE_LINK; + desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG; + desc.pdata = adev->dev.platform_data; + desc.dev = &adev->dev; + desc.access = CSDEV_ACCESS_IOMEM(drvdata->base); + desc.groups = coresight_tnoc_groups; + drvdata->csdev = coresight_register(&desc); + if (IS_ERR(drvdata->csdev)) { + coresight_trace_id_put_system_id(drvdata->atid); + return PTR_ERR(drvdata->csdev); + } + pm_runtime_put(&adev->dev); + + return 0; +} + +static void trace_noc_remove(struct amba_device *adev) +{ + struct trace_noc_drvdata *drvdata = dev_get_drvdata(&adev->dev); + + coresight_unregister(drvdata->csdev); + coresight_trace_id_put_system_id(drvdata->atid); +} + +static struct amba_id trace_noc_ids[] = { + { + .id = 0x000f0c00, + .mask = 0x00ffff00, + }, + { + .id = 0x001f0c00, + .mask = 0x00ffff00, + }, + {}, +}; +MODULE_DEVICE_TABLE(amba, trace_noc_ids); + +static struct amba_driver trace_noc_driver = { + .drv = { + .name = "coresight-trace-noc", + .suppress_bind_attrs = true, + }, + .probe = trace_noc_probe, + .remove = trace_noc_remove, + .id_table = trace_noc_ids, +}; + +module_amba_driver(trace_noc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Trace NOC driver"); diff --git a/drivers/hwtracing/coresight/coresight-tpda.c b/drivers/hwtracing/coresight/coresight-tpda.c index 0633f04beb24..333b3cb23685 100644 --- a/drivers/hwtracing/coresight/coresight-tpda.c +++ b/drivers/hwtracing/coresight/coresight-tpda.c @@ -71,6 +71,8 @@ static int tpdm_read_element_size(struct tpda_drvdata *drvdata, if (tpdm_data->dsb) { rc = fwnode_property_read_u32(dev_fwnode(csdev->dev.parent), "qcom,dsb-element-bits", &drvdata->dsb_esize); + if (rc) + goto out; } if (tpdm_data->cmb) { @@ -78,6 +80,7 @@ static int tpdm_read_element_size(struct tpda_drvdata *drvdata, "qcom,cmb-element-bits", &drvdata->cmb_esize); } +out: if (rc) dev_warn_once(&csdev->dev, "Failed to read TPDM Element size: %d\n", rc); diff --git a/drivers/hwtracing/coresight/coresight-tpiu.c b/drivers/hwtracing/coresight/coresight-tpiu.c index 3e0159288428..9463afdbda8a 100644 --- a/drivers/hwtracing/coresight/coresight-tpiu.c +++ b/drivers/hwtracing/coresight/coresight-tpiu.c @@ -128,11 +128,11 @@ static const struct coresight_ops tpiu_cs_ops = { static int __tpiu_probe(struct device *dev, struct resource *res) { - int ret; void __iomem *base; struct coresight_platform_data *pdata = NULL; struct tpiu_drvdata *drvdata; struct coresight_desc desc = { 0 }; + int ret; desc.name = coresight_alloc_device_name(&tpiu_devs, dev); if (!desc.name) @@ -144,16 +144,10 @@ static int __tpiu_probe(struct device *dev, struct resource *res) spin_lock_init(&drvdata->spinlock); - drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */ - if (!IS_ERR(drvdata->atclk)) { - ret = clk_prepare_enable(drvdata->atclk); - if (ret) - return ret; - } + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; - drvdata->pclk = coresight_get_enable_apb_pclk(dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; dev_set_drvdata(dev, drvdata); /* Validity for the resource is already checked by the AMBA core */ @@ -212,24 +206,26 @@ static int tpiu_runtime_suspend(struct device *dev) { struct tpiu_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); return 0; } static int tpiu_runtime_resume(struct device *dev) { struct tpiu_drvdata *drvdata = dev_get_drvdata(dev); + int ret; - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_prepare_enable(drvdata->atclk); + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + + return ret; } #endif @@ -293,8 +289,6 @@ static void tpiu_platform_remove(struct platform_device *pdev) __tpiu_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } #ifdef CONFIG_ACPI diff --git a/drivers/hwtracing/coresight/coresight-trbe.c b/drivers/hwtracing/coresight/coresight-trbe.c index 8f426f94e32a..43643d2c5bdd 100644 --- a/drivers/hwtracing/coresight/coresight-trbe.c +++ b/drivers/hwtracing/coresight/coresight-trbe.c @@ -258,6 +258,7 @@ static void trbe_drain_and_disable_local(struct trbe_cpudata *cpudata) static void trbe_reset_local(struct trbe_cpudata *cpudata) { write_sysreg_s(0, SYS_TRBLIMITR_EL1); + isb(); trbe_drain_buffer(); write_sysreg_s(0, SYS_TRBPTR_EL1); write_sysreg_s(0, SYS_TRBBASER_EL1); @@ -748,12 +749,12 @@ static void *arm_trbe_alloc_buffer(struct coresight_device *csdev, buf = kzalloc_node(sizeof(*buf), GFP_KERNEL, trbe_alloc_node(event)); if (!buf) - return ERR_PTR(-ENOMEM); + return NULL; pglist = kcalloc(nr_pages, sizeof(*pglist), GFP_KERNEL); if (!pglist) { kfree(buf); - return ERR_PTR(-ENOMEM); + return NULL; } for (i = 0; i < nr_pages; i++) @@ -763,7 +764,7 @@ static void *arm_trbe_alloc_buffer(struct coresight_device *csdev, if (!buf->trbe_base) { kfree(pglist); kfree(buf); - return ERR_PTR(-ENOMEM); + return NULL; } buf->trbe_limit = buf->trbe_base + nr_pages * PAGE_SIZE; buf->trbe_write = buf->trbe_base; @@ -1280,7 +1281,7 @@ static void arm_trbe_register_coresight_cpu(struct trbe_drvdata *drvdata, int cp * into the device for that purpose. */ desc.pdata = devm_kzalloc(dev, sizeof(*desc.pdata), GFP_KERNEL); - if (IS_ERR(desc.pdata)) + if (!desc.pdata) goto cpu_clear; desc.type = CORESIGHT_DEV_TYPE_SINK; diff --git a/drivers/hwtracing/coresight/ultrasoc-smb.h b/drivers/hwtracing/coresight/ultrasoc-smb.h index c4c111275627..323f0ccb6878 100644 --- a/drivers/hwtracing/coresight/ultrasoc-smb.h +++ b/drivers/hwtracing/coresight/ultrasoc-smb.h @@ -7,6 +7,7 @@ #ifndef _ULTRASOC_SMB_H #define _ULTRASOC_SMB_H +#include <linux/bitfield.h> #include <linux/miscdevice.h> #include <linux/spinlock.h> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index ae8fcc864060..fd81e49638aa 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -1368,6 +1368,17 @@ config I2C_NCT6694 This driver can also be built as a module. If so, the module will be called i2c-nct6694. +config I2C_USBIO + tristate "Intel USBIO I2C Adapter support" + depends on USB_USBIO + default USB_USBIO + help + Select this option to enable I2C driver for the INTEL + USBIO driver stack. + + This driver can also be built as a module. If so, the module + will be called i2c_usbio. + config I2C_CP2615 tristate "Silicon Labs CP2615 USB sound card and I2C adapter" depends on USB diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index fe8cf6325fc9..fb985769f5ff 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -136,6 +136,7 @@ obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o obj-$(CONFIG_I2C_LJCA) += i2c-ljca.o obj-$(CONFIG_I2C_NCT6694) += i2c-nct6694.o +obj-$(CONFIG_I2C_USBIO) += i2c-usbio.o obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o obj-$(CONFIG_I2C_PCI1XXXX) += i2c-mchp-pci1xxxx.o diff --git a/drivers/i2c/busses/i2c-usbio.c b/drivers/i2c/busses/i2c-usbio.c new file mode 100644 index 000000000000..d42f9ab6e9a5 --- /dev/null +++ b/drivers/i2c/busses/i2c-usbio.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025 Intel Corporation. + * Copyright (c) 2025 Red Hat, Inc. + */ + +#include <linux/auxiliary_bus.h> +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/usb/usbio.h> + +#define I2C_RW_OVERHEAD (sizeof(struct usbio_bulk_packet) + sizeof(struct usbio_i2c_rw)) + +struct usbio_i2c { + struct i2c_adapter adap; + struct auxiliary_device *adev; + struct usbio_i2c_rw *rwbuf; + unsigned long quirks; + u32 speed; + u16 txbuf_len; + u16 rxbuf_len; +}; + +static const struct acpi_device_id usbio_i2c_acpi_hids[] = { + { "INTC1008" }, /* MTL */ + { "INTC10B3" }, /* ARL */ + { "INTC10B6" }, /* LNL */ + { "INTC10E3" }, /* PTL */ + { } +}; + +static const u32 usbio_i2c_speeds[] = { + I2C_MAX_STANDARD_MODE_FREQ, + I2C_MAX_FAST_MODE_FREQ, + I2C_MAX_FAST_MODE_PLUS_FREQ, + I2C_MAX_HIGH_SPEED_MODE_FREQ +}; + +static void usbio_i2c_uninit(struct i2c_adapter *adap, struct i2c_msg *msg) +{ + struct usbio_i2c *i2c = i2c_get_adapdata(adap); + struct usbio_i2c_uninit ubuf; + + ubuf.busid = i2c->adev->id; + ubuf.config = cpu_to_le16(msg->addr); + + usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, USBIO_I2CCMD_UNINIT, true, + &ubuf, sizeof(ubuf), NULL, 0); +} + +static int usbio_i2c_init(struct i2c_adapter *adap, struct i2c_msg *msg) +{ + struct usbio_i2c *i2c = i2c_get_adapdata(adap); + struct usbio_i2c_init ibuf; + void *reply_buf; + u16 reply_len; + int ret; + + ibuf.busid = i2c->adev->id; + ibuf.config = cpu_to_le16(msg->addr); + ibuf.speed = cpu_to_le32(i2c->speed); + + if (i2c->quirks & USBIO_QUIRK_I2C_NO_INIT_ACK) { + reply_buf = NULL; + reply_len = 0; + } else { + reply_buf = &ibuf; + reply_len = sizeof(ibuf); + } + + ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, USBIO_I2CCMD_INIT, true, + &ibuf, sizeof(ibuf), reply_buf, reply_len); + if (ret != sizeof(ibuf)) + return (ret < 0) ? ret : -EIO; + + return 0; +} + +static int usbio_i2c_read(struct i2c_adapter *adap, struct i2c_msg *msg) +{ + struct usbio_i2c *i2c = i2c_get_adapdata(adap); + u16 rxchunk = i2c->rxbuf_len - I2C_RW_OVERHEAD; + struct usbio_i2c_rw *rbuf = i2c->rwbuf; + int ret; + + rbuf->busid = i2c->adev->id; + rbuf->config = cpu_to_le16(msg->addr); + rbuf->size = cpu_to_le16(msg->len); + + if (msg->len > rxchunk) { + /* Need to split the input buffer */ + u16 len = 0; + + do { + if (msg->len - len < rxchunk) + rxchunk = msg->len - len; + + ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, + USBIO_I2CCMD_READ, true, + rbuf, len == 0 ? sizeof(*rbuf) : 0, + rbuf, sizeof(*rbuf) + rxchunk); + if (ret < 0) + return ret; + + memcpy(&msg->buf[len], rbuf->data, rxchunk); + len += rxchunk; + } while (msg->len > len); + + return 0; + } + + ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, USBIO_I2CCMD_READ, true, + rbuf, sizeof(*rbuf), rbuf, sizeof(*rbuf) + msg->len); + if (ret != sizeof(*rbuf) + msg->len) + return (ret < 0) ? ret : -EIO; + + memcpy(msg->buf, rbuf->data, msg->len); + + return 0; +} + +static int usbio_i2c_write(struct i2c_adapter *adap, struct i2c_msg *msg) +{ + struct usbio_i2c *i2c = i2c_get_adapdata(adap); + u16 txchunk = i2c->txbuf_len - I2C_RW_OVERHEAD; + struct usbio_i2c_rw *wbuf = i2c->rwbuf; + int ret; + + if (msg->len > txchunk) { + /* Need to split the output buffer */ + u16 len = 0; + + do { + wbuf->busid = i2c->adev->id; + wbuf->config = cpu_to_le16(msg->addr); + + if (i2c->quirks & USBIO_QUIRK_I2C_USE_CHUNK_LEN) + wbuf->size = cpu_to_le16(txchunk); + else + wbuf->size = cpu_to_le16(msg->len); + + memcpy(wbuf->data, &msg->buf[len], txchunk); + len += txchunk; + + ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, + USBIO_I2CCMD_WRITE, msg->len == len, + wbuf, sizeof(*wbuf) + txchunk, + wbuf, sizeof(*wbuf)); + if (ret < 0) + return ret; + + if (msg->len - len < txchunk) + txchunk = msg->len - len; + } while (msg->len > len); + + return 0; + } + + wbuf->busid = i2c->adev->id; + wbuf->config = cpu_to_le16(msg->addr); + wbuf->size = cpu_to_le16(msg->len); + memcpy(wbuf->data, msg->buf, msg->len); + + ret = usbio_bulk_msg(i2c->adev, USBIO_PKTTYPE_I2C, USBIO_I2CCMD_WRITE, true, + wbuf, sizeof(*wbuf) + msg->len, wbuf, sizeof(*wbuf)); + if (ret != sizeof(*wbuf) || le16_to_cpu(wbuf->size) != msg->len) + return (ret < 0) ? ret : -EIO; + + return 0; +} + +static int usbio_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + struct usbio_i2c *i2c = i2c_get_adapdata(adap); + int ret; + + usbio_acquire(i2c->adev); + + ret = usbio_i2c_init(adap, msgs); + if (ret) + goto out_release; + + for (int i = 0; i < num; ret = ++i) { + if (msgs[i].flags & I2C_M_RD) + ret = usbio_i2c_read(adap, &msgs[i]); + else + ret = usbio_i2c_write(adap, &msgs[i]); + + if (ret) + break; + } + + usbio_i2c_uninit(adap, msgs); + +out_release: + usbio_release(i2c->adev); + + return ret; +} + +static u32 usbio_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_adapter_quirks usbio_i2c_quirks = { + .flags = I2C_AQ_NO_ZERO_LEN | I2C_AQ_NO_REP_START, + .max_read_len = SZ_4K, + .max_write_len = SZ_4K, +}; + +static const struct i2c_adapter_quirks usbio_i2c_quirks_max_rw_len52 = { + .flags = I2C_AQ_NO_ZERO_LEN | I2C_AQ_NO_REP_START, + .max_read_len = 52, + .max_write_len = 52, +}; + +static const struct i2c_algorithm usbio_i2c_algo = { + .master_xfer = usbio_i2c_xfer, + .functionality = usbio_i2c_func, +}; + +static int usbio_i2c_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *adev_id) +{ + struct usbio_i2c_bus_desc *i2c_desc; + struct device *dev = &adev->dev; + struct usbio_i2c *i2c; + u32 max_speed; + int ret; + + i2c_desc = dev_get_platdata(dev); + if (!i2c_desc) + return -EINVAL; + + i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + i2c->adev = adev; + + usbio_acpi_bind(i2c->adev, usbio_i2c_acpi_hids); + usbio_get_txrxbuf_len(i2c->adev, &i2c->txbuf_len, &i2c->rxbuf_len); + + i2c->rwbuf = devm_kzalloc(dev, max(i2c->txbuf_len, i2c->rxbuf_len), GFP_KERNEL); + if (!i2c->rwbuf) + return -ENOMEM; + + i2c->quirks = usbio_get_quirks(i2c->adev); + + max_speed = usbio_i2c_speeds[i2c_desc->caps & USBIO_I2C_BUS_MODE_CAP_MASK]; + if (max_speed < I2C_MAX_FAST_MODE_FREQ && + (i2c->quirks & USBIO_QUIRK_I2C_ALLOW_400KHZ)) + max_speed = I2C_MAX_FAST_MODE_FREQ; + + i2c->speed = i2c_acpi_find_bus_speed(dev); + if (!i2c->speed) + i2c->speed = I2C_MAX_STANDARD_MODE_FREQ; + else if (i2c->speed > max_speed) { + dev_warn(dev, "Invalid speed %u adjusting to bus max %u\n", + i2c->speed, max_speed); + i2c->speed = max_speed; + } + + i2c->adap.owner = THIS_MODULE; + i2c->adap.class = I2C_CLASS_HWMON; + i2c->adap.dev.parent = dev; + i2c->adap.algo = &usbio_i2c_algo; + + if (i2c->quirks & USBIO_QUIRK_I2C_MAX_RW_LEN_52) + i2c->adap.quirks = &usbio_i2c_quirks_max_rw_len52; + else + i2c->adap.quirks = &usbio_i2c_quirks; + + snprintf(i2c->adap.name, sizeof(i2c->adap.name), "%s.%d", + USBIO_I2C_CLIENT, i2c->adev->id); + + device_set_node(&i2c->adap.dev, dev_fwnode(&adev->dev)); + + auxiliary_set_drvdata(adev, i2c); + i2c_set_adapdata(&i2c->adap, i2c); + + ret = i2c_add_adapter(&i2c->adap); + if (ret) + return ret; + + if (has_acpi_companion(&i2c->adap.dev)) + acpi_dev_clear_dependencies(ACPI_COMPANION(&i2c->adap.dev)); + + return 0; +} + +static void usbio_i2c_remove(struct auxiliary_device *adev) +{ + struct usbio_i2c *i2c = auxiliary_get_drvdata(adev); + + i2c_del_adapter(&i2c->adap); +} + +static const struct auxiliary_device_id usbio_i2c_id_table[] = { + { "usbio.usbio-i2c" }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, usbio_i2c_id_table); + +static struct auxiliary_driver usbio_i2c_driver = { + .name = USBIO_I2C_CLIENT, + .probe = usbio_i2c_probe, + .remove = usbio_i2c_remove, + .id_table = usbio_i2c_id_table +}; +module_auxiliary_driver(usbio_i2c_driver); + +MODULE_DESCRIPTION("Intel USBIO I2C driver"); +MODULE_AUTHOR("Israel Cepeda <israel.a.cepeda.lopez@intel.com>"); +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("USBIO"); diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index b7dfd0007aa0..78e3f799ecc1 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -36,10 +36,29 @@ #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0) #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3) #define ADXL345_REG_TAP_SUPPRESS BIT(3) +#define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK) #define ADXL345_TAP_Z_EN BIT(0) #define ADXL345_TAP_Y_EN BIT(1) #define ADXL345_TAP_X_EN BIT(2) +#define ADXL345_REG_TAP_SUPPRESS BIT(3) + +#define ADXL345_INACT_Z_EN BIT(0) +#define ADXL345_INACT_Y_EN BIT(1) +#define ADXL345_INACT_X_EN BIT(2) +#define ADXL345_REG_INACT_ACDC BIT(3) +#define ADXL345_ACT_INACT_NO_AXIS_EN 0x00 +#define ADXL345_INACT_XYZ_EN (ADXL345_INACT_Z_EN | ADXL345_INACT_Y_EN | ADXL345_INACT_X_EN) + +#define ADXL345_ACT_Z_EN BIT(4) +#define ADXL345_ACT_Y_EN BIT(5) +#define ADXL345_ACT_X_EN BIT(6) +#define ADXL345_REG_ACT_ACDC BIT(7) +#define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN) + +#define ADXL345_COUPLING_DC 0 +#define ADXL345_COUPLING_AC 1 +#define ADXL345_REG_NO_ACDC 0x00 /* single/double tap */ enum adxl345_tap_type { @@ -64,6 +83,39 @@ static const unsigned int adxl345_tap_time_reg[] = { [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR, }; +/* activity/inactivity */ +enum adxl345_activity_type { + ADXL345_ACTIVITY, + ADXL345_INACTIVITY, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC, + ADXL345_INACTIVITY_FF, +}; + +static const unsigned int adxl345_act_int_reg[] = { + [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY, + [ADXL345_INACTIVITY] = ADXL345_INT_INACTIVITY, + [ADXL345_ACTIVITY_AC] = ADXL345_INT_ACTIVITY, + [ADXL345_INACTIVITY_AC] = ADXL345_INT_INACTIVITY, + [ADXL345_INACTIVITY_FF] = ADXL345_INT_FREE_FALL, +}; + +static const unsigned int adxl345_act_thresh_reg[] = { + [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT, + [ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT, + [ADXL345_ACTIVITY_AC] = ADXL345_REG_THRESH_ACT, + [ADXL345_INACTIVITY_AC] = ADXL345_REG_THRESH_INACT, + [ADXL345_INACTIVITY_FF] = ADXL345_REG_THRESH_FF, +}; + +static const unsigned int adxl345_act_acdc_msk[] = { + [ADXL345_ACTIVITY] = ADXL345_REG_ACT_ACDC, + [ADXL345_INACTIVITY] = ADXL345_REG_INACT_ACDC, + [ADXL345_ACTIVITY_AC] = ADXL345_REG_ACT_ACDC, + [ADXL345_INACTIVITY_AC] = ADXL345_REG_INACT_ACDC, + [ADXL345_INACTIVITY_FF] = ADXL345_REG_NO_ACDC, +}; + enum adxl345_odr { ADXL345_ODR_0P10HZ = 0, ADXL345_ODR_0P20HZ, @@ -129,6 +181,14 @@ static const int adxl345_fullres_range_tbl[][2] = { [ADXL345_16G_RANGE] = { 0, 38312 }, }; +/* scaling */ +static const int adxl345_range_factor_tbl[] = { + [ADXL345_2G_RANGE] = 1, + [ADXL345_4G_RANGE] = 2, + [ADXL345_8G_RANGE] = 4, + [ADXL345_16G_RANGE] = 8, +}; + struct adxl345_state { const struct adxl345_chip_info *info; struct regmap *regmap; @@ -136,6 +196,9 @@ struct adxl345_state { u8 watermark; u8 fifo_mode; + u8 inact_threshold; + u32 inact_time_ms; + u32 tap_duration_us; u32 tap_latent_us; u32 tap_window_us; @@ -145,6 +208,22 @@ struct adxl345_state { static const struct iio_event_spec adxl345_events[] = { { + /* activity */ + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, + { + /* activity, ac bit set */ + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, + { /* single tap */ .type = IIO_EV_TYPE_GESTURE, .dir = IIO_EV_DIR_SINGLETAP, @@ -188,10 +267,39 @@ enum adxl345_chans { chan_x, chan_y, chan_z, }; +static const struct iio_event_spec adxl345_fake_chan_events[] = { + { + /* inactivity */ + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, + { + /* inactivity, AC bit set */ + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, +}; + static const struct iio_chan_spec adxl345_channels[] = { ADXL345_CHANNEL(0, chan_x, X), ADXL345_CHANNEL(1, chan_y, Y), ADXL345_CHANNEL(2, chan_z, Z), + { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X_AND_Y_AND_Z, + .scan_index = -1, /* Fake channel */ + .event_spec = adxl345_fake_chan_events, + .num_event_specs = ARRAY_SIZE(adxl345_fake_chan_events), + }, }; static const unsigned long adxl345_scan_masks[] = { @@ -237,6 +345,394 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en) ADXL345_POWER_CTL_MEASURE, en); } +/* activity / inactivity */ + +static int adxl345_set_inact_threshold(struct adxl345_state *st, + unsigned int threshold) +{ + int ret; + + st->inact_threshold = min(U8_MAX, threshold); + + ret = regmap_write(st->regmap, + adxl345_act_thresh_reg[ADXL345_INACTIVITY], + st->inact_threshold); + if (ret) + return ret; + + return regmap_write(st->regmap, + adxl345_act_thresh_reg[ADXL345_INACTIVITY_FF], + st->inact_threshold); +} + +static int adxl345_set_default_time(struct adxl345_state *st) +{ + int max_boundary = U8_MAX; + int min_boundary = 10; + enum adxl345_odr odr; + unsigned int regval; + unsigned int val; + int ret; + + /* Generated inactivity time based on ODR */ + ret = regmap_read(st->regmap, ADXL345_REG_BW_RATE, ®val); + if (ret) + return ret; + + odr = FIELD_GET(ADXL345_BW_RATE_MSK, regval); + val = clamp(max_boundary - adxl345_odr_tbl[odr][0], + min_boundary, max_boundary); + st->inact_time_ms = MILLI * val; + + /* Inactivity time in s */ + return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val); +} + +static int adxl345_set_inactivity_time(struct adxl345_state *st, u32 val_int) +{ + st->inact_time_ms = MILLI * val_int; + + return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val_int); +} + +static int adxl345_set_freefall_time(struct adxl345_state *st, u32 val_fract) +{ + /* + * Datasheet max. value is 255 * 5000 us = 1.275000 seconds. + * + * Recommended values between 100ms and 350ms (0x14 to 0x46) + */ + st->inact_time_ms = DIV_ROUND_UP(val_fract, MILLI); + + return regmap_write(st->regmap, ADXL345_REG_TIME_FF, + DIV_ROUND_CLOSEST(val_fract, 5)); +} + +/** + * adxl345_set_inact_time - Configure inactivity time explicitly or by ODR. + * @st: The sensor state instance. + * @val_int: The inactivity time, integer part. + * @val_fract: The inactivity time, fractional part when val_int is 0. + * + * Inactivity time can be configured between 1 and 255 seconds. If a user sets + * val_s to 0, a default inactivity time is calculated automatically (since 0 is + * also invalid and undefined by the sensor). + * + * In such cases, power consumption should be considered: the inactivity period + * should be shorter at higher sampling frequencies and longer at lower ones. + * Specifically, for frequencies above 255 Hz, the default is set to 10 seconds; + * for frequencies below 10 Hz, it defaults to 255 seconds. + * + * The calculation method subtracts the integer part of the configured sample + * frequency from 255 to estimate the inactivity time in seconds. Sub-Hertz + * values are ignored in this approximation. Since the recommended output data + * rates (ODRs) for features like activity/inactivity detection, sleep modes, + * and free fall range between 12.5 Hz and 400 Hz, frequencies outside this + * range will either use the defined boundary defaults or require explicit + * configuration via val_s. + * + * Return: 0 or error value. + */ +static int adxl345_set_inact_time(struct adxl345_state *st, u32 val_int, + u32 val_fract) +{ + if (val_int > 0) { + /* Time >= 1s, inactivity */ + return adxl345_set_inactivity_time(st, val_int); + } else if (val_int == 0) { + if (val_fract > 0) { + /* Time < 1s, free-fall */ + return adxl345_set_freefall_time(st, val_fract); + } else if (val_fract == 0) { + /* Time == 0.0s */ + return adxl345_set_default_time(st); + } + } + + /* Do not support negative or wrong input. */ + return -EINVAL; +} + +/** + * adxl345_is_act_inact_ac() - Verify if AC or DC coupling is currently enabled. + * + * @st: The device data. + * @type: The activity or inactivity type. + * + * Given a type of activity / inactivity combined with either AC coupling set or + * default to DC, this function verifies if the combination is currently + * configured, hence enabled or not. + * + * Return: true if configured coupling matches the provided type, else a negative + * error value. + */ +static int adxl345_is_act_inact_ac(struct adxl345_state *st, + enum adxl345_activity_type type) +{ + unsigned int regval; + bool coupling; + int ret; + + if (type == ADXL345_INACTIVITY_FF) + return true; + + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val); + if (ret) + return ret; + + coupling = adxl345_act_acdc_msk[type] & regval; + + switch (type) { + case ADXL345_ACTIVITY: + case ADXL345_INACTIVITY: + return coupling == ADXL345_COUPLING_DC; + case ADXL345_ACTIVITY_AC: + case ADXL345_INACTIVITY_AC: + return coupling == ADXL345_COUPLING_AC; + default: + return -EINVAL; + } +} + +/** + * adxl345_set_act_inact_ac() - Configure AC coupling or DC coupling. + * + * @st: The device data. + * @type: Provide a type of activity or inactivity. + * @cmd_en: enable or disable AC coupling. + * + * Enables AC coupling or DC coupling depending on the provided type argument. + * Note: Activity and inactivity can be either AC coupled or DC coupled not + * both at the same time. + * + * Return: 0 if successful, else error value. + */ +static int adxl345_set_act_inact_ac(struct adxl345_state *st, + enum adxl345_activity_type type, + bool cmd_en) +{ + unsigned int act_inact_ac; + + if (type == ADXL345_ACTIVITY_AC || type == ADXL345_INACTIVITY_AC) + act_inact_ac = ADXL345_COUPLING_AC && cmd_en; + else + act_inact_ac = ADXL345_COUPLING_DC && cmd_en; + + /* + * A setting of false selects dc-coupled operation, and a setting of + * true enables ac-coupled operation. In dc-coupled operation, the + * current acceleration magnitude is compared directly with + * ADXL345_REG_THRESH_ACT and ADXL345_REG_THRESH_INACT to determine + * whether activity or inactivity is detected. + * + * In ac-coupled operation for activity detection, the acceleration + * value at the start of activity detection is taken as a reference + * value. New samples of acceleration are then compared to this + * reference value, and if the magnitude of the difference exceeds the + * ADXL345_REG_THRESH_ACT value, the device triggers an activity + * interrupt. + * + * Similarly, in ac-coupled operation for inactivity detection, a + * reference value is used for comparison and is updated whenever the + * device exceeds the inactivity threshold. After the reference value + * is selected, the device compares the magnitude of the difference + * between the reference value and the current acceleration with + * ADXL345_REG_THRESH_INACT. If the difference is less than the value in + * ADXL345_REG_THRESH_INACT for the time in ADXL345_REG_TIME_INACT, the + * device is considered inactive and the inactivity interrupt is + * triggered. [quoted from p. 24, ADXL345 datasheet Rev. G] + * + * In a conclusion, the first acceleration snapshot sample which hit the + * threshold in a particular direction is always taken as acceleration + * reference value to that direction. Since for the hardware activity + * and inactivity depend on the x/y/z axis, so do ac and dc coupling. + * Note, this sw driver always enables or disables all three x/y/z axis + * for detection via act_axis_ctrl and inact_axis_ctrl, respectively. + * Where in dc-coupling samples are compared against the thresholds, in + * ac-coupling measurement difference to the first acceleration + * reference value are compared against the threshold. So, ac-coupling + * allows for a bit more dynamic compensation depending on the initial + * sample. + */ + return regmap_assign_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL, + adxl345_act_acdc_msk[type], act_inact_ac); +} + +static int adxl345_is_act_inact_en(struct adxl345_state *st, + enum adxl345_activity_type type) +{ + unsigned int axis_ctrl; + unsigned int regval; + bool int_en, en; + int ret; + + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &axis_ctrl); + if (ret) + return ret; + + /* Check if axis for activity are enabled */ + switch (type) { + case ADXL345_ACTIVITY: + case ADXL345_ACTIVITY_AC: + en = FIELD_GET(ADXL345_ACT_XYZ_EN, axis_ctrl); + if (!en) + return false; + break; + case ADXL345_INACTIVITY: + case ADXL345_INACTIVITY_AC: + en = FIELD_GET(ADXL345_INACT_XYZ_EN, axis_ctrl); + if (!en) + return false; + break; + case ADXL345_INACTIVITY_FF: + en = true; + break; + default: + return -EINVAL; + } + + /* Check if specific interrupt is enabled */ + ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, ®val); + if (ret) + return ret; + + int_en = adxl345_act_int_reg[type] & regval; + if (!int_en) + return false; + + /* Check if configured coupling matches provided type */ + return adxl345_is_act_inact_ac(st, type); +} + +static int adxl345_set_act_inact_linkbit(struct adxl345_state *st, + enum adxl345_activity_type type, + bool en) +{ + int act_ac_en, inact_ac_en; + int act_en, inact_en; + + act_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY); + if (act_en < 0) + return act_en; + + act_ac_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY_AC); + if (act_ac_en < 0) + return act_ac_en; + + if (type == ADXL345_INACTIVITY_FF) { + inact_en = false; + } else { + inact_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY); + if (inact_en < 0) + return inact_en; + + inact_ac_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY_AC); + if (inact_ac_en < 0) + return inact_ac_en; + + inact_en = inact_en || inact_ac_en; + } + + act_en = act_en || act_ac_en; + + return regmap_assign_bits(st->regmap, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_INACT_MSK, + en && act_en && inact_en); +} + +static int adxl345_set_act_inact_en(struct adxl345_state *st, + enum adxl345_activity_type type, + bool cmd_en) +{ + unsigned int axis_ctrl; + unsigned int threshold; + unsigned int period; + int ret; + + if (cmd_en) { + /* When turning on, check if threshold is valid */ + if (type == ADXL345_ACTIVITY || type == ADXL345_ACTIVITY_AC) { + ret = regmap_read(st->regmap, + adxl345_act_thresh_reg[type], + &threshold); + if (ret) + return ret; + } else { + threshold = st->inact_threshold; + } + + if (!threshold) /* Just ignore the command if threshold is 0 */ + return 0; + + /* When turning on inactivity, check if inact time is valid */ + if (type == ADXL345_INACTIVITY || type == ADXL345_INACTIVITY_AC) { + ret = regmap_read(st->regmap, + ADXL345_REG_TIME_INACT, + &period); + if (ret) + return ret; + + if (!period) + return 0; + } + } else { + /* + * When turning off an activity, ensure that the correct + * coupling event is specified. This step helps prevent misuse - + * for example, if an AC-coupled activity is active and the + * current call attempts to turn off a DC-coupled activity, this + * inconsistency should be detected here. + */ + if (adxl345_is_act_inact_ac(st, type) <= 0) + return 0; + } + + /* Start modifying configuration registers */ + ret = adxl345_set_measure_en(st, false); + if (ret) + return ret; + + /* Enable axis according to the command */ + switch (type) { + case ADXL345_ACTIVITY: + case ADXL345_ACTIVITY_AC: + axis_ctrl = ADXL345_ACT_XYZ_EN; + break; + case ADXL345_INACTIVITY: + case ADXL345_INACTIVITY_AC: + axis_ctrl = ADXL345_INACT_XYZ_EN; + break; + case ADXL345_INACTIVITY_FF: + axis_ctrl = ADXL345_ACT_INACT_NO_AXIS_EN; + break; + default: + return -EINVAL; + } + + ret = regmap_assign_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL, + axis_ctrl, cmd_en); + if (ret) + return ret; + + /* Update AC/DC-coupling according to the command */ + ret = adxl345_set_act_inact_ac(st, type, cmd_en); + if (ret) + return ret; + + /* Enable the interrupt line, according to the command */ + ret = regmap_assign_bits(st->regmap, ADXL345_REG_INT_ENABLE, + adxl345_act_int_reg[type], cmd_en); + if (ret) + return ret; + + /* Set link-bit and auto-sleep only when ACT and INACT are enabled */ + ret = adxl345_set_act_inact_linkbit(st, type, cmd_en); + if (ret) + return ret; + + return adxl345_set_measure_en(st, true); +} + /* tap */ static int _adxl345_set_tap_int(struct adxl345_state *st, @@ -368,9 +864,8 @@ static int adxl345_set_doubletap_en(struct adxl345_state *st, bool en) * Generally suppress detection of spikes during the latency period as * double taps here, this is fully optional for double tap detection */ - ret = regmap_update_bits(st->regmap, ADXL345_REG_TAP_AXIS, - ADXL345_REG_TAP_SUPPRESS_MSK, - en ? ADXL345_REG_TAP_SUPPRESS : 0x00); + ret = regmap_assign_bits(st->regmap, ADXL345_REG_TAP_AXIS, + ADXL345_REG_TAP_SUPPRESS, en); if (ret) return ret; @@ -466,9 +961,16 @@ static int adxl345_find_odr(struct adxl345_state *st, int val, static int adxl345_set_odr(struct adxl345_state *st, enum adxl345_odr odr) { - return regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE, + int ret; + + ret = regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE, ADXL345_BW_RATE_MSK, FIELD_PREP(ADXL345_BW_RATE_MSK, odr)); + if (ret) + return ret; + + /* update inactivity time by ODR */ + return adxl345_set_inact_time(st, 0, 0); } static int adxl345_find_range(struct adxl345_state *st, int val, int val2, @@ -489,9 +991,43 @@ static int adxl345_find_range(struct adxl345_state *st, int val, int val2, static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range) { - return regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT, + unsigned int act_threshold, inact_threshold; + unsigned int range_old; + unsigned int regval; + int ret; + + ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, ®val); + if (ret) + return ret; + range_old = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval); + + ret = regmap_read(st->regmap, + adxl345_act_thresh_reg[ADXL345_ACTIVITY], + &act_threshold); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT, ADXL345_DATA_FORMAT_RANGE, FIELD_PREP(ADXL345_DATA_FORMAT_RANGE, range)); + if (ret) + return ret; + + act_threshold = act_threshold * adxl345_range_factor_tbl[range_old] + / adxl345_range_factor_tbl[range]; + act_threshold = min(U8_MAX, max(1, act_threshold)); + + inact_threshold = st->inact_threshold; + inact_threshold = inact_threshold * adxl345_range_factor_tbl[range_old] + / adxl345_range_factor_tbl[range]; + inact_threshold = min(U8_MAX, max(1, inact_threshold)); + + ret = regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_ACTIVITY], + act_threshold); + if (ret) + return ret; + + return adxl345_set_inact_threshold(st, inact_threshold); } static int adxl345_read_avail(struct iio_dev *indio_dev, @@ -624,6 +1160,37 @@ static int adxl345_write_raw(struct iio_dev *indio_dev, return adxl345_set_measure_en(st, true); } +static int adxl345_read_mag_config(struct adxl345_state *st, + enum iio_event_direction dir, + enum adxl345_activity_type type_act, + enum adxl345_activity_type type_inact) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + return !!adxl345_is_act_inact_en(st, type_act); + case IIO_EV_DIR_FALLING: + return !!adxl345_is_act_inact_en(st, type_inact); + default: + return -EINVAL; + } +} + +static int adxl345_write_mag_config(struct adxl345_state *st, + enum iio_event_direction dir, + enum adxl345_activity_type type_act, + enum adxl345_activity_type type_inact, + bool state) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + return adxl345_set_act_inact_en(st, type_act, state); + case IIO_EV_DIR_FALLING: + return adxl345_set_act_inact_en(st, type_inact, state); + default: + return -EINVAL; + } +} + static int adxl345_read_event_config(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, enum iio_event_type type, @@ -634,6 +1201,14 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev, int ret; switch (type) { + case IIO_EV_TYPE_MAG: + return adxl345_read_mag_config(st, dir, + ADXL345_ACTIVITY, + ADXL345_INACTIVITY); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl345_read_mag_config(st, dir, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC); case IIO_EV_TYPE_GESTURE: switch (dir) { case IIO_EV_DIR_SINGLETAP: @@ -665,6 +1240,16 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev, struct adxl345_state *st = iio_priv(indio_dev); switch (type) { + case IIO_EV_TYPE_MAG: + return adxl345_write_mag_config(st, dir, + ADXL345_ACTIVITY, + ADXL345_INACTIVITY, + state); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl345_write_mag_config(st, dir, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC, + state); case IIO_EV_TYPE_GESTURE: switch (dir) { case IIO_EV_DIR_SINGLETAP: @@ -679,6 +1264,72 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev, } } +static int adxl345_read_mag_value(struct adxl345_state *st, + enum iio_event_direction dir, + enum iio_event_info info, + enum adxl345_activity_type type_act, + enum adxl345_activity_type type_inact, + int *val, int *val2) +{ + unsigned int threshold; + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_read(st->regmap, + adxl345_act_thresh_reg[type_act], + &threshold); + if (ret) + return ret; + *val = 62500 * threshold; + *val2 = MICRO; + return IIO_VAL_FRACTIONAL; + case IIO_EV_DIR_FALLING: + *val = 62500 * st->inact_threshold; + *val2 = MICRO; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + *val = st->inact_time_ms; + *val2 = MILLI; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int adxl345_write_mag_value(struct adxl345_state *st, + enum iio_event_direction dir, + enum iio_event_info info, + enum adxl345_activity_type type_act, + enum adxl345_activity_type type_inact, + int val, int val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + /* Scaling factor 62.5mg/LSB, i.e. ~16g corresponds to 0xff */ + val = DIV_ROUND_CLOSEST(val * MICRO + val2, 62500); + switch (dir) { + case IIO_EV_DIR_RISING: + return regmap_write(st->regmap, + adxl345_act_thresh_reg[type_act], + val); + case IIO_EV_DIR_FALLING: + return adxl345_set_inact_threshold(st, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + return adxl345_set_inact_time(st, val, val2); + default: + return -EINVAL; + } +} + static int adxl345_read_event_value(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, enum iio_event_type type, @@ -691,6 +1342,16 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev, int ret; switch (type) { + case IIO_EV_TYPE_MAG: + return adxl345_read_mag_value(st, dir, info, + ADXL345_ACTIVITY, + ADXL345_INACTIVITY, + val, val2); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl345_read_mag_value(st, dir, info, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC, + val, val2); case IIO_EV_TYPE_GESTURE: switch (info) { case IIO_EV_INFO_VALUE: @@ -741,6 +1402,22 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev, return ret; switch (type) { + case IIO_EV_TYPE_MAG: + ret = adxl345_write_mag_value(st, dir, info, + ADXL345_ACTIVITY, + ADXL345_INACTIVITY, + val, val2); + if (ret) + return ret; + break; + case IIO_EV_TYPE_MAG_ADAPTIVE: + ret = adxl345_write_mag_value(st, dir, info, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC, + val, val2); + if (ret) + return ret; + break; case IIO_EV_TYPE_GESTURE: switch (info) { case IIO_EV_INFO_VALUE: @@ -980,10 +1657,12 @@ static int adxl345_fifo_push(struct iio_dev *indio_dev, } static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat, + enum iio_modifier act_dir, enum iio_modifier tap_dir) { s64 ts = iio_get_time_ns(indio_dev); struct adxl345_state *st = iio_priv(indio_dev); + unsigned int regval; int samples; int ret = -ENOENT; @@ -1007,6 +1686,68 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat, return ret; } + if (FIELD_GET(ADXL345_INT_ACTIVITY, int_stat)) { + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val); + if (ret) + return ret; + + if (FIELD_GET(ADXL345_REG_ACT_ACDC, regval)) { + /* AC coupled */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir, + IIO_EV_TYPE_MAG_ADAPTIVE, + IIO_EV_DIR_RISING), + ts); + + } else { + /* DC coupled, relying on THRESH */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + ts); + } + if (ret) + return ret; + } + + if (FIELD_GET(ADXL345_INT_INACTIVITY, int_stat)) { + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val); + if (ret) + return ret; + + if (FIELD_GET(ADXL345_REG_INACT_ACDC, regval)) { + /* AC coupled */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG_ADAPTIVE, + IIO_EV_DIR_FALLING), + ts); + } else { + /* DC coupled, relying on THRESH */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + ts); + } + if (ret) + return ret; + } + + if (FIELD_GET(ADXL345_INT_FREE_FALL, int_stat)) { + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + ts); + if (ret) + return ret; + } + if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) { samples = adxl345_get_samples(st); if (samples < 0) @@ -1034,6 +1775,7 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p) struct adxl345_state *st = iio_priv(indio_dev); unsigned int regval; enum iio_modifier tap_dir = IIO_NO_MOD; + enum iio_modifier act_dir = IIO_NO_MOD; u32 axis_ctrl; int int_stat; int ret; @@ -1042,7 +1784,8 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p) if (ret) return IRQ_NONE; - if (FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl)) { + if (FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl) || + FIELD_GET(ADXL345_ACT_XYZ_EN, axis_ctrl)) { ret = regmap_read(st->regmap, ADXL345_REG_ACT_TAP_STATUS, ®val); if (ret) return IRQ_NONE; @@ -1053,12 +1796,19 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p) tap_dir = IIO_MOD_Y; else if (FIELD_GET(ADXL345_TAP_X_EN, regval)) tap_dir = IIO_MOD_X; + + if (FIELD_GET(ADXL345_ACT_Z_EN, regval)) + act_dir = IIO_MOD_Z; + else if (FIELD_GET(ADXL345_ACT_Y_EN, regval)) + act_dir = IIO_MOD_Y; + else if (FIELD_GET(ADXL345_ACT_X_EN, regval)) + act_dir = IIO_MOD_X; } if (regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, &int_stat)) return IRQ_NONE; - if (adxl345_push_event(indio_dev, int_stat, tap_dir)) + if (adxl345_push_event(indio_dev, int_stat, act_dir, tap_dir)) goto err; if (FIELD_GET(ADXL345_INT_OVERRUN, int_stat)) @@ -1226,6 +1976,24 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap, if (ret) return ret; + /* + * Initialized with sensible default values to streamline + * sensor operation. These defaults are partly derived from + * the previous input driver for the ADXL345 and partly + * based on the recommendations provided in the datasheet. + */ + ret = regmap_write(st->regmap, ADXL345_REG_ACT_INACT_CTRL, 0); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADXL345_REG_THRESH_ACT, 6); + if (ret) + return ret; + + ret = adxl345_set_inact_threshold(st, 4); + if (ret) + return ret; + ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP, tap_threshold); if (ret) return ret; diff --git a/drivers/iio/accel/bma180.c b/drivers/iio/accel/bma180.c index 4fccbcb76e04..8925f5279e62 100644 --- a/drivers/iio/accel/bma180.c +++ b/drivers/iio/accel/bma180.c @@ -139,11 +139,6 @@ struct bma180_data { int scale; int bw; bool pmode; - /* Ensure timestamp is naturally aligned */ - struct { - s16 chan[4]; - aligned_s64 timestamp; - } scan; }; enum bma180_chan { @@ -870,6 +865,10 @@ static irqreturn_t bma180_trigger_handler(int irq, void *p) struct bma180_data *data = iio_priv(indio_dev); s64 time_ns = iio_get_time_ns(indio_dev); int bit, ret, i = 0; + struct { + s16 chan[4]; + aligned_s64 timestamp; + } scan = { }; mutex_lock(&data->mutex); @@ -879,12 +878,12 @@ static irqreturn_t bma180_trigger_handler(int irq, void *p) mutex_unlock(&data->mutex); goto err; } - data->scan.chan[i++] = ret; + scan.chan[i++] = ret; } mutex_unlock(&data->mutex); - iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), time_ns); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns); err: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index 38f7498431ee..01592eebf05b 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -255,10 +255,8 @@ static int bma220_probe(struct spi_device *spi) struct bma220_data *data; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&spi->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->spi_device = spi; diff --git a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c index be5fbb0c5d29..3c5d1560b163 100644 --- a/drivers/iio/accel/bmc150-accel-core.c +++ b/drivers/iio/accel/bmc150-accel-core.c @@ -332,13 +332,10 @@ static int bmc150_accel_set_power_state(struct bmc150_accel_data *data, bool on) struct device *dev = regmap_get_device(data->regmap); int ret; - if (on) { + if (on) ret = pm_runtime_resume_and_get(dev); - } else { - pm_runtime_mark_last_busy(dev); + else ret = pm_runtime_put_autosuspend(dev); - } - if (ret < 0) { dev_err(dev, "Failed: %s for %d\n", __func__, on); diff --git a/drivers/iio/accel/bmi088-accel-core.c b/drivers/iio/accel/bmi088-accel-core.c index dea126f993c1..c7da90af0d2d 100644 --- a/drivers/iio/accel/bmi088-accel-core.c +++ b/drivers/iio/accel/bmi088-accel-core.c @@ -375,7 +375,6 @@ static int bmi088_accel_read_raw(struct iio_dev *indio_dev, return -EINVAL; out_read_raw_pm_put: - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -419,7 +418,6 @@ static int bmi088_accel_write_raw(struct iio_dev *indio_dev, return ret; ret = bmi088_accel_set_scale(data, val, val2); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; case IIO_CHAN_INFO_SAMP_FREQ: @@ -428,7 +426,6 @@ static int bmi088_accel_write_raw(struct iio_dev *indio_dev, return ret; ret = bmi088_accel_set_sample_freq(data, val); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; default: diff --git a/drivers/iio/accel/dmard06.c b/drivers/iio/accel/dmard06.c index fb14894c66f9..33f225d73e7b 100644 --- a/drivers/iio/accel/dmard06.c +++ b/drivers/iio/accel/dmard06.c @@ -137,10 +137,8 @@ static int dmard06_probe(struct i2c_client *client) } indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dmard06)); - if (!indio_dev) { - dev_err(&client->dev, "Failed to allocate iio device\n"); + if (!indio_dev) return -ENOMEM; - } dmard06 = iio_priv(indio_dev); dmard06->client = client; diff --git a/drivers/iio/accel/dmard09.c b/drivers/iio/accel/dmard09.c index 4ec70ca6910d..d9290e3b9c46 100644 --- a/drivers/iio/accel/dmard09.c +++ b/drivers/iio/accel/dmard09.c @@ -95,10 +95,8 @@ static int dmard09_probe(struct i2c_client *client) struct dmard09_data *data; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/accel/dmard10.c b/drivers/iio/accel/dmard10.c index 71cd1928baa6..575e8510e1bd 100644 --- a/drivers/iio/accel/dmard10.c +++ b/drivers/iio/accel/dmard10.c @@ -191,10 +191,8 @@ static int dmard10_probe(struct i2c_client *client) return (ret < 0) ? ret : -ENODEV; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/accel/fxls8962af-core.c b/drivers/iio/accel/fxls8962af-core.c index b10a30960e1e..8763e91c63d2 100644 --- a/drivers/iio/accel/fxls8962af-core.c +++ b/drivers/iio/accel/fxls8962af-core.c @@ -222,7 +222,6 @@ static int fxls8962af_power_off(struct fxls8962af_data *data) struct device *dev = regmap_get_device(data->regmap); int ret; - pm_runtime_mark_last_busy(dev); ret = pm_runtime_put_autosuspend(dev); if (ret) dev_err(dev, "failed to power off\n"); diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c index 6aefe8221296..2823ddde4bf2 100644 --- a/drivers/iio/accel/kxcjk-1013.c +++ b/drivers/iio/accel/kxcjk-1013.c @@ -636,10 +636,8 @@ static int kxcjk1013_set_power_state(struct kxcjk1013_data *data, bool on) if (on) ret = pm_runtime_resume_and_get(&data->client->dev); - else { - pm_runtime_mark_last_busy(&data->client->dev); + else ret = pm_runtime_put_autosuspend(&data->client->dev); - } if (ret < 0) { dev_err(&data->client->dev, "Failed: %s for %d\n", __func__, on); diff --git a/drivers/iio/accel/kxsd9.c b/drivers/iio/accel/kxsd9.c index cfc31265cdd0..4717d80fc24a 100644 --- a/drivers/iio/accel/kxsd9.c +++ b/drivers/iio/accel/kxsd9.c @@ -151,7 +151,6 @@ static int kxsd9_write_raw(struct iio_dev *indio_dev, ret = kxsd9_write_scale(indio_dev, val2); } - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return ret; @@ -199,7 +198,6 @@ static int kxsd9_read_raw(struct iio_dev *indio_dev, } error_ret: - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return ret; @@ -250,7 +248,6 @@ static int kxsd9_buffer_postdisable(struct iio_dev *indio_dev) { struct kxsd9_state *st = iio_priv(indio_dev); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return 0; diff --git a/drivers/iio/accel/mc3230.c b/drivers/iio/accel/mc3230.c index e2853090fa6e..3e494f9ddc56 100644 --- a/drivers/iio/accel/mc3230.c +++ b/drivers/iio/accel/mc3230.c @@ -169,10 +169,8 @@ static int mc3230_probe(struct i2c_client *client) } indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->chip_info = chip_info; diff --git a/drivers/iio/accel/mma7660.c b/drivers/iio/accel/mma7660.c index d0a16f227903..be3213600cf4 100644 --- a/drivers/iio/accel/mma7660.c +++ b/drivers/iio/accel/mma7660.c @@ -192,10 +192,8 @@ static int mma7660_probe(struct i2c_client *client) struct mma7660_data *data; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index aba444a980d9..15172ba2972c 100644 --- a/drivers/iio/accel/mma8452.c +++ b/drivers/iio/accel/mma8452.c @@ -224,13 +224,10 @@ static int mma8452_set_runtime_pm_state(struct i2c_client *client, bool on) #ifdef CONFIG_PM int ret; - if (on) { + if (on) ret = pm_runtime_resume_and_get(&client->dev); - } else { - pm_runtime_mark_last_busy(&client->dev); + else ret = pm_runtime_put_autosuspend(&client->dev); - } - if (ret < 0) { dev_err(&client->dev, "failed to change power state to %d\n", on); diff --git a/drivers/iio/accel/mma9551_core.c b/drivers/iio/accel/mma9551_core.c index 3e7d9b79ed0e..2ccb1fb19b96 100644 --- a/drivers/iio/accel/mma9551_core.c +++ b/drivers/iio/accel/mma9551_core.c @@ -671,11 +671,8 @@ int mma9551_set_power_state(struct i2c_client *client, bool on) if (on) ret = pm_runtime_resume_and_get(&client->dev); - else { - pm_runtime_mark_last_busy(&client->dev); + else ret = pm_runtime_put_autosuspend(&client->dev); - } - if (ret < 0) { dev_err(&client->dev, "failed to change power state to %d\n", on); diff --git a/drivers/iio/accel/msa311.c b/drivers/iio/accel/msa311.c index 3e10225410e8..5eace0de3750 100644 --- a/drivers/iio/accel/msa311.c +++ b/drivers/iio/accel/msa311.c @@ -607,7 +607,6 @@ static int msa311_read_raw_data(struct iio_dev *indio_dev, err = msa311_get_axis(msa311, chan, &axis); mutex_unlock(&msa311->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); iio_device_release_direct(indio_dev); @@ -741,7 +740,6 @@ static int msa311_write_scale(struct iio_dev *indio_dev, int val, int val2) break; } - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); if (err) @@ -781,7 +779,6 @@ static int msa311_write_samp_freq(struct iio_dev *indio_dev, int val, int val2) break; } - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); iio_device_release_direct(indio_dev); @@ -832,7 +829,6 @@ static int msa311_debugfs_reg_access(struct iio_dev *indio_dev, mutex_unlock(&msa311->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); if (err) @@ -855,7 +851,6 @@ static int msa311_buffer_postdisable(struct iio_dev *indio_dev) struct msa311_priv *msa311 = iio_priv(indio_dev); struct device *dev = msa311->dev; - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; @@ -990,7 +985,7 @@ static int msa311_check_partid(struct msa311_priv *msa311) msa311->chip_name = devm_kasprintf(dev, GFP_KERNEL, "msa311-%02x", partid); if (!msa311->chip_name) - return dev_err_probe(dev, -ENOMEM, "can't alloc chip name\n"); + return -ENOMEM; return 0; } @@ -1069,8 +1064,7 @@ static int msa311_setup_interrupts(struct msa311_priv *msa311) trig = devm_iio_trigger_alloc(dev, "%s-new-data", msa311->chip_name); if (!trig) - return dev_err_probe(dev, -ENOMEM, - "can't allocate newdata trigger\n"); + return -ENOMEM; msa311->new_data_trig = trig; msa311->new_data_trig->ops = &msa311_new_data_trig_ops; @@ -1153,8 +1147,7 @@ static int msa311_probe(struct i2c_client *i2c) indio_dev = devm_iio_device_alloc(dev, sizeof(*msa311)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "IIO device allocation failed\n"); + return -ENOMEM; msa311 = iio_priv(indio_dev); msa311->dev = dev; @@ -1195,7 +1188,7 @@ static int msa311_probe(struct i2c_client *i2c) */ err = devm_add_action_or_reset(dev, msa311_powerdown, msa311); if (err) - return dev_err_probe(dev, err, "can't add powerdown action\n"); + return err; err = pm_runtime_set_active(dev); if (err) @@ -1231,7 +1224,6 @@ static int msa311_probe(struct i2c_client *i2c) if (err) return err; - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); err = devm_iio_device_register(dev, indio_dev); diff --git a/drivers/iio/accel/stk8312.c b/drivers/iio/accel/stk8312.c index 89569ce221d7..f31c6ab3392d 100644 --- a/drivers/iio/accel/stk8312.c +++ b/drivers/iio/accel/stk8312.c @@ -504,10 +504,8 @@ static int stk8312_probe(struct i2c_client *client) struct stk8312_data *data; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/accel/stk8ba50.c b/drivers/iio/accel/stk8ba50.c index c1d7e7dcb09b..384f1fbcbcb3 100644 --- a/drivers/iio/accel/stk8ba50.c +++ b/drivers/iio/accel/stk8ba50.c @@ -385,10 +385,8 @@ static int stk8ba50_probe(struct i2c_client *client) struct stk8ba50_data *data; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/adc/88pm886-gpadc.c b/drivers/iio/adc/88pm886-gpadc.c new file mode 100644 index 000000000000..cffe35136685 --- /dev/null +++ b/drivers/iio/adc/88pm886-gpadc.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2025, Duje Mihanović <duje@dujemihanovic.xyz> + */ + +#include <linux/bits.h> +#include <linux/bug.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/math.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <asm/byteorder.h> + +#include <linux/iio/iio.h> +#include <linux/iio/types.h> + +#include <linux/mfd/88pm886.h> + +struct pm886_gpadc { + struct regmap *map; +}; + +enum pm886_gpadc_channel { + VSC_CHAN, + VCHG_PWR_CHAN, + VCF_OUT_CHAN, + VBAT_CHAN, + VBAT_SLP_CHAN, + VBUS_CHAN, + + GPADC0_CHAN, + GPADC1_CHAN, + GPADC2_CHAN, + GPADC3_CHAN, + + GND_DET1_CHAN, + GND_DET2_CHAN, + MIC_DET_CHAN, + + TINT_CHAN, +}; + +static const int pm886_gpadc_regs[] = { + [VSC_CHAN] = PM886_REG_GPADC_VSC, + [VCHG_PWR_CHAN] = PM886_REG_GPADC_VCHG_PWR, + [VCF_OUT_CHAN] = PM886_REG_GPADC_VCF_OUT, + [VBAT_CHAN] = PM886_REG_GPADC_VBAT, + [VBAT_SLP_CHAN] = PM886_REG_GPADC_VBAT_SLP, + [VBUS_CHAN] = PM886_REG_GPADC_VBUS, + + [GPADC0_CHAN] = PM886_REG_GPADC_GPADC0, + [GPADC1_CHAN] = PM886_REG_GPADC_GPADC1, + [GPADC2_CHAN] = PM886_REG_GPADC_GPADC2, + [GPADC3_CHAN] = PM886_REG_GPADC_GPADC3, + + [GND_DET1_CHAN] = PM886_REG_GPADC_GND_DET1, + [GND_DET2_CHAN] = PM886_REG_GPADC_GND_DET2, + [MIC_DET_CHAN] = PM886_REG_GPADC_MIC_DET, + + [TINT_CHAN] = PM886_REG_GPADC_TINT, +}; + +#define ADC_CHANNEL_VOLTAGE(index, lsb, name) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .address = lsb, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = name, \ +} + +#define ADC_CHANNEL_RESISTANCE(index, lsb, name) \ +{ \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .channel = index, \ + .address = lsb, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .datasheet_name = name, \ +} + +#define ADC_CHANNEL_TEMPERATURE(index, lsb, name) \ +{ \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = index, \ + .address = lsb, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .datasheet_name = name, \ +} + +static const struct iio_chan_spec pm886_gpadc_channels[] = { + ADC_CHANNEL_VOLTAGE(VSC_CHAN, 1367, "vsc"), + ADC_CHANNEL_VOLTAGE(VCHG_PWR_CHAN, 1709, "vchg_pwr"), + ADC_CHANNEL_VOLTAGE(VCF_OUT_CHAN, 1367, "vcf_out"), + ADC_CHANNEL_VOLTAGE(VBAT_CHAN, 1367, "vbat"), + ADC_CHANNEL_VOLTAGE(VBAT_SLP_CHAN, 1367, "vbat_slp"), + ADC_CHANNEL_VOLTAGE(VBUS_CHAN, 1709, "vbus"), + + ADC_CHANNEL_RESISTANCE(GPADC0_CHAN, 342, "gpadc0"), + ADC_CHANNEL_RESISTANCE(GPADC1_CHAN, 342, "gpadc1"), + ADC_CHANNEL_RESISTANCE(GPADC2_CHAN, 342, "gpadc2"), + ADC_CHANNEL_RESISTANCE(GPADC3_CHAN, 342, "gpadc3"), + + ADC_CHANNEL_VOLTAGE(GND_DET1_CHAN, 342, "gnddet1"), + ADC_CHANNEL_VOLTAGE(GND_DET2_CHAN, 342, "gnddet2"), + ADC_CHANNEL_VOLTAGE(MIC_DET_CHAN, 1367, "mic_det"), + + ADC_CHANNEL_TEMPERATURE(TINT_CHAN, 104, "tint"), +}; + +static const struct regmap_config pm886_gpadc_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PM886_GPADC_MAX_REGISTER, +}; + +static int gpadc_get_raw(struct iio_dev *iio, enum pm886_gpadc_channel chan) +{ + struct pm886_gpadc *gpadc = iio_priv(iio); + __be16 buf; + int ret; + + ret = regmap_bulk_read(gpadc->map, pm886_gpadc_regs[chan], &buf, sizeof(buf)); + if (ret) + return ret; + + return be16_to_cpu(buf) >> 4; +} + +static int +gpadc_set_bias(struct pm886_gpadc *gpadc, enum pm886_gpadc_channel chan, bool on) +{ + unsigned int gpadc_num = chan - GPADC0_CHAN; + unsigned int bits = BIT(gpadc_num + 4) | BIT(gpadc_num); + + return regmap_assign_bits(gpadc->map, PM886_REG_GPADC_CONFIG(0x14), bits, on); +} + +static int +gpadc_find_bias_current(struct iio_dev *iio, struct iio_chan_spec const *chan, + unsigned int *raw_uV, unsigned int *raw_uA) +{ + struct pm886_gpadc *gpadc = iio_priv(iio); + unsigned int gpadc_num = chan->channel - GPADC0_CHAN; + unsigned int reg = PM886_REG_GPADC_CONFIG(0xb + gpadc_num); + unsigned long lsb = chan->address; + int ret; + + for (unsigned int i = 0; i < PM886_GPADC_BIAS_LEVELS; i++) { + ret = regmap_update_bits(gpadc->map, reg, GENMASK(3, 0), i); + if (ret) + return ret; + + /* Wait for the new bias level to apply. */ + fsleep(5 * USEC_PER_MSEC); + + *raw_uA = PM886_GPADC_INDEX_TO_BIAS_uA(i); + *raw_uV = gpadc_get_raw(iio, chan->channel) * lsb; + + /* + * Vendor kernel errors out above 1.25 V, but testing shows + * that the resistance of the battery detection channel (GPADC2 + * on coreprimevelte) reaches about 1.4 MΩ when the battery is + * removed, which can't be measured with such a low upper + * limit. Therefore, to be able to detect the battery without + * ugly externs as used in the vendor fuel gauge driver, + * increase this limit a bit. + */ + if (WARN_ON(*raw_uV > 1500 * (MICRO / MILLI))) + return -EIO; + + /* + * Vendor kernel errors out under 300 mV, but for the same + * reason as above (except the channel hovers around 3.5 kΩ + * with battery present) reduce this limit. + */ + if (*raw_uV < 200 * (MICRO / MILLI)) { + dev_dbg(&iio->dev, "bad bias for chan %d: %d uA @ %d uV\n", + chan->channel, *raw_uA, *raw_uV); + continue; + } + + dev_dbg(&iio->dev, "good bias for chan %d: %d uA @ %d uV\n", + chan->channel, *raw_uA, *raw_uV); + return 0; + } + + dev_err(&iio->dev, "failed to find good bias for chan %d\n", chan->channel); + return -EINVAL; +} + +static int +gpadc_get_resistance_ohm(struct iio_dev *iio, struct iio_chan_spec const *chan) +{ + struct pm886_gpadc *gpadc = iio_priv(iio); + unsigned int raw_uV, raw_uA; + int ret; + + ret = gpadc_set_bias(gpadc, chan->channel, true); + if (ret) + goto out; + + ret = gpadc_find_bias_current(iio, chan, &raw_uV, &raw_uA); + if (ret) + goto out; + + ret = DIV_ROUND_CLOSEST(raw_uV, raw_uA); +out: + gpadc_set_bias(gpadc, chan->channel, false); + return ret; +} + +static int +__pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + unsigned long lsb = chan->address; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = gpadc_get_raw(iio, chan->channel); + if (*val < 0) + return *val; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = lsb; + + if (chan->type == IIO_VOLTAGE) { + *val2 = MILLI; + return IIO_VAL_FRACTIONAL; + } else { + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OFFSET: + /* Raw value is 104 millikelvin/LSB, convert it to 104 millicelsius/LSB */ + *val = ABSOLUTE_ZERO_MILLICELSIUS; + *val2 = lsb; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_PROCESSED: + *val = gpadc_get_resistance_ohm(iio, chan); + if (*val < 0) + return *val; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct device *dev = iio->dev.parent; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = __pm886_gpadc_read_raw(iio, chan, val, val2, mask); + + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int pm886_gpadc_hw_enable(struct regmap *map) +{ + const u8 config[] = { + PM886_GPADC_CONFIG1_EN_ALL, + PM886_GPADC_CONFIG2_EN_ALL, + PM886_GPADC_GND_DET2_EN, + }; + int ret; + + /* Enable the ADC block. */ + ret = regmap_set_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0)); + if (ret) + return ret; + + /* Enable all channels. */ + return regmap_bulk_write(map, PM886_REG_GPADC_CONFIG(0x1), config, ARRAY_SIZE(config)); +} + +static int pm886_gpadc_hw_disable(struct regmap *map) +{ + return regmap_clear_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0)); +} + +static const struct iio_info pm886_gpadc_iio_info = { + .read_raw = pm886_gpadc_read_raw, +}; + +static int pm886_gpadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pm886_chip *chip = dev_get_drvdata(dev->parent); + struct i2c_client *client = chip->client; + struct pm886_gpadc *gpadc; + struct i2c_client *page; + struct iio_dev *iio; + int ret; + + iio = devm_iio_device_alloc(dev, sizeof(*gpadc)); + if (!iio) + return -ENOMEM; + + gpadc = iio_priv(iio); + dev_set_drvdata(dev, iio); + + page = devm_i2c_new_dummy_device(dev, client->adapter, + client->addr + PM886_PAGE_OFFSET_GPADC); + if (IS_ERR(page)) + return dev_err_probe(dev, PTR_ERR(page), "Failed to initialize GPADC page\n"); + + gpadc->map = devm_regmap_init_i2c(page, &pm886_gpadc_regmap_config); + if (IS_ERR(gpadc->map)) + return dev_err_probe(dev, PTR_ERR(gpadc->map), + "Failed to initialize GPADC regmap\n"); + + iio->name = "88pm886-gpadc"; + iio->modes = INDIO_DIRECT_MODE; + iio->info = &pm886_gpadc_iio_info; + iio->channels = pm886_gpadc_channels; + iio->num_channels = ARRAY_SIZE(pm886_gpadc_channels); + device_set_node(&iio->dev, dev_fwnode(dev->parent)); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + pm_runtime_set_autosuspend_delay(dev, 50); + pm_runtime_use_autosuspend(dev); + ret = devm_iio_device_register(dev, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to register ADC\n"); + + return 0; +} + +static int pm886_gpadc_runtime_resume(struct device *dev) +{ + struct iio_dev *iio = dev_get_drvdata(dev); + struct pm886_gpadc *gpadc = iio_priv(iio); + + return pm886_gpadc_hw_enable(gpadc->map); +} + +static int pm886_gpadc_runtime_suspend(struct device *dev) +{ + struct iio_dev *iio = dev_get_drvdata(dev); + struct pm886_gpadc *gpadc = iio_priv(iio); + + return pm886_gpadc_hw_disable(gpadc->map); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(pm886_gpadc_pm_ops, + pm886_gpadc_runtime_suspend, + pm886_gpadc_runtime_resume, NULL); + +static const struct platform_device_id pm886_gpadc_id[] = { + { "88pm886-gpadc" }, + { } +}; +MODULE_DEVICE_TABLE(platform, pm886_gpadc_id); + +static struct platform_driver pm886_gpadc_driver = { + .driver = { + .name = "88pm886-gpadc", + .pm = pm_ptr(&pm886_gpadc_pm_ops), + }, + .probe = pm886_gpadc_probe, + .id_table = pm886_gpadc_id, +}; +module_platform_driver(pm886_gpadc_driver); + +MODULE_AUTHOR("Duje Mihanović <duje@dujemihanovic.xyz>"); +MODULE_DESCRIPTION("Marvell 88PM886 GPADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 24f2572c487e..58a14e6833f6 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -9,6 +9,19 @@ menu "Analog to digital converters" config IIO_ADC_HELPER tristate +config 88PM886_GPADC + tristate "Marvell 88PM886 GPADC driver" + depends on MFD_88PM886_PMIC + default MFD_88PM886_PMIC + help + Say Y here to enable support for the GPADC (General Purpose ADC) + found on the Marvell 88PM886 PMIC. The GPADC measures various + internal voltages and temperatures, including (but not limited to) + system, battery and USB Vbus. + + To compile this driver as a module, choose M here: the module will be + called 88pm886-gpadc. + config AB8500_GPADC bool "ST-Ericsson AB8500 GPADC driver" depends on AB8500_CORE && REGULATOR_AB8500 @@ -389,6 +402,7 @@ config AD7779 depends on SPI select CRC8 select IIO_BUFFER + select IIO_BACKEND help Say yes here to build support for Analog Devices AD777X family (AD7770, AD7771, AD7779) analog to digital converter (ADC). @@ -507,6 +521,25 @@ config AD9467 To compile this driver as a module, choose M here: the module will be called ad9467. +config ADE9000 + tristate "Analog Devices ADE9000 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. + + The device features 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. + + To compile this driver as a module, choose M here: the module will + be called ade9000. + config ADI_AXI_ADC tristate "Analog Devices Generic AXI ADC IP core driver" depends on MICROBLAZE || NIOS2 || ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || COMPILE_TEST @@ -766,6 +799,17 @@ config INGENIC_ADC This driver can also be built as a module. If so, the module will be called ingenic_adc. +config INTEL_DC_TI_ADC + tristate "Intel Bay Trail / Cherry Trail Dollar Cove TI ADC driver" + depends on INTEL_SOC_PMIC_CHTDC_TI + help + Say yes here to have support for the Dollar Cove TI PMIC ADC device. + Depending on platform configuration, this general purpose ADC can be + used for sensors such as battery voltage and thermal resistors. + + To compile this driver as a module, choose M here: the module will be + called intel_dc_ti_adc. + config INTEL_MRFLD_ADC tristate "Intel Merrifield Basin Cove ADC driver" depends on INTEL_SOC_PMIC_MRFLD @@ -1298,6 +1342,16 @@ config RN5T618_ADC This driver can also be built as a module. If so, the module will be called rn5t618-adc. +config ROHM_BD79112 + tristate "Rohm BD79112 ADC driver" + depends on SPI && GPIOLIB + select REGMAP_SPI + select IIO_ADC_HELPER + help + Say yes here to build support for the ROHM BD79112 ADC. The + ROHM BD79112 is a 12-bit, 32-channel, SAR ADC. Analog inputs + can also be used for GPIO. + config ROHM_BD79124 tristate "Rohm BD79124 ADC driver" depends on I2C && GPIOLIB diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 1c6ca5fd4b6d..d008f78dc010 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_IIO_ADC_HELPER) += industrialio-adc.o # When adding new entries keep the list in alphabetical order +obj-$(CONFIG_88PM886_GPADC) += 88pm886-gpadc.o obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o obj-$(CONFIG_AD4000) += ad4000.o @@ -46,6 +47,7 @@ obj-$(CONFIG_AD7944) += ad7944.o obj-$(CONFIG_AD7949) += ad7949.o obj-$(CONFIG_AD799X) += ad799x.o obj-$(CONFIG_AD9467) += ad9467.o +obj-$(CONFIG_ADE9000) += ade9000.o obj-$(CONFIG_ADI_AXI_ADC) += adi-axi-adc.o obj-$(CONFIG_ASPEED_ADC) += aspeed_adc.o obj-$(CONFIG_AT91_ADC) += at91_adc.o @@ -70,6 +72,7 @@ obj-$(CONFIG_IMX8QXP_ADC) += imx8qxp-adc.o obj-$(CONFIG_IMX93_ADC) += imx93_adc.o obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o obj-$(CONFIG_INGENIC_ADC) += ingenic-adc.o +obj-$(CONFIG_INTEL_DC_TI_ADC) += intel_dc_ti_adc.o obj-$(CONFIG_INTEL_MRFLD_ADC) += intel_mrfld_adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o @@ -116,6 +119,7 @@ obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o obj-$(CONFIG_RCAR_GYRO_ADC) += rcar-gyroadc.o obj-$(CONFIG_RICHTEK_RTQ6056) += rtq6056.o obj-$(CONFIG_RN5T618_ADC) += rn5t618-adc.o +obj-$(CONFIG_ROHM_BD79112) += rohm-bd79112.o obj-$(CONFIG_ROHM_BD79124) += rohm-bd79124.o obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o obj-$(CONFIG_RZG2L_ADC) += rzg2l_adc.o diff --git a/drivers/iio/adc/ab8500-gpadc.c b/drivers/iio/adc/ab8500-gpadc.c index f3b057f92310..8eaa1dd6a89b 100644 --- a/drivers/iio/adc/ab8500-gpadc.c +++ b/drivers/iio/adc/ab8500-gpadc.c @@ -607,7 +607,6 @@ static int ab8500_gpadc_read(struct ab8500_gpadc *gpadc, } /* This eventually drops the regulator */ - pm_runtime_mark_last_busy(gpadc->dev); pm_runtime_put_autosuspend(gpadc->dev); return (high_data << 8) | low_data; diff --git a/drivers/iio/adc/ad4130.c b/drivers/iio/adc/ad4130.c index dcdb5778f7d6..5567ae5dee88 100644 --- a/drivers/iio/adc/ad4130.c +++ b/drivers/iio/adc/ad4130.c @@ -2035,8 +2035,7 @@ static int ad4130_probe(struct spi_device *spi) ret = devm_add_action_or_reset(dev, ad4130_disable_regulators, st); if (ret) - return dev_err_probe(dev, ret, - "Failed to add regulators disable action\n"); + return ret; ret = ad4130_soft_reset(st); if (ret) diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c index 4d8c6bafd1c3..910b40393f77 100644 --- a/drivers/iio/adc/ad7124.c +++ b/drivers/iio/adc/ad7124.c @@ -3,21 +3,27 @@ * AD7124 SPI ADC driver * * Copyright 2018 Analog Devices Inc. + * Copyright 2025 BayLibre, SAS */ #include <linux/bitfield.h> #include <linux/bitops.h> +#include <linux/cleanup.h> #include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/err.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/kfifo.h> +#include <linux/minmax.h> #include <linux/module.h> #include <linux/mod_devicetable.h> #include <linux/property.h> #include <linux/regulator/consumer.h> #include <linux/spi/spi.h> +#include <linux/sprintf.h> +#include <linux/units.h> #include <linux/iio/iio.h> #include <linux/iio/adc/ad_sigma_delta.h> @@ -44,6 +50,11 @@ #define AD7124_STATUS_POR_FLAG BIT(4) /* AD7124_ADC_CONTROL */ +#define AD7124_ADC_CONTROL_CLK_SEL GENMASK(1, 0) +#define AD7124_ADC_CONTROL_CLK_SEL_INT 0 +#define AD7124_ADC_CONTROL_CLK_SEL_INT_OUT 1 +#define AD7124_ADC_CONTROL_CLK_SEL_EXT 2 +#define AD7124_ADC_CONTROL_CLK_SEL_EXT_DIV4 3 #define AD7124_ADC_CONTROL_MODE GENMASK(5, 2) #define AD7124_ADC_CONTROL_MODE_CONTINUOUS 0 #define AD7124_ADC_CONTROL_MODE_SINGLE 1 @@ -84,14 +95,26 @@ #define AD7124_CONFIG_PGA GENMASK(2, 0) /* AD7124_FILTER_X */ -#define AD7124_FILTER_FS GENMASK(10, 0) #define AD7124_FILTER_FILTER GENMASK(23, 21) #define AD7124_FILTER_FILTER_SINC4 0 #define AD7124_FILTER_FILTER_SINC3 2 +#define AD7124_FILTER_FILTER_SINC4_SINC1 4 +#define AD7124_FILTER_FILTER_SINC3_SINC1 5 +#define AD7124_FILTER_FILTER_SINC3_PF 7 +#define AD7124_FILTER_REJ60 BIT(20) +#define AD7124_FILTER_POST_FILTER GENMASK(19, 17) +#define AD7124_FILTER_POST_FILTER_47dB 2 +#define AD7124_FILTER_POST_FILTER_62dB 3 +#define AD7124_FILTER_POST_FILTER_86dB 5 +#define AD7124_FILTER_POST_FILTER_92dB 6 +#define AD7124_FILTER_SINGLE_CYCLE BIT(16) +#define AD7124_FILTER_FS GENMASK(10, 0) #define AD7124_MAX_CONFIGS 8 #define AD7124_MAX_CHANNELS 16 +#define AD7124_INT_CLK_HZ 614400 + /* AD7124 input sources */ enum ad7124_ref_sel { @@ -120,9 +143,9 @@ static const unsigned int ad7124_reg_size[] = { }; static const int ad7124_master_clk_freq_hz[3] = { - [AD7124_LOW_POWER] = 76800, - [AD7124_MID_POWER] = 153600, - [AD7124_FULL_POWER] = 614400, + [AD7124_LOW_POWER] = AD7124_INT_CLK_HZ / 8, + [AD7124_MID_POWER] = AD7124_INT_CLK_HZ / 4, + [AD7124_FULL_POWER] = AD7124_INT_CLK_HZ, }; static const char * const ad7124_ref_names[] = { @@ -138,9 +161,24 @@ struct ad7124_chip_info { unsigned int num_inputs; }; +enum ad7124_filter_type { + AD7124_FILTER_TYPE_SINC3, + AD7124_FILTER_TYPE_SINC3_PF1, + AD7124_FILTER_TYPE_SINC3_PF2, + AD7124_FILTER_TYPE_SINC3_PF3, + AD7124_FILTER_TYPE_SINC3_PF4, + AD7124_FILTER_TYPE_SINC3_REJ60, + AD7124_FILTER_TYPE_SINC3_SINC1, + AD7124_FILTER_TYPE_SINC4, + AD7124_FILTER_TYPE_SINC4_REJ60, + AD7124_FILTER_TYPE_SINC4_SINC1, +}; + struct ad7124_channel_config { bool live; unsigned int cfg_slot; + unsigned int requested_odr; + unsigned int requested_odr_micro; /* * Following fields are used to compare for equality. If you * make adaptations in it, you most likely also have to adapt @@ -153,9 +191,8 @@ struct ad7124_channel_config { bool buf_negative; unsigned int vref_mv; unsigned int pga_bits; - unsigned int odr; unsigned int odr_sel_bits; - unsigned int filter_type; + enum ad7124_filter_type filter_type; unsigned int calibration_offset; unsigned int calibration_gain; ); @@ -174,7 +211,7 @@ struct ad7124_state { struct ad_sigma_delta sd; struct ad7124_channel *channels; struct regulator *vref[4]; - struct clk *mclk; + u32 clk_hz; unsigned int adc_control; unsigned int num_channels; struct mutex cfgs_lock; /* lock for configs access */ @@ -250,44 +287,117 @@ static int ad7124_set_mode(struct ad_sigma_delta *sd, return ad_sd_write_reg(&st->sd, AD7124_ADC_CONTROL, 2, st->adc_control); } -static void ad7124_set_channel_odr(struct ad7124_state *st, unsigned int channel, unsigned int odr) +static u32 ad7124_get_fclk_hz(struct ad7124_state *st) +{ + enum ad7124_power_mode power_mode; + u32 fclk_hz; + + power_mode = FIELD_GET(AD7124_ADC_CONTROL_POWER_MODE, st->adc_control); + fclk_hz = st->clk_hz; + + switch (power_mode) { + case AD7124_LOW_POWER: + fclk_hz /= 8; + break; + case AD7124_MID_POWER: + fclk_hz /= 4; + break; + default: + break; + } + + return fclk_hz; +} + +static u32 ad7124_get_fs_factor(struct ad7124_state *st, unsigned int channel) { - unsigned int fclk, odr_sel_bits; + enum ad7124_power_mode power_mode = + FIELD_GET(AD7124_ADC_CONTROL_POWER_MODE, st->adc_control); + u32 avg = power_mode == AD7124_LOW_POWER ? 8 : 16; - fclk = clk_get_rate(st->mclk); /* - * FS[10:0] = fCLK / (fADC x 32) where: + * These are the "zero-latency" factors from the data sheet. For the + * sinc1 filters, these aren't documented, but derived by taking the + * single-channel formula from the sinc1 section of the data sheet and + * multiplying that by the sinc3/4 factor from the corresponding zero- + * latency sections. + */ + switch (st->channels[channel].cfg.filter_type) { + case AD7124_FILTER_TYPE_SINC4: + case AD7124_FILTER_TYPE_SINC4_REJ60: + return 4 * 32; + case AD7124_FILTER_TYPE_SINC4_SINC1: + return 4 * avg * 32; + case AD7124_FILTER_TYPE_SINC3_SINC1: + return 3 * avg * 32; + default: + return 3 * 32; + } +} + +static u32 ad7124_get_fadc_divisor(struct ad7124_state *st, unsigned int channel) +{ + u32 factor = ad7124_get_fs_factor(st, channel); + + /* + * The output data rate (f_ADC) is f_CLK / divisor. We are returning + * the divisor. + */ + return st->channels[channel].cfg.odr_sel_bits * factor; +} + +static void ad7124_set_channel_odr(struct ad7124_state *st, unsigned int channel) +{ + struct ad7124_channel_config *cfg = &st->channels[channel].cfg; + unsigned int fclk, factor, divisor, odr_sel_bits; + + fclk = ad7124_get_fclk_hz(st); + factor = ad7124_get_fs_factor(st, channel); + + /* + * FS[10:0] = fCLK / (fADC x 32 * N) where: * fADC is the output data rate * fCLK is the master clock frequency + * N is number of conversions per sample (depends on filter type) * FS[10:0] are the bits in the filter register * FS[10:0] can have a value from 1 to 2047 */ - odr_sel_bits = DIV_ROUND_CLOSEST(fclk, odr * 32); - if (odr_sel_bits < 1) - odr_sel_bits = 1; - else if (odr_sel_bits > 2047) - odr_sel_bits = 2047; + divisor = cfg->requested_odr * factor + + cfg->requested_odr_micro * factor / MICRO; + odr_sel_bits = clamp(DIV_ROUND_CLOSEST(fclk, divisor), 1, 2047); if (odr_sel_bits != st->channels[channel].cfg.odr_sel_bits) st->channels[channel].cfg.live = false; - /* fADC = fCLK / (FS[10:0] x 32) */ - st->channels[channel].cfg.odr = DIV_ROUND_CLOSEST(fclk, odr_sel_bits * 32); st->channels[channel].cfg.odr_sel_bits = odr_sel_bits; } -static int ad7124_get_3db_filter_freq(struct ad7124_state *st, - unsigned int channel) +static int ad7124_get_3db_filter_factor(struct ad7124_state *st, + unsigned int channel) { - unsigned int fadc; + struct ad7124_channel_config *cfg = &st->channels[channel].cfg; - fadc = st->channels[channel].cfg.odr; - - switch (st->channels[channel].cfg.filter_type) { - case AD7124_FILTER_FILTER_SINC3: - return DIV_ROUND_CLOSEST(fadc * 272, 1000); - case AD7124_FILTER_FILTER_SINC4: - return DIV_ROUND_CLOSEST(fadc * 230, 1000); + /* + * 3dB point is the f_CLK rate times some factor. This functions returns + * the factor times 1000. + */ + switch (cfg->filter_type) { + case AD7124_FILTER_TYPE_SINC3: + case AD7124_FILTER_TYPE_SINC3_REJ60: + case AD7124_FILTER_TYPE_SINC3_SINC1: + return 272; + case AD7124_FILTER_TYPE_SINC4: + case AD7124_FILTER_TYPE_SINC4_REJ60: + case AD7124_FILTER_TYPE_SINC4_SINC1: + return 230; + case AD7124_FILTER_TYPE_SINC3_PF1: + return 633; + case AD7124_FILTER_TYPE_SINC3_PF2: + return 605; + case AD7124_FILTER_TYPE_SINC3_PF3: + return 669; + case AD7124_FILTER_TYPE_SINC3_PF4: + return 759; default: return -EINVAL; } @@ -311,9 +421,8 @@ static struct ad7124_channel_config *ad7124_find_similar_live_cfg(struct ad7124_ bool buf_negative; unsigned int vref_mv; unsigned int pga_bits; - unsigned int odr; unsigned int odr_sel_bits; - unsigned int filter_type; + enum ad7124_filter_type filter_type; unsigned int calibration_offset; unsigned int calibration_gain; })); @@ -328,7 +437,6 @@ static struct ad7124_channel_config *ad7124_find_similar_live_cfg(struct ad7124_ cfg->buf_negative == cfg_aux->buf_negative && cfg->vref_mv == cfg_aux->vref_mv && cfg->pga_bits == cfg_aux->pga_bits && - cfg->odr == cfg_aux->odr && cfg->odr_sel_bits == cfg_aux->odr_sel_bits && cfg->filter_type == cfg_aux->filter_type && cfg->calibration_offset == cfg_aux->calibration_offset && @@ -381,8 +489,9 @@ static int ad7124_init_config_vref(struct ad7124_state *st, struct ad7124_channe static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_config *cfg, unsigned int cfg_slot) { - unsigned int tmp; - unsigned int val; + unsigned int val, filter; + unsigned int rej60 = 0; + unsigned int post = 0; int ret; cfg->cfg_slot = cfg_slot; @@ -405,11 +514,60 @@ static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_co if (ret < 0) return ret; - tmp = FIELD_PREP(AD7124_FILTER_FILTER, cfg->filter_type) | - FIELD_PREP(AD7124_FILTER_FS, cfg->odr_sel_bits); - return ad7124_spi_write_mask(st, AD7124_FILTER(cfg->cfg_slot), - AD7124_FILTER_FILTER | AD7124_FILTER_FS, - tmp, 3); + switch (cfg->filter_type) { + case AD7124_FILTER_TYPE_SINC3: + filter = AD7124_FILTER_FILTER_SINC3; + break; + case AD7124_FILTER_TYPE_SINC3_PF1: + filter = AD7124_FILTER_FILTER_SINC3_PF; + post = AD7124_FILTER_POST_FILTER_47dB; + break; + case AD7124_FILTER_TYPE_SINC3_PF2: + filter = AD7124_FILTER_FILTER_SINC3_PF; + post = AD7124_FILTER_POST_FILTER_62dB; + break; + case AD7124_FILTER_TYPE_SINC3_PF3: + filter = AD7124_FILTER_FILTER_SINC3_PF; + post = AD7124_FILTER_POST_FILTER_86dB; + break; + case AD7124_FILTER_TYPE_SINC3_PF4: + filter = AD7124_FILTER_FILTER_SINC3_PF; + post = AD7124_FILTER_POST_FILTER_92dB; + break; + case AD7124_FILTER_TYPE_SINC3_REJ60: + filter = AD7124_FILTER_FILTER_SINC3; + rej60 = 1; + break; + case AD7124_FILTER_TYPE_SINC3_SINC1: + filter = AD7124_FILTER_FILTER_SINC3_SINC1; + break; + case AD7124_FILTER_TYPE_SINC4: + filter = AD7124_FILTER_FILTER_SINC4; + break; + case AD7124_FILTER_TYPE_SINC4_REJ60: + filter = AD7124_FILTER_FILTER_SINC4; + rej60 = 1; + break; + case AD7124_FILTER_TYPE_SINC4_SINC1: + filter = AD7124_FILTER_FILTER_SINC4_SINC1; + break; + default: + return -EINVAL; + } + + /* + * NB: AD7124_FILTER_SINGLE_CYCLE is always set so that we get the same + * sampling frequency even when only one channel is enabled in a + * buffered read. If it was not set, the N in ad7124_set_channel_odr() + * would be 1 and we would get a faster sampling frequency than what + * was requested. + */ + return ad_sd_write_reg(&st->sd, AD7124_FILTER(cfg->cfg_slot), 3, + FIELD_PREP(AD7124_FILTER_FILTER, filter) | + FIELD_PREP(AD7124_FILTER_REJ60, rej60) | + FIELD_PREP(AD7124_FILTER_POST_FILTER, post) | + AD7124_FILTER_SINGLE_CYCLE | + FIELD_PREP(AD7124_FILTER_FS, cfg->odr_sel_bits)); } static struct ad7124_channel_config *ad7124_pop_config(struct ad7124_state *st) @@ -576,6 +734,33 @@ static const struct ad_sigma_delta_info ad7124_sigma_delta_info = { .num_resetclks = 64, }; +static const int ad7124_voltage_scales[][2] = { + { 0, 1164 }, + { 0, 2328 }, + { 0, 4656 }, + { 0, 9313 }, + { 0, 18626 }, + { 0, 37252 }, + { 0, 74505 }, + { 0, 149011 }, + { 0, 298023 }, +}; + +static int ad7124_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)ad7124_voltage_scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(ad7124_voltage_scales) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + static int ad7124_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long info) @@ -644,18 +829,59 @@ static int ad7124_read_raw(struct iio_dev *indio_dev, return -EINVAL; } - case IIO_CHAN_INFO_SAMP_FREQ: - mutex_lock(&st->cfgs_lock); - *val = st->channels[chan->address].cfg.odr; - mutex_unlock(&st->cfgs_lock); + case IIO_CHAN_INFO_SAMP_FREQ: { + struct ad7124_channel_config *cfg = &st->channels[chan->address].cfg; - return IIO_VAL_INT; - case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: - mutex_lock(&st->cfgs_lock); - *val = ad7124_get_3db_filter_freq(st, chan->scan_index); - mutex_unlock(&st->cfgs_lock); + guard(mutex)(&st->cfgs_lock); - return IIO_VAL_INT; + switch (cfg->filter_type) { + case AD7124_FILTER_TYPE_SINC3: + case AD7124_FILTER_TYPE_SINC3_REJ60: + case AD7124_FILTER_TYPE_SINC3_SINC1: + case AD7124_FILTER_TYPE_SINC4: + case AD7124_FILTER_TYPE_SINC4_REJ60: + case AD7124_FILTER_TYPE_SINC4_SINC1: + *val = ad7124_get_fclk_hz(st); + *val2 = ad7124_get_fadc_divisor(st, chan->address); + return IIO_VAL_FRACTIONAL; + /* + * Post filters force the chip to a fixed rate. These are the + * single-channel rates from the data sheet divided by 3 for + * the multi-channel case (data sheet doesn't explicitly state + * this but confirmed through testing). + */ + case AD7124_FILTER_TYPE_SINC3_PF1: + *val = 300; + *val2 = 33; + return IIO_VAL_FRACTIONAL; + case AD7124_FILTER_TYPE_SINC3_PF2: + *val = 25; + *val2 = 3; + return IIO_VAL_FRACTIONAL; + case AD7124_FILTER_TYPE_SINC3_PF3: + *val = 20; + *val2 = 3; + return IIO_VAL_FRACTIONAL; + case AD7124_FILTER_TYPE_SINC3_PF4: + *val = 50; + *val2 = 9; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + } + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: { + guard(mutex)(&st->cfgs_lock); + + ret = ad7124_get_3db_filter_factor(st, chan->address); + if (ret < 0) + return ret; + + /* 3dB point is the f_CLK rate times a fractional value */ + *val = ret * ad7124_get_fclk_hz(st); + *val2 = MILLI * ad7124_get_fadc_divisor(st, chan->address); + return IIO_VAL_FRACTIONAL; + } default: return -EINVAL; } @@ -666,25 +892,24 @@ static int ad7124_write_raw(struct iio_dev *indio_dev, int val, int val2, long info) { struct ad7124_state *st = iio_priv(indio_dev); + struct ad7124_channel_config *cfg = &st->channels[chan->address].cfg; unsigned int res, gain, full_scale, vref; - int ret = 0; - mutex_lock(&st->cfgs_lock); + guard(mutex)(&st->cfgs_lock); switch (info) { case IIO_CHAN_INFO_SAMP_FREQ: - if (val2 != 0 || val == 0) { - ret = -EINVAL; - break; - } + if (val2 < 0 || val < 0 || (val2 == 0 && val == 0)) + return -EINVAL; - ad7124_set_channel_odr(st, chan->address, val); - break; + cfg->requested_odr = val; + cfg->requested_odr_micro = val2; + ad7124_set_channel_odr(st, chan->address); + + return 0; case IIO_CHAN_INFO_SCALE: - if (val != 0) { - ret = -EINVAL; - break; - } + if (val != 0) + return -EINVAL; if (st->channels[chan->address].cfg.bipolar) full_scale = 1 << (chan->scan_type.realbits - 1); @@ -700,13 +925,10 @@ static int ad7124_write_raw(struct iio_dev *indio_dev, st->channels[chan->address].cfg.live = false; st->channels[chan->address].cfg.pga_bits = res; - break; + return 0; default: - ret = -EINVAL; + return -EINVAL; } - - mutex_unlock(&st->cfgs_lock); - return ret; } static int ad7124_reg_access(struct iio_dev *indio_dev, @@ -730,18 +952,6 @@ static int ad7124_reg_access(struct iio_dev *indio_dev, return ret; } -static IIO_CONST_ATTR(in_voltage_scale_available, - "0.000001164 0.000002328 0.000004656 0.000009313 0.000018626 0.000037252 0.000074505 0.000149011 0.000298023"); - -static struct attribute *ad7124_attributes[] = { - &iio_const_attr_in_voltage_scale_available.dev_attr.attr, - NULL, -}; - -static const struct attribute_group ad7124_attrs_group = { - .attrs = ad7124_attributes, -}; - static int ad7124_update_scan_mode(struct iio_dev *indio_dev, const unsigned long *scan_mask) { @@ -750,7 +960,8 @@ static int ad7124_update_scan_mode(struct iio_dev *indio_dev, int ret; int i; - mutex_lock(&st->cfgs_lock); + guard(mutex)(&st->cfgs_lock); + for (i = 0; i < st->num_channels; i++) { bit_set = test_bit(i, scan_mask); if (bit_set) @@ -758,25 +969,20 @@ static int ad7124_update_scan_mode(struct iio_dev *indio_dev, else ret = ad7124_spi_write_mask(st, AD7124_CHANNEL(i), AD7124_CHANNEL_ENABLE, 0, 2); - if (ret < 0) { - mutex_unlock(&st->cfgs_lock); - + if (ret < 0) return ret; - } } - mutex_unlock(&st->cfgs_lock); - return 0; } static const struct iio_info ad7124_info = { + .read_avail = ad7124_read_avail, .read_raw = ad7124_read_raw, .write_raw = ad7124_write_raw, .debugfs_reg_access = &ad7124_reg_access, .validate_trigger = ad_sd_validate_trigger, .update_scan_mode = ad7124_update_scan_mode, - .attrs = &ad7124_attrs_group, }; /* Only called during probe, so dev_err_probe() can be used */ @@ -944,6 +1150,52 @@ static const struct iio_enum ad7124_syscalib_mode_enum = { .get = ad7124_get_syscalib_mode }; +static const char * const ad7124_filter_types[] = { + [AD7124_FILTER_TYPE_SINC3] = "sinc3", + [AD7124_FILTER_TYPE_SINC3_PF1] = "sinc3+pf1", + [AD7124_FILTER_TYPE_SINC3_PF2] = "sinc3+pf2", + [AD7124_FILTER_TYPE_SINC3_PF3] = "sinc3+pf3", + [AD7124_FILTER_TYPE_SINC3_PF4] = "sinc3+pf4", + [AD7124_FILTER_TYPE_SINC3_REJ60] = "sinc3+rej60", + [AD7124_FILTER_TYPE_SINC3_SINC1] = "sinc3+sinc1", + [AD7124_FILTER_TYPE_SINC4] = "sinc4", + [AD7124_FILTER_TYPE_SINC4_REJ60] = "sinc4+rej60", + [AD7124_FILTER_TYPE_SINC4_SINC1] = "sinc4+sinc1", +}; + +static int ad7124_set_filter_type_attr(struct iio_dev *dev, + const struct iio_chan_spec *chan, + unsigned int value) +{ + struct ad7124_state *st = iio_priv(dev); + struct ad7124_channel_config *cfg = &st->channels[chan->address].cfg; + + guard(mutex)(&st->cfgs_lock); + + cfg->live = false; + cfg->filter_type = value; + ad7124_set_channel_odr(st, chan->address); + + return 0; +} + +static int ad7124_get_filter_type_attr(struct iio_dev *dev, + const struct iio_chan_spec *chan) +{ + struct ad7124_state *st = iio_priv(dev); + + guard(mutex)(&st->cfgs_lock); + + return st->channels[chan->address].cfg.filter_type; +} + +static const struct iio_enum ad7124_filter_type_enum = { + .items = ad7124_filter_types, + .num_items = ARRAY_SIZE(ad7124_filter_types), + .set = ad7124_set_filter_type_attr, + .get = ad7124_get_filter_type_attr, +}; + static const struct iio_chan_spec_ext_info ad7124_calibsys_ext_info[] = { { .name = "sys_calibration", @@ -954,6 +1206,9 @@ static const struct iio_chan_spec_ext_info ad7124_calibsys_ext_info[] = { &ad7124_syscalib_mode_enum), IIO_ENUM_AVAILABLE("sys_calibration_mode", IIO_SHARED_BY_TYPE, &ad7124_syscalib_mode_enum), + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7124_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad7124_filter_type_enum), { } }; @@ -966,6 +1221,7 @@ static const struct iio_chan_spec ad7124_channel_template = { BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SAMP_FREQ) | BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), .scan_type = { .sign = 'u', .realbits = 24, @@ -1111,24 +1367,122 @@ static int ad7124_parse_channel_config(struct iio_dev *indio_dev, static int ad7124_setup(struct ad7124_state *st) { struct device *dev = &st->sd.spi->dev; - unsigned int fclk, power_mode; + unsigned int power_mode, clk_sel; + struct clk *mclk; int i, ret; - fclk = clk_get_rate(st->mclk); - if (!fclk) - return dev_err_probe(dev, -EINVAL, "Failed to get mclk rate\n"); + /* + * Always use full power mode for max performance. If needed, the driver + * could be adapted to use a dynamic power mode based on the requested + * output data rate. + */ + power_mode = AD7124_ADC_CONTROL_POWER_MODE_FULL; + + /* + * This "mclk" business is needed for backwards compatibility with old + * devicetrees that specified a fake clock named "mclk" to select the + * power mode. + */ + mclk = devm_clk_get_optional_enabled(dev, "mclk"); + if (IS_ERR(mclk)) + return dev_err_probe(dev, PTR_ERR(mclk), "Failed to get mclk\n"); + + if (mclk) { + unsigned long mclk_hz; - /* The power mode changes the master clock frequency */ - power_mode = ad7124_find_closest_match(ad7124_master_clk_freq_hz, - ARRAY_SIZE(ad7124_master_clk_freq_hz), - fclk); - if (fclk != ad7124_master_clk_freq_hz[power_mode]) { - ret = clk_set_rate(st->mclk, fclk); + mclk_hz = clk_get_rate(mclk); + if (!mclk_hz) + return dev_err_probe(dev, -EINVAL, + "Failed to get mclk rate\n"); + + /* + * This logic is a bit backwards, which is why it is only here + * for backwards compatibility. The driver should be able to set + * the power mode as it sees fit and the f_clk/mclk rate should + * be dynamic accordingly. But here, we are selecting a fixed + * power mode based on the given "mclk" rate. + */ + power_mode = ad7124_find_closest_match(ad7124_master_clk_freq_hz, + ARRAY_SIZE(ad7124_master_clk_freq_hz), mclk_hz); + + if (mclk_hz != ad7124_master_clk_freq_hz[power_mode]) { + ret = clk_set_rate(mclk, mclk_hz); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set mclk rate\n"); + } + + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT; + st->clk_hz = AD7124_INT_CLK_HZ; + } else if (!device_property_present(dev, "clocks") && + device_property_present(dev, "#clock-cells")) { +#ifdef CONFIG_COMMON_CLK + struct clk_hw *clk_hw; + + const char *name __free(kfree) = kasprintf(GFP_KERNEL, "%pfwP-clk", + dev_fwnode(dev)); + if (!name) + return -ENOMEM; + + clk_hw = devm_clk_hw_register_fixed_rate(dev, name, NULL, 0, + AD7124_INT_CLK_HZ); + if (IS_ERR(clk_hw)) + return dev_err_probe(dev, PTR_ERR(clk_hw), + "Failed to register clock provider\n"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + clk_hw); if (ret) - return dev_err_probe(dev, ret, "Failed to set mclk rate\n"); + return dev_err_probe(dev, ret, + "Failed to add clock provider\n"); +#endif + + /* + * Treat the clock as always on. This way we don't have to deal + * with someone trying to enable/disable the clock while we are + * reading samples. + */ + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT_OUT; + st->clk_hz = AD7124_INT_CLK_HZ; + } else { + struct clk *clk; + + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to get external clock\n"); + + if (clk) { + unsigned long clk_hz; + + clk_hz = clk_get_rate(clk); + if (!clk_hz) + return dev_err_probe(dev, -EINVAL, + "Failed to get external clock rate\n"); + + /* + * The external clock may be 4x the nominal clock rate, + * in which case the ADC needs to be configured to + * divide it by 4. Using MEGA is a bit arbitrary, but + * the expected clock rates are either 614.4 kHz or + * 2.4576 MHz, so this should work. + */ + if (clk_hz > (1 * HZ_PER_MHZ)) { + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_EXT_DIV4; + st->clk_hz = clk_hz / 4; + } else { + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_EXT; + st->clk_hz = clk_hz; + } + } else { + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT; + st->clk_hz = AD7124_INT_CLK_HZ; + } } - /* Set the power mode */ + st->adc_control &= ~AD7124_ADC_CONTROL_CLK_SEL; + st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_CLK_SEL, clk_sel); + st->adc_control &= ~AD7124_ADC_CONTROL_POWER_MODE; st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_POWER_MODE, power_mode); @@ -1138,17 +1492,22 @@ static int ad7124_setup(struct ad7124_state *st) mutex_init(&st->cfgs_lock); INIT_KFIFO(st->live_cfgs_fifo); for (i = 0; i < st->num_channels; i++) { + struct ad7124_channel_config *cfg = &st->channels[i].cfg; - ret = ad7124_init_config_vref(st, &st->channels[i].cfg); + ret = ad7124_init_config_vref(st, cfg); if (ret < 0) return ret; + /* Default filter type on the ADC after reset. */ + cfg->filter_type = AD7124_FILTER_TYPE_SINC4; + /* * 9.38 SPS is the minimum output data rate supported * regardless of the selected power mode. Round it up to 10 and * set all channels to this default value. */ - ad7124_set_channel_odr(st, i, 10); + cfg->requested_odr = 10; + ad7124_set_channel_odr(st, i); } ad7124_disable_all(&st->sd); @@ -1300,13 +1659,9 @@ static int ad7124_probe(struct spi_device *spi) ret = devm_add_action_or_reset(&spi->dev, ad7124_reg_disable, st->vref[i]); if (ret) - return dev_err_probe(dev, ret, "Failed to register disable handler for regulator #%d\n", i); + return ret; } - st->mclk = devm_clk_get_enabled(&spi->dev, "mclk"); - if (IS_ERR(st->mclk)) - return dev_err_probe(dev, PTR_ERR(st->mclk), "Failed to get mclk\n"); - ret = ad7124_soft_reset(st); if (ret < 0) return ret; diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c index 683146e83ab2..d36612352b44 100644 --- a/drivers/iio/adc/ad7173.c +++ b/drivers/iio/adc/ad7173.c @@ -8,6 +8,7 @@ * AD7175-8/AD7176-2/AD7177-2 * * Copyright (C) 2015, 2024 Analog Devices, Inc. + * Copyright (C) 2025 BayLibre, SAS */ #include <linux/array_size.h> @@ -149,7 +150,12 @@ (pin2) < st->info->num_voltage_in && \ (pin2) >= st->info->num_voltage_in_div) -#define AD7173_FILTER_ODR0_MASK GENMASK(5, 0) +#define AD7173_FILTER_SINC3_MAP BIT(15) +#define AD7173_FILTER_SINC3_MAP_DIV GENMASK(14, 0) +#define AD7173_FILTER_ENHFILTEN BIT(11) +#define AD7173_FILTER_ENHFILT_MASK GENMASK(10, 8) +#define AD7173_FILTER_ORDER BIT(6) +#define AD7173_FILTER_ODR_MASK GENMASK(5, 0) #define AD7173_MAX_CONFIGS 8 #define AD4111_OW_DET_THRSH_MV 300 @@ -190,6 +196,15 @@ struct ad7173_device_info { u8 num_gpios; }; +enum ad7173_filter_type { + AD7173_FILTER_SINC3, + AD7173_FILTER_SINC5_SINC1, + AD7173_FILTER_SINC5_SINC1_PF1, + AD7173_FILTER_SINC5_SINC1_PF2, + AD7173_FILTER_SINC5_SINC1_PF3, + AD7173_FILTER_SINC5_SINC1_PF4, +}; + struct ad7173_channel_config { /* Openwire detection threshold */ unsigned int openwire_thrsh_raw; @@ -205,8 +220,10 @@ struct ad7173_channel_config { struct_group(config_props, bool bipolar; bool input_buf; - u8 odr; + u16 sinc3_odr_div; + u8 sinc5_odr_index; u8 ref_sel; + enum ad7173_filter_type filter_type; ); }; @@ -266,6 +283,24 @@ static const unsigned int ad7175_sinc5_data_rates[] = { 5000, /* 20 */ }; +/** + * ad7173_sinc3_odr_div_from_odr() - Convert ODR to divider value + * @odr_millihz: ODR (sampling_frequency) in milliHz + * Returns: Divider value for SINC3 filter to pass. + */ +static u16 ad7173_sinc3_odr_div_from_odr(u32 odr_millihz) +{ + /* + * Divider is f_MOD (1 MHz) / 32 / ODR. ODR freq is in milliHz, so + * we need to convert f_MOD to the same units. When SING_CYC=1 or + * multiple channels are enabled (currently always the case), there + * is an additional factor of 3. + */ + u32 div = DIV_ROUND_CLOSEST(MEGA * MILLI, odr_millihz * 32 * 3); + /* Avoid divide by 0 and limit to register field size. */ + return clamp(div, 1U, AD7173_FILTER_SINC3_MAP_DIV); +} + static unsigned int ad4111_current_channel_config[] = { /* Ain sel: pos neg */ 0x1E8, /* 15:IIN0+ 8:IIN0− */ @@ -369,7 +404,48 @@ static const struct iio_enum ad7173_syscalib_mode_enum = { .get = ad7173_get_syscalib_mode }; -static const struct iio_chan_spec_ext_info ad7173_calibsys_ext_info[] = { +static const char * const ad7173_filter_types_str[] = { + [AD7173_FILTER_SINC3] = "sinc3", + [AD7173_FILTER_SINC5_SINC1] = "sinc5+sinc1", + [AD7173_FILTER_SINC5_SINC1_PF1] = "sinc5+sinc1+pf1", + [AD7173_FILTER_SINC5_SINC1_PF2] = "sinc5+sinc1+pf2", + [AD7173_FILTER_SINC5_SINC1_PF3] = "sinc5+sinc1+pf3", + [AD7173_FILTER_SINC5_SINC1_PF4] = "sinc5+sinc1+pf4", +}; + +static int ad7173_set_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int val) +{ + struct ad7173_state *st = iio_priv(indio_dev); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + st->channels[chan->address].cfg.filter_type = val; + st->channels[chan->address].cfg.live = false; + + iio_device_release_direct(indio_dev); + + return 0; +} + +static int ad7173_get_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7173_state *st = iio_priv(indio_dev); + + return st->channels[chan->address].cfg.filter_type; +} + +static const struct iio_enum ad7173_filter_type_enum = { + .items = ad7173_filter_types_str, + .num_items = ARRAY_SIZE(ad7173_filter_types_str), + .set = ad7173_set_filter_type, + .get = ad7173_get_filter_type, +}; + +static const struct iio_chan_spec_ext_info ad7173_chan_spec_ext_info[] = { { .name = "sys_calibration", .write = ad7173_write_syscalib, @@ -379,6 +455,16 @@ static const struct iio_chan_spec_ext_info ad7173_calibsys_ext_info[] = { &ad7173_syscalib_mode_enum), IIO_ENUM_AVAILABLE("sys_calibration_mode", IIO_SHARED_BY_TYPE, &ad7173_syscalib_mode_enum), + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7173_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad7173_filter_type_enum), + { } +}; + +static const struct iio_chan_spec_ext_info ad7173_temp_chan_spec_ext_info[] = { + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7173_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad7173_filter_type_enum), { } }; @@ -582,14 +668,18 @@ static bool ad7173_is_setup_equal(const struct ad7173_channel_config *cfg1, sizeof(struct { bool bipolar; bool input_buf; - u8 odr; + u16 sinc3_odr_div; + u8 sinc5_odr_index; u8 ref_sel; + enum ad7173_filter_type filter_type; })); return cfg1->bipolar == cfg2->bipolar && cfg1->input_buf == cfg2->input_buf && - cfg1->odr == cfg2->odr && - cfg1->ref_sel == cfg2->ref_sel; + cfg1->sinc3_odr_div == cfg2->sinc3_odr_div && + cfg1->sinc5_odr_index == cfg2->sinc5_odr_index && + cfg1->ref_sel == cfg2->ref_sel && + cfg1->filter_type == cfg2->filter_type; } static struct ad7173_channel_config * @@ -630,6 +720,7 @@ static int ad7173_load_config(struct ad7173_state *st, { unsigned int config; int free_cfg_slot, ret; + u8 post_filter_enable, post_filter_select; free_cfg_slot = ida_alloc_range(&st->cfg_slots_status, 0, st->info->num_configs - 1, GFP_KERNEL); @@ -649,8 +740,49 @@ static int ad7173_load_config(struct ad7173_state *st, if (ret) return ret; + /* + * When SINC3_MAP flag is enabled, the rest of the register has a + * different meaning. We are using this option to allow the most + * possible sampling frequencies with SINC3 filter. + */ + if (cfg->filter_type == AD7173_FILTER_SINC3) + return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2, + FIELD_PREP(AD7173_FILTER_SINC3_MAP, 1) | + FIELD_PREP(AD7173_FILTER_SINC3_MAP_DIV, + cfg->sinc3_odr_div)); + + switch (cfg->filter_type) { + case AD7173_FILTER_SINC5_SINC1_PF1: + post_filter_enable = 1; + post_filter_select = 2; + break; + case AD7173_FILTER_SINC5_SINC1_PF2: + post_filter_enable = 1; + post_filter_select = 3; + break; + case AD7173_FILTER_SINC5_SINC1_PF3: + post_filter_enable = 1; + post_filter_select = 5; + break; + case AD7173_FILTER_SINC5_SINC1_PF4: + post_filter_enable = 1; + post_filter_select = 6; + break; + default: + post_filter_enable = 0; + post_filter_select = 0; + break; + } + return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2, - AD7173_FILTER_ODR0_MASK & cfg->odr); + FIELD_PREP(AD7173_FILTER_SINC3_MAP, 0) | + FIELD_PREP(AD7173_FILTER_ENHFILT_MASK, + post_filter_enable) | + FIELD_PREP(AD7173_FILTER_ENHFILTEN, + post_filter_select) | + FIELD_PREP(AD7173_FILTER_ORDER, 0) | + FIELD_PREP(AD7173_FILTER_ODR_MASK, + cfg->sinc5_odr_index)); } static int ad7173_config_channel(struct ad7173_state *st, int addr) @@ -761,6 +893,7 @@ static const struct ad_sigma_delta_info ad7173_sigma_delta_info_4_slots = { .set_mode = ad7173_set_mode, .has_registers = true, .has_named_irqs = true, + .supports_spi_offload = true, .addr_shift = 0, .read_mask = BIT(6), .status_ch_mask = GENMASK(3, 0), @@ -777,6 +910,7 @@ static const struct ad_sigma_delta_info ad7173_sigma_delta_info_8_slots = { .set_mode = ad7173_set_mode, .has_registers = true, .has_named_irqs = true, + .supports_spi_offload = true, .addr_shift = 0, .read_mask = BIT(6), .status_ch_mask = GENMASK(3, 0), @@ -793,6 +927,7 @@ static const struct ad_sigma_delta_info ad7173_sigma_delta_info_16_slots = { .set_mode = ad7173_set_mode, .has_registers = true, .has_named_irqs = true, + .supports_spi_offload = true, .addr_shift = 0, .read_mask = BIT(6), .status_ch_mask = GENMASK(3, 0), @@ -1180,7 +1315,14 @@ static int ad7173_read_raw(struct iio_dev *indio_dev, return -EINVAL; } case IIO_CHAN_INFO_SAMP_FREQ: - reg = st->channels[chan->address].cfg.odr; + if (st->channels[chan->address].cfg.filter_type == AD7173_FILTER_SINC3) { + /* Inverse operation of ad7173_sinc3_odr_div_from_odr() */ + *val = MEGA; + *val2 = 3 * 32 * st->channels[chan->address].cfg.sinc3_odr_div; + return IIO_VAL_FRACTIONAL; + } + + reg = st->channels[chan->address].cfg.sinc5_odr_index; *val = st->info->sinc5_data_rates[reg] / MILLI; *val2 = (st->info->sinc5_data_rates[reg] % MILLI) * (MICRO / MILLI); @@ -1218,6 +1360,10 @@ static int ad7173_write_raw(struct iio_dev *indio_dev, * * This will cause the reading of CH1 to be actually done once every * 200.16ms, an effective rate of 4.99sps. + * + * Both the sinc5 and sinc3 rates are set here so that if the filter + * type is changed, the requested rate will still be set (aside from + * rounding differences). */ case IIO_CHAN_INFO_SAMP_FREQ: freq = val * MILLI + val2 / MILLI; @@ -1226,7 +1372,8 @@ static int ad7173_write_raw(struct iio_dev *indio_dev, break; cfg = &st->channels[chan->address].cfg; - cfg->odr = i; + cfg->sinc5_odr_index = i; + cfg->sinc3_odr_div = ad7173_sinc3_odr_div_from_odr(freq); cfg->live = false; break; @@ -1243,17 +1390,40 @@ static int ad7173_update_scan_mode(struct iio_dev *indio_dev, const unsigned long *scan_mask) { struct ad7173_state *st = iio_priv(indio_dev); + u16 sinc3_count = 0; + u16 sinc3_div = 0; int i, j, k, ret; for (i = 0; i < indio_dev->num_channels; i++) { - if (test_bit(i, scan_mask)) + const struct ad7173_channel_config *cfg = &st->channels[i].cfg; + + if (test_bit(i, scan_mask)) { + if (cfg->filter_type == AD7173_FILTER_SINC3) { + sinc3_count++; + + if (sinc3_div == 0) { + sinc3_div = cfg->sinc3_odr_div; + } else if (sinc3_div != cfg->sinc3_odr_div) { + dev_err(&st->sd.spi->dev, + "All enabled channels must have the same sampling_frequency for sinc3 filter_type\n"); + return -EINVAL; + } + } + ret = ad7173_set_channel(&st->sd, i); - else + } else { ret = ad_sd_write_reg(&st->sd, AD7173_REG_CH(i), 2, 0); + } if (ret < 0) return ret; } + if (sinc3_count && sinc3_count < bitmap_weight(scan_mask, indio_dev->num_channels)) { + dev_err(&st->sd.spi->dev, + "All enabled channels must have sinc3 filter_type\n"); + return -EINVAL; + } + /* * On some chips, there are more channels that setups, so if there were * more unique setups requested than the number of available slots, @@ -1396,7 +1566,7 @@ static const struct iio_chan_spec ad7173_channel_template = { .storagebits = 32, .endianness = IIO_BE, }, - .ext_info = ad7173_calibsys_ext_info, + .ext_info = ad7173_chan_spec_ext_info, }; static const struct iio_chan_spec ad7173_temp_iio_channel_template = { @@ -1412,6 +1582,7 @@ static const struct iio_chan_spec ad7173_temp_iio_channel_template = { .storagebits = 32, .endianness = IIO_BE, }, + .ext_info = ad7173_temp_chan_spec_ext_info, }; static void ad7173_disable_regulators(void *data) @@ -1652,12 +1823,21 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) chan_st_priv->cfg.bipolar = false; chan_st_priv->cfg.input_buf = st->info->has_input_buf; chan_st_priv->cfg.ref_sel = AD7173_SETUP_REF_SEL_INT_REF; - chan_st_priv->cfg.odr = st->info->odr_start_value; + chan_st_priv->cfg.sinc3_odr_div = ad7173_sinc3_odr_div_from_odr( + st->info->sinc5_data_rates[st->info->odr_start_value] + ); + chan_st_priv->cfg.sinc5_odr_index = st->info->odr_start_value; + chan_st_priv->cfg.filter_type = AD7173_FILTER_SINC5_SINC1; chan_st_priv->cfg.openwire_comp_chan = -1; st->adc_mode |= AD7173_ADC_MODE_REF_EN; if (st->info->data_reg_only_16bit) chan_arr[chan_index].scan_type = ad4113_scan_type; + if (ad_sigma_delta_has_spi_offload(&st->sd)) { + chan_arr[chan_index].scan_type.storagebits = 32; + chan_arr[chan_index].scan_type.endianness = IIO_CPU; + } + chan_index++; } @@ -1719,7 +1899,11 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) chan->scan_index = chan_index; chan->channel = ain[0]; chan_st_priv->cfg.input_buf = st->info->has_input_buf; - chan_st_priv->cfg.odr = st->info->odr_start_value; + chan_st_priv->cfg.sinc3_odr_div = ad7173_sinc3_odr_div_from_odr( + st->info->sinc5_data_rates[st->info->odr_start_value] + ); + chan_st_priv->cfg.sinc5_odr_index = st->info->odr_start_value; + chan_st_priv->cfg.filter_type = AD7173_FILTER_SINC5_SINC1; chan_st_priv->cfg.openwire_comp_chan = -1; chan_st_priv->cfg.bipolar = fwnode_property_read_bool(child, "bipolar"); @@ -1748,6 +1932,12 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) if (st->info->data_reg_only_16bit) chan_arr[chan_index].scan_type = ad4113_scan_type; + /* Assuming SPI offload is ad411x_ad717x HDL project. */ + if (ad_sigma_delta_has_spi_offload(&st->sd)) { + chan_arr[chan_index].scan_type.storagebits = 32; + chan_arr[chan_index].scan_type.endianness = IIO_CPU; + } + chan_index++; } return 0; @@ -1780,8 +1970,7 @@ static int ad7173_fw_parse_device_config(struct iio_dev *indio_dev) ret = devm_add_action_or_reset(dev, ad7173_disable_regulators, st); if (ret) - return dev_err_probe(dev, ret, - "Failed to add regulators disable action\n"); + return ret; ret = device_property_match_property_string(dev, "clock-names", ad7173_clk_sel, diff --git a/drivers/iio/adc/ad7476.c b/drivers/iio/adc/ad7476.c index aea734aa06bd..1bec6657394c 100644 --- a/drivers/iio/adc/ad7476.c +++ b/drivers/iio/adc/ad7476.c @@ -6,6 +6,7 @@ * Copyright 2010 Analog Devices Inc. */ +#include <linux/bitops.h> #include <linux/device.h> #include <linux/kernel.h> #include <linux/slab.h> @@ -27,22 +28,24 @@ struct ad7476_state; struct ad7476_chip_info { - unsigned int int_vref_uv; + unsigned int int_vref_mv; struct iio_chan_spec channel[2]; - /* channels used when convst gpio is defined */ - struct iio_chan_spec convst_channel[2]; void (*reset)(struct ad7476_state *); + void (*conversion_pre_op)(struct ad7476_state *st); + void (*conversion_post_op)(struct ad7476_state *st); bool has_vref; bool has_vdrive; + bool convstart_required; }; struct ad7476_state { struct spi_device *spi; const struct ad7476_chip_info *chip_info; - struct regulator *ref_reg; struct gpio_desc *convst_gpio; struct spi_transfer xfer; struct spi_message msg; + struct iio_chan_spec channel[2]; + int scale_mv; /* * DMA (thus cache coherency maintenance) may require the * transfer buffers to live in their own cache lines. @@ -52,40 +55,29 @@ struct ad7476_state { unsigned char data[ALIGN(2, sizeof(s64)) + sizeof(s64)] __aligned(IIO_DMA_MINALIGN); }; -enum ad7476_supported_device_ids { - ID_AD7091, - ID_AD7091R, - ID_AD7273, - ID_AD7274, - ID_AD7276, - ID_AD7277, - ID_AD7278, - ID_AD7466, - ID_AD7467, - ID_AD7468, - ID_AD7475, - ID_AD7495, - ID_AD7940, - ID_ADC081S, - ID_ADC101S, - ID_ADC121S, - ID_ADS7866, - ID_ADS7867, - ID_ADS7868, - ID_LTC2314_14, -}; - static void ad7091_convst(struct ad7476_state *st) { if (!st->convst_gpio) return; - gpiod_set_value(st->convst_gpio, 0); + gpiod_set_value_cansleep(st->convst_gpio, 0); udelay(1); /* CONVST pulse width: 10 ns min */ - gpiod_set_value(st->convst_gpio, 1); + gpiod_set_value_cansleep(st->convst_gpio, 1); udelay(1); /* Conversion time: 650 ns max */ } +static void bd79105_convst_disable(struct ad7476_state *st) +{ + gpiod_set_value_cansleep(st->convst_gpio, 0); +} + +static void bd79105_convst_enable(struct ad7476_state *st) +{ + gpiod_set_value_cansleep(st->convst_gpio, 1); + /* Worst case, 2790 ns required for conversion */ + ndelay(2790); +} + static irqreturn_t ad7476_trigger_handler(int irq, void *p) { struct iio_poll_func *pf = p; @@ -93,7 +85,8 @@ static irqreturn_t ad7476_trigger_handler(int irq, void *p) struct ad7476_state *st = iio_priv(indio_dev); int b_sent; - ad7091_convst(st); + if (st->chip_info->conversion_pre_op) + st->chip_info->conversion_pre_op(st); b_sent = spi_sync(st->spi, &st->msg); if (b_sent < 0) @@ -102,6 +95,8 @@ static irqreturn_t ad7476_trigger_handler(int irq, void *p) iio_push_to_buffers_with_ts(indio_dev, st->data, sizeof(st->data), iio_get_time_ns(indio_dev)); done: + if (st->chip_info->conversion_post_op) + st->chip_info->conversion_post_op(st); iio_trigger_notify_done(indio_dev->trig); return IRQ_HANDLED; @@ -117,12 +112,16 @@ static int ad7476_scan_direct(struct ad7476_state *st) { int ret; - ad7091_convst(st); + if (st->chip_info->conversion_pre_op) + st->chip_info->conversion_pre_op(st); ret = spi_sync(st->spi, &st->msg); if (ret) return ret; + if (st->chip_info->conversion_post_op) + st->chip_info->conversion_post_op(st); + return be16_to_cpup((__be16 *)st->data); } @@ -134,7 +133,6 @@ static int ad7476_read_raw(struct iio_dev *indio_dev, { int ret; struct ad7476_state *st = iio_priv(indio_dev); - int scale_uv; switch (m) { case IIO_CHAN_INFO_RAW: @@ -145,18 +143,11 @@ static int ad7476_read_raw(struct iio_dev *indio_dev, if (ret < 0) return ret; - *val = (ret >> st->chip_info->channel[0].scan_type.shift) & - GENMASK(st->chip_info->channel[0].scan_type.realbits - 1, 0); + *val = (ret >> chan->scan_type.shift) & + GENMASK(chan->scan_type.realbits - 1, 0); return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - if (st->ref_reg) { - scale_uv = regulator_get_voltage(st->ref_reg); - if (scale_uv < 0) - return scale_uv; - } else { - scale_uv = st->chip_info->int_vref_uv; - } - *val = scale_uv / 1000; + *val = st->scale_mv; *val2 = chan->scan_type.realbits; return IIO_VAL_FRACTIONAL_LOG2; } @@ -185,125 +176,147 @@ static int ad7476_read_raw(struct iio_dev *indio_dev, #define AD7940_CHAN(bits) _AD7476_CHAN((bits), 15 - (bits), \ BIT(IIO_CHAN_INFO_RAW)) #define AD7091R_CHAN(bits) _AD7476_CHAN((bits), 16 - (bits), 0) -#define AD7091R_CONVST_CHAN(bits) _AD7476_CHAN((bits), 16 - (bits), \ - BIT(IIO_CHAN_INFO_RAW)) #define ADS786X_CHAN(bits) _AD7476_CHAN((bits), 12 - (bits), \ BIT(IIO_CHAN_INFO_RAW)) -static const struct ad7476_chip_info ad7476_chip_info_tbl[] = { - [ID_AD7091] = { - .channel[0] = AD7091R_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .convst_channel[0] = AD7091R_CONVST_CHAN(12), - .convst_channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .reset = ad7091_reset, - }, - [ID_AD7091R] = { - .channel[0] = AD7091R_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .convst_channel[0] = AD7091R_CONVST_CHAN(12), - .convst_channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .int_vref_uv = 2500000, - .has_vref = true, - .reset = ad7091_reset, - }, - [ID_AD7273] = { - .channel[0] = AD7940_CHAN(10), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .has_vref = true, - }, - [ID_AD7274] = { - .channel[0] = AD7940_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .has_vref = true, - }, - [ID_AD7276] = { - .channel[0] = AD7940_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_AD7277] = { - .channel[0] = AD7940_CHAN(10), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_AD7278] = { - .channel[0] = AD7940_CHAN(8), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_AD7466] = { - .channel[0] = AD7476_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_AD7467] = { - .channel[0] = AD7476_CHAN(10), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_AD7468] = { - .channel[0] = AD7476_CHAN(8), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_AD7475] = { - .channel[0] = AD7476_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .has_vref = true, - .has_vdrive = true, - }, - [ID_AD7495] = { - .channel[0] = AD7476_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .int_vref_uv = 2500000, - .has_vdrive = true, - }, - [ID_AD7940] = { - .channel[0] = AD7940_CHAN(14), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_ADC081S] = { - .channel[0] = ADC081S_CHAN(8), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_ADC101S] = { - .channel[0] = ADC081S_CHAN(10), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_ADC121S] = { - .channel[0] = ADC081S_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_ADS7866] = { - .channel[0] = ADS786X_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_ADS7867] = { - .channel[0] = ADS786X_CHAN(10), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_ADS7868] = { - .channel[0] = ADS786X_CHAN(8), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_LTC2314_14] = { - .channel[0] = AD7940_CHAN(14), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .has_vref = true, - }, +static const struct ad7476_chip_info ad7091_chip_info = { + .channel[0] = AD7091R_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .conversion_pre_op = ad7091_convst, + .reset = ad7091_reset, }; -static const struct iio_info ad7476_info = { - .read_raw = &ad7476_read_raw, +static const struct ad7476_chip_info ad7091r_chip_info = { + .channel[0] = AD7091R_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .conversion_pre_op = ad7091_convst, + .int_vref_mv = 2500, + .has_vref = true, + .reset = ad7091_reset, }; -static void ad7476_reg_disable(void *data) -{ - struct regulator *reg = data; +static const struct ad7476_chip_info ad7273_chip_info = { + .channel[0] = AD7940_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .has_vref = true, +}; - regulator_disable(reg); -} +static const struct ad7476_chip_info ad7274_chip_info = { + .channel[0] = AD7940_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .has_vref = true, +}; + +static const struct ad7476_chip_info ad7276_chip_info = { + .channel[0] = AD7940_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7277_chip_info = { + .channel[0] = AD7940_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7278_chip_info = { + .channel[0] = AD7940_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7466_chip_info = { + .channel[0] = AD7476_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7467_chip_info = { + .channel[0] = AD7476_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7468_chip_info = { + .channel[0] = AD7476_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7475_chip_info = { + .channel[0] = AD7476_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .has_vref = true, + .has_vdrive = true, +}; + +static const struct ad7476_chip_info ad7495_chip_info = { + .channel[0] = AD7476_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .int_vref_mv = 2500, + .has_vdrive = true, +}; + +static const struct ad7476_chip_info ad7940_chip_info = { + .channel[0] = AD7940_CHAN(14), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info adc081s_chip_info = { + .channel[0] = ADC081S_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info adc101s_chip_info = { + .channel[0] = ADC081S_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info adc121s_chip_info = { + .channel[0] = ADC081S_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ads7866_chip_info = { + .channel[0] = ADS786X_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ads7867_chip_info = { + .channel[0] = ADS786X_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ads7868_chip_info = { + .channel[0] = ADS786X_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ltc2314_14_chip_info = { + .channel[0] = AD7940_CHAN(14), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .has_vref = true, +}; + +static const struct ad7476_chip_info bd79105_chip_info = { + .channel[0] = AD7091R_CHAN(16), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + /* + * The BD79105 starts ADC data conversion when the CONVSTART line is + * set HIGH. The CONVSTART must be kept HIGH until the data has been + * read from the ADC. + */ + .conversion_pre_op = bd79105_convst_enable, + .conversion_post_op = bd79105_convst_disable, + /* BD79105 won't do conversion without convstart */ + .convstart_required = true, + .has_vref = true, + .has_vdrive = true, +}; + +static const struct iio_info ad7476_info = { + .read_raw = &ad7476_read_raw, +}; static int ad7476_probe(struct spi_device *spi) { struct ad7476_state *st; struct iio_dev *indio_dev; - struct regulator *reg; + unsigned int i; int ret; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); @@ -311,61 +324,37 @@ static int ad7476_probe(struct spi_device *spi) return -ENOMEM; st = iio_priv(indio_dev); - st->chip_info = - &ad7476_chip_info_tbl[spi_get_device_id(spi)->driver_data]; - reg = devm_regulator_get(&spi->dev, "vcc"); - if (IS_ERR(reg)) - return PTR_ERR(reg); + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODEV; - ret = regulator_enable(reg); - if (ret) - return ret; - - ret = devm_add_action_or_reset(&spi->dev, ad7476_reg_disable, reg); - if (ret) - return ret; - - /* Either vcc or vref (below) as appropriate */ - if (!st->chip_info->int_vref_uv) - st->ref_reg = reg; + /* Use VCC for reference voltage if vref / internal vref aren't used */ + if (!st->chip_info->int_vref_mv && !st->chip_info->has_vref) { + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vcc"); + if (ret < 0) + return ret; + st->scale_mv = ret / 1000; + } else { + ret = devm_regulator_get_enable(&spi->dev, "vcc"); + if (ret < 0) + return ret; + } if (st->chip_info->has_vref) { - - /* If a device has an internal reference vref is optional */ - if (st->chip_info->int_vref_uv) { - reg = devm_regulator_get_optional(&spi->dev, "vref"); - if (IS_ERR(reg) && (PTR_ERR(reg) != -ENODEV)) - return PTR_ERR(reg); - } else { - reg = devm_regulator_get(&spi->dev, "vref"); - if (IS_ERR(reg)) - return PTR_ERR(reg); - } - - if (!IS_ERR(reg)) { - ret = regulator_enable(reg); - if (ret) - return ret; - - ret = devm_add_action_or_reset(&spi->dev, - ad7476_reg_disable, - reg); - if (ret) + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0) { + /* Vref is optional if a device has an internal reference */ + if (!st->chip_info->int_vref_mv || ret != -ENODEV) return ret; - st->ref_reg = reg; } else { - /* - * Can only get here if device supports both internal - * and external reference, but the regulator connected - * to the external reference is not connected. - * Set the reference regulator pointer to NULL to - * indicate this. - */ - st->ref_reg = NULL; + st->scale_mv = ret / 1000; } } + if (!st->scale_mv) + st->scale_mv = st->chip_info->int_vref_mv; + if (st->chip_info->has_vdrive) { ret = devm_regulator_get_enable(&spi->dev, "vdrive"); if (ret) @@ -378,20 +367,35 @@ static int ad7476_probe(struct spi_device *spi) if (IS_ERR(st->convst_gpio)) return PTR_ERR(st->convst_gpio); + if (st->chip_info->convstart_required && !st->convst_gpio) + return dev_err_probe(&spi->dev, -EINVAL, "No convstart GPIO\n"); + + /* + * This will never happen. Unless someone changes the channel specs + * in this driver. And if someone does, without changing the loop + * below, then we'd better immediately produce a big fat error, before + * the change proceeds from that developer's table. + */ + static_assert(ARRAY_SIZE(st->channel) == ARRAY_SIZE(st->chip_info->channel)); + for (i = 0; i < ARRAY_SIZE(st->channel); i++) { + st->channel[i] = st->chip_info->channel[i]; + if (st->convst_gpio) + __set_bit(IIO_CHAN_INFO_RAW, + &st->channel[i].info_mask_separate); + } + st->spi = spi; indio_dev->name = spi_get_device_id(spi)->name; indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = st->chip_info->channel; - indio_dev->num_channels = 2; + indio_dev->channels = st->channel; + indio_dev->num_channels = ARRAY_SIZE(st->channel); indio_dev->info = &ad7476_info; - if (st->convst_gpio) - indio_dev->channels = st->chip_info->convst_channel; /* Setup default message */ st->xfer.rx_buf = &st->data; - st->xfer.len = st->chip_info->channel[0].scan_type.storagebits / 8; + st->xfer.len = indio_dev->channels[0].scan_type.storagebits / 8; spi_message_init(&st->msg); spi_message_add_tail(&st->xfer, &st->msg); @@ -408,41 +412,42 @@ static int ad7476_probe(struct spi_device *spi) } static const struct spi_device_id ad7476_id[] = { - { "ad7091", ID_AD7091 }, - { "ad7091r", ID_AD7091R }, - { "ad7273", ID_AD7273 }, - { "ad7274", ID_AD7274 }, - { "ad7276", ID_AD7276}, - { "ad7277", ID_AD7277 }, - { "ad7278", ID_AD7278 }, - { "ad7466", ID_AD7466 }, - { "ad7467", ID_AD7467 }, - { "ad7468", ID_AD7468 }, - { "ad7475", ID_AD7475 }, - { "ad7476", ID_AD7466 }, - { "ad7476a", ID_AD7466 }, - { "ad7477", ID_AD7467 }, - { "ad7477a", ID_AD7467 }, - { "ad7478", ID_AD7468 }, - { "ad7478a", ID_AD7468 }, - { "ad7495", ID_AD7495 }, - { "ad7910", ID_AD7467 }, - { "ad7920", ID_AD7466 }, - { "ad7940", ID_AD7940 }, - { "adc081s", ID_ADC081S }, - { "adc101s", ID_ADC101S }, - { "adc121s", ID_ADC121S }, - { "ads7866", ID_ADS7866 }, - { "ads7867", ID_ADS7867 }, - { "ads7868", ID_ADS7868 }, + { "ad7091", (kernel_ulong_t)&ad7091_chip_info }, + { "ad7091r", (kernel_ulong_t)&ad7091r_chip_info }, + { "ad7273", (kernel_ulong_t)&ad7273_chip_info }, + { "ad7274", (kernel_ulong_t)&ad7274_chip_info }, + { "ad7276", (kernel_ulong_t)&ad7276_chip_info }, + { "ad7277", (kernel_ulong_t)&ad7277_chip_info }, + { "ad7278", (kernel_ulong_t)&ad7278_chip_info }, + { "ad7466", (kernel_ulong_t)&ad7466_chip_info }, + { "ad7467", (kernel_ulong_t)&ad7467_chip_info }, + { "ad7468", (kernel_ulong_t)&ad7468_chip_info }, + { "ad7475", (kernel_ulong_t)&ad7475_chip_info }, + { "ad7476", (kernel_ulong_t)&ad7466_chip_info }, + { "ad7476a", (kernel_ulong_t)&ad7466_chip_info }, + { "ad7477", (kernel_ulong_t)&ad7467_chip_info }, + { "ad7477a", (kernel_ulong_t)&ad7467_chip_info }, + { "ad7478", (kernel_ulong_t)&ad7468_chip_info }, + { "ad7478a", (kernel_ulong_t)&ad7468_chip_info }, + { "ad7495", (kernel_ulong_t)&ad7495_chip_info }, + { "ad7910", (kernel_ulong_t)&ad7467_chip_info }, + { "ad7920", (kernel_ulong_t)&ad7466_chip_info }, + { "ad7940", (kernel_ulong_t)&ad7940_chip_info }, + { "adc081s", (kernel_ulong_t)&adc081s_chip_info }, + { "adc101s", (kernel_ulong_t)&adc101s_chip_info }, + { "adc121s", (kernel_ulong_t)&adc121s_chip_info }, + { "ads7866", (kernel_ulong_t)&ads7866_chip_info }, + { "ads7867", (kernel_ulong_t)&ads7867_chip_info }, + { "ads7868", (kernel_ulong_t)&ads7868_chip_info }, + { "bd79105", (kernel_ulong_t)&bd79105_chip_info }, /* * The ROHM BU79100G is identical to the TI's ADS7866 from the software * point of view. The binding document mandates the ADS7866 to be * marked as a fallback for the BU79100G, but we still need the SPI ID * here to make the module loading work. */ - { "bu79100g", ID_ADS7866 }, - { "ltc2314-14", ID_LTC2314_14 }, + { "bu79100g", (kernel_ulong_t)&ads7866_chip_info }, + { "ltc2314-14", (kernel_ulong_t)<c2314_14_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ad7476_id); diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c index ca8fa91796ca..872c88d0c86c 100644 --- a/drivers/iio/adc/ad7768-1.c +++ b/drivers/iio/adc/ad7768-1.c @@ -217,7 +217,7 @@ struct ad7768_state { struct spi_device *spi; struct regmap *regmap; struct regmap *regmap24; - struct regulator *vref; + int vref_uv; struct regulator_dev *vcm_rdev; unsigned int vcm_output_sel; struct clk *mclk; @@ -687,8 +687,6 @@ static int ad7768_set_freq(struct ad7768_state *st, int ret; freq = clamp(freq, 50, 1024000); - if (freq == 0) - return -EINVAL; mclk_div = DIV_ROUND_CLOSEST(st->mclk_freq, freq * st->oversampling_ratio); /* Find the closest match for the desired sampling frequency */ @@ -776,7 +774,7 @@ static int ad7768_read_raw(struct iio_dev *indio_dev, { struct ad7768_state *st = iio_priv(indio_dev); const struct iio_scan_type *scan_type; - int scale_uv, ret, temp; + int ret, temp; scan_type = iio_get_current_scan_type(indio_dev, chan); if (IS_ERR(scan_type)) @@ -797,11 +795,7 @@ static int ad7768_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - scale_uv = regulator_get_voltage(st->vref); - if (scale_uv < 0) - return scale_uv; - - *val = (scale_uv * 2) / 1000; + *val = (st->vref_uv * 2) / 1000; *val2 = scan_type->realbits; return IIO_VAL_FRACTIONAL_LOG2; @@ -1134,13 +1128,6 @@ static const struct iio_trigger_ops ad7768_trigger_ops = { .validate_device = iio_trigger_validate_own_device, }; -static void ad7768_regulator_disable(void *data) -{ - struct ad7768_state *st = data; - - regulator_disable(st->vref); -} - static int ad7768_set_channel_label(struct iio_dev *indio_dev, int num_channels) { @@ -1372,19 +1359,11 @@ static int ad7768_probe(struct spi_device *spi) return dev_err_probe(&spi->dev, PTR_ERR(st->regmap24), "Failed to initialize regmap24"); - st->vref = devm_regulator_get(&spi->dev, "vref"); - if (IS_ERR(st->vref)) - return PTR_ERR(st->vref); - - ret = regulator_enable(st->vref); - if (ret) { - dev_err(&spi->dev, "Failed to enable specified vref supply\n"); - return ret; - } - - ret = devm_add_action_or_reset(&spi->dev, ad7768_regulator_disable, st); - if (ret) - return ret; + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "Failed to get VREF voltage\n"); + st->vref_uv = ret; st->mclk = devm_clk_get_enabled(&spi->dev, "mclk"); if (IS_ERR(st->mclk)) diff --git a/drivers/iio/adc/ad7779.c b/drivers/iio/adc/ad7779.c index 845adc510239..aac5049c9a07 100644 --- a/drivers/iio/adc/ad7779.c +++ b/drivers/iio/adc/ad7779.c @@ -25,6 +25,7 @@ #include <linux/units.h> #include <linux/iio/iio.h> +#include <linux/iio/backend.h> #include <linux/iio/buffer.h> #include <linux/iio/sysfs.h> #include <linux/iio/trigger.h> @@ -145,6 +146,7 @@ struct ad7779_state { struct completion completion; unsigned int sampling_freq; enum ad7779_filter filter_enabled; + struct iio_backend *back; /* * DMA (thus cache coherency maintenance) requires the * transfer buffers to live in their own cache lines. @@ -630,12 +632,38 @@ static int ad7779_reset(struct iio_dev *indio_dev, struct gpio_desc *reset_gpio) return ret; } +static int ad7779_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7779_state *st = iio_priv(indio_dev); + unsigned int c; + int ret; + + for (c = 0; c < AD7779_NUM_CHANNELS; c++) { + if (test_bit(c, scan_mask)) + ret = iio_backend_chan_enable(st->back, c); + else + ret = iio_backend_chan_disable(st->back, c); + if (ret) + return ret; + } + + return 0; +} + static const struct iio_info ad7779_info = { .read_raw = ad7779_read_raw, .write_raw = ad7779_write_raw, .debugfs_reg_access = &ad7779_reg_access, }; +static const struct iio_info ad7779_info_data = { + .read_raw = ad7779_read_raw, + .write_raw = ad7779_write_raw, + .debugfs_reg_access = &ad7779_reg_access, + .update_scan_mode = &ad7779_update_scan_mode, +}; + static const struct iio_enum ad7779_filter_enum = { .items = ad7779_filter_type, .num_items = ARRAY_SIZE(ad7779_filter_type), @@ -752,6 +780,125 @@ static int ad7779_conf(struct ad7779_state *st, struct gpio_desc *start_gpio) return 0; } +static int ad7779_set_data_lines(struct iio_dev *indio_dev, u32 num_lanes) +{ + struct ad7779_state *st = iio_priv(indio_dev); + int ret; + + if (num_lanes != 1 && num_lanes != 2 && num_lanes != 4) + return -EINVAL; + + ret = ad7779_set_sampling_frequency(st, num_lanes * AD7779_DEFAULT_SAMPLING_1LINE); + if (ret) + return ret; + + ret = iio_backend_num_lanes_set(st->back, num_lanes); + if (ret) + return ret; + + return ad7779_spi_write_mask(st, AD7779_REG_DOUT_FORMAT, + AD7779_DOUT_FORMAT_MSK, + FIELD_PREP(AD7779_DOUT_FORMAT_MSK, 2 - ilog2(num_lanes))); +} + +static int ad7779_setup_channels(struct iio_dev *indio_dev, const struct ad7779_state *st) +{ + struct iio_chan_spec *channels; + struct device *dev = &st->spi->dev; + + channels = devm_kmemdup_array(dev, st->chip_info->channels, + ARRAY_SIZE(ad7779_channels), + sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + for (unsigned int i = 0; i < ARRAY_SIZE(ad7779_channels); i++) + channels[i].scan_type.endianness = IIO_CPU; + + indio_dev->channels = channels; + indio_dev->num_channels = ARRAY_SIZE(ad7779_channels); + + return 0; +} + +static int ad7779_setup_without_backend(struct ad7779_state *st, struct iio_dev *indio_dev) +{ + int ret; + struct device *dev = &st->spi->dev; + + indio_dev->info = &ad7779_info; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = ARRAY_SIZE(ad7779_channels); + + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ad7779_trigger_ops; + + iio_trigger_set_drvdata(st->trig, st); + + ret = devm_request_irq(dev, st->spi->irq, iio_trigger_generic_data_rdy_poll, + IRQF_ONESHOT | IRQF_NO_AUTOEN, indio_dev->name, + st->trig); + if (ret) + return dev_err_probe(dev, ret, "request IRQ %d failed\n", + st->spi->irq); + + ret = devm_iio_trigger_register(dev, st->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trig); + + init_completion(&st->completion); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &ad7779_trigger_handler, + &ad7779_buffer_setup_ops); + if (ret) + return ret; + + return ad7779_spi_write_mask(st, AD7779_REG_DOUT_FORMAT, + AD7779_DCLK_CLK_DIV_MSK, + FIELD_PREP(AD7779_DCLK_CLK_DIV_MSK, 7)); +} + +static int ad7779_setup_backend(struct ad7779_state *st, struct iio_dev *indio_dev) +{ + struct device *dev = &st->spi->dev; + int ret; + u32 num_lanes; + + indio_dev->info = &ad7779_info_data; + + ret = ad7779_setup_channels(indio_dev, st); + if (ret) + return ret; + + st->back = devm_iio_backend_get(dev, NULL); + if (IS_ERR(st->back)) + return dev_err_probe(dev, PTR_ERR(st->back), + "failed to get iio backend"); + + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + + num_lanes = 4; + ret = device_property_read_u32(dev, "adi,num-lanes", &num_lanes); + if (ret && ret != -EINVAL) + return ret; + + return ad7779_set_data_lines(indio_dev, num_lanes); +} + static int ad7779_probe(struct spi_device *spi) { struct iio_dev *indio_dev; @@ -760,9 +907,6 @@ static int ad7779_probe(struct spi_device *spi) struct device *dev = &spi->dev; int ret = -EINVAL; - if (!spi->irq) - return dev_err_probe(dev, ret, "DRDY irq not present\n"); - indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); if (!indio_dev) return -ENOMEM; @@ -804,45 +948,12 @@ static int ad7779_probe(struct spi_device *spi) return ret; indio_dev->name = st->chip_info->name; - indio_dev->info = &ad7779_info; indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = st->chip_info->channels; - indio_dev->num_channels = ARRAY_SIZE(ad7779_channels); - st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, - iio_device_id(indio_dev)); - if (!st->trig) - return -ENOMEM; - - st->trig->ops = &ad7779_trigger_ops; - - iio_trigger_set_drvdata(st->trig, st); - - ret = devm_request_irq(dev, spi->irq, iio_trigger_generic_data_rdy_poll, - IRQF_ONESHOT | IRQF_NO_AUTOEN, indio_dev->name, - st->trig); - if (ret) - return dev_err_probe(dev, ret, "request IRQ %d failed\n", - st->spi->irq); - - ret = devm_iio_trigger_register(dev, st->trig); - if (ret) - return ret; - - indio_dev->trig = iio_trigger_get(st->trig); - - init_completion(&st->completion); - - ret = devm_iio_triggered_buffer_setup(dev, indio_dev, - &iio_pollfunc_store_time, - &ad7779_trigger_handler, - &ad7779_buffer_setup_ops); - if (ret) - return ret; - - ret = ad7779_spi_write_mask(st, AD7779_REG_DOUT_FORMAT, - AD7779_DCLK_CLK_DIV_MSK, - FIELD_PREP(AD7779_DCLK_CLK_DIV_MSK, 7)); + if (device_property_present(dev, "io-backends")) + ret = ad7779_setup_backend(st, indio_dev); + else + ret = ad7779_setup_without_backend(st, indio_dev); if (ret) return ret; @@ -936,3 +1047,4 @@ module_spi_driver(ad7779_driver); MODULE_AUTHOR("Ramona Alexandra Nechita <ramona.nechita@analog.com>"); MODULE_DESCRIPTION("Analog Devices AD7779 ADC"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/adc/ad7949.c b/drivers/iio/adc/ad7949.c index 202561cad401..b35d299a3977 100644 --- a/drivers/iio/adc/ad7949.c +++ b/drivers/iio/adc/ad7949.c @@ -316,10 +316,8 @@ static int ad7949_spi_probe(struct spi_device *spi) int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*ad7949_adc)); - if (!indio_dev) { - dev_err(dev, "can not allocate iio device\n"); + if (!indio_dev) return -ENOMEM; - } indio_dev->info = &ad7949_spi_info; indio_dev->name = spi_get_device_id(spi)->name; diff --git a/drivers/iio/adc/ad799x.c b/drivers/iio/adc/ad799x.c index 9c02f9199139..108bb22162ef 100644 --- a/drivers/iio/adc/ad799x.c +++ b/drivers/iio/adc/ad799x.c @@ -114,11 +114,13 @@ struct ad799x_chip_config { * @num_channels: number of channels * @noirq_config: device configuration w/o IRQ * @irq_config: device configuration w/IRQ + * @has_vref: device supports external reference voltage */ struct ad799x_chip_info { int num_channels; const struct ad799x_chip_config noirq_config; const struct ad799x_chip_config irq_config; + bool has_vref; }; struct ad799x_state { @@ -604,6 +606,7 @@ static const struct iio_event_spec ad799x_events[] = { static const struct ad799x_chip_info ad799x_chip_info_tbl[] = { [ad7991] = { .num_channels = 5, + .has_vref = true, .noirq_config = { .channel = { AD799X_CHANNEL(0, 12), @@ -617,6 +620,7 @@ static const struct ad799x_chip_info ad799x_chip_info_tbl[] = { }, [ad7995] = { .num_channels = 5, + .has_vref = true, .noirq_config = { .channel = { AD799X_CHANNEL(0, 10), @@ -630,6 +634,7 @@ static const struct ad799x_chip_info ad799x_chip_info_tbl[] = { }, [ad7999] = { .num_channels = 5, + .has_vref = true, .noirq_config = { .channel = { AD799X_CHANNEL(0, 8), @@ -687,6 +692,7 @@ static const struct ad799x_chip_info ad799x_chip_info_tbl[] = { }, [ad7994] = { .num_channels = 5, + .has_vref = true, .noirq_config = { .channel = { AD799X_CHANNEL(0, 12), @@ -809,32 +815,22 @@ static int ad799x_probe(struct i2c_client *client) return ret; /* check if an external reference is supplied */ - st->vref = devm_regulator_get_optional(&client->dev, "vref"); - - if (IS_ERR(st->vref)) { - if (PTR_ERR(st->vref) == -ENODEV) { + if (chip_info->has_vref) { + st->vref = devm_regulator_get_optional(&client->dev, "vref"); + ret = PTR_ERR_OR_ZERO(st->vref); + if (ret) { + if (ret != -ENODEV) + goto error_disable_reg; st->vref = NULL; dev_info(&client->dev, "Using VCC reference voltage\n"); - } else { - ret = PTR_ERR(st->vref); - goto error_disable_reg; } - } - if (st->vref) { - /* - * Use external reference voltage if supported by hardware. - * This is optional if voltage / regulator present, use VCC otherwise. - */ - if ((st->id == ad7991) || (st->id == ad7995) || (st->id == ad7999)) { + if (st->vref) { dev_info(&client->dev, "Using external reference voltage\n"); extra_config |= AD7991_REF_SEL; ret = regulator_enable(st->vref); if (ret) goto error_disable_reg; - } else { - st->vref = NULL; - dev_warn(&client->dev, "Supplied reference not supported\n"); } } diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c new file mode 100644 index 000000000000..94e05e11abd9 --- /dev/null +++ b/drivers/iio/adc/ade9000.c @@ -0,0 +1,1799 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** + * ADE9000 driver + * + * Copyright 2025 Analog Devices Inc. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/events.h> +#include <linux/interrupt.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <linux/unaligned.h> + +/* Address of ADE9000 registers */ +#define ADE9000_REG_AIGAIN 0x000 +#define ADE9000_REG_AVGAIN 0x00B +#define ADE9000_REG_AIRMSOS 0x00C +#define ADE9000_REG_AVRMSOS 0x00D +#define ADE9000_REG_APGAIN 0x00E +#define ADE9000_REG_AWATTOS 0x00F +#define ADE9000_REG_AVAROS 0x010 +#define ADE9000_REG_AFVAROS 0x012 +#define ADE9000_REG_CONFIG0 0x060 +#define ADE9000_REG_DICOEFF 0x072 +#define ADE9000_REG_AI_PCF 0x20A +#define ADE9000_REG_AV_PCF 0x20B +#define ADE9000_REG_AIRMS 0x20C +#define ADE9000_REG_AVRMS 0x20D +#define ADE9000_REG_AWATT 0x210 +#define ADE9000_REG_AVAR 0x211 +#define ADE9000_REG_AVA 0x212 +#define ADE9000_REG_AFVAR 0x214 +#define ADE9000_REG_APF 0x216 +#define ADE9000_REG_BI_PCF 0x22A +#define ADE9000_REG_BV_PCF 0x22B +#define ADE9000_REG_BIRMS 0x22C +#define ADE9000_REG_BVRMS 0x22D +#define ADE9000_REG_CI_PCF 0x24A +#define ADE9000_REG_CV_PCF 0x24B +#define ADE9000_REG_CIRMS 0x24C +#define ADE9000_REG_CVRMS 0x24D +#define ADE9000_REG_AWATT_ACC 0x2E5 +#define ADE9000_REG_AWATTHR_LO 0x2E6 +#define ADE9000_REG_AVAHR_LO 0x2FA +#define ADE9000_REG_AFVARHR_LO 0x30E +#define ADE9000_REG_BWATTHR_LO 0x322 +#define ADE9000_REG_BVAHR_LO 0x336 +#define ADE9000_REG_BFVARHR_LO 0x34A +#define ADE9000_REG_CWATTHR_LO 0x35E +#define ADE9000_REG_CVAHR_LO 0x372 +#define ADE9000_REG_CFVARHR_LO 0x386 +#define ADE9000_REG_STATUS0 0x402 +#define ADE9000_REG_STATUS1 0x403 +#define ADE9000_REG_MASK0 0x405 +#define ADE9000_REG_MASK1 0x406 +#define ADE9000_REG_EVENT_MASK 0x407 +#define ADE9000_REG_VLEVEL 0x40F +#define ADE9000_REG_DIP_LVL 0x410 +#define ADE9000_REG_DIPA 0x411 +#define ADE9000_REG_DIPB 0x412 +#define ADE9000_REG_DIPC 0x413 +#define ADE9000_REG_SWELL_LVL 0x414 +#define ADE9000_REG_SWELLA 0x415 +#define ADE9000_REG_SWELLB 0x416 +#define ADE9000_REG_SWELLC 0x417 +#define ADE9000_REG_APERIOD 0x418 +#define ADE9000_REG_BPERIOD 0x419 +#define ADE9000_REG_CPERIOD 0x41A +#define ADE9000_REG_RUN 0x480 +#define ADE9000_REG_CONFIG1 0x481 +#define ADE9000_REG_ACCMODE 0x492 +#define ADE9000_REG_CONFIG3 0x493 +#define ADE9000_REG_ZXTOUT 0x498 +#define ADE9000_REG_ZX_LP_SEL 0x49A +#define ADE9000_REG_WFB_CFG 0x4A0 +#define ADE9000_REG_WFB_PG_IRQEN 0x4A1 +#define ADE9000_REG_WFB_TRG_CFG 0x4A2 +#define ADE9000_REG_WFB_TRG_STAT 0x4A3 +#define ADE9000_REG_CONFIG2 0x4AF +#define ADE9000_REG_EP_CFG 0x4B0 +#define ADE9000_REG_EGY_TIME 0x4B2 +#define ADE9000_REG_PGA_GAIN 0x4B9 +#define ADE9000_REG_VERSION 0x4FE +#define ADE9000_REG_WF_BUFF 0x800 +#define ADE9000_REG_WF_HALF_BUFF 0xC00 + +#define ADE9000_REG_ADDR_MASK GENMASK(15, 4) +#define ADE9000_REG_READ_BIT_MASK BIT(3) + +#define ADE9000_WF_CAP_EN_MASK BIT(4) +#define ADE9000_WF_CAP_SEL_MASK BIT(5) +#define ADE9000_WF_MODE_MASK GENMASK(7, 6) +#define ADE9000_WF_SRC_MASK GENMASK(9, 8) +#define ADE9000_WF_IN_EN_MASK BIT(12) + +/* External reference selection bit in CONFIG1 */ +#define ADE9000_EXT_REF_MASK BIT(15) + +/* + * Configuration registers + */ +#define ADE9000_PGA_GAIN 0x0000 + +/* Default configuration */ + +#define ADE9000_CONFIG0 0x00000000 + +/* CF3/ZX pin outputs Zero crossing, CF4 = DREADY */ +#define ADE9000_CONFIG1 0x000E + +/* Default High pass corner frequency of 1.25Hz */ +#define ADE9000_CONFIG2 0x0A00 + +/* Peak and overcurrent detection disabled */ +#define ADE9000_CONFIG3 0x0000 + +/* + * 50Hz operation, 3P4W Wye configuration, signed accumulation + * 3P4W Wye = 3-Phase 4-Wire star configuration (3 phases + neutral wire) + * Clear bit 8 i.e. ACCMODE=0x00xx for 50Hz operation + * ACCMODE=0x0x9x for 3Wire delta when phase B is used as reference + * 3Wire delta = 3-Phase 3-Wire triangle configuration (3 phases, no neutral) + */ +#define ADE9000_ACCMODE 0x0000 +#define ADE9000_ACCMODE_60HZ 0x0100 + +/*Line period and zero crossing obtained from VA */ +#define ADE9000_ZX_LP_SEL 0x0000 + +/* Interrupt mask values for initialization */ +#define ADE9000_MASK0_ALL_INT_DIS 0 +#define ADE9000_MASK1_ALL_INT_DIS 0x00000000 + +/* Events disabled */ +#define ADE9000_EVENT_DISABLE 0x00000000 + +/* + * Assuming Vnom=1/2 of full scale. + * Refer to Technical reference manual for detailed calculations. + */ +#define ADE9000_VLEVEL 0x0022EA28 + +/* Set DICOEFF= 0xFFFFE000 when integrator is enabled */ +#define ADE9000_DICOEFF 0x00000000 + +/* DSP ON */ +#define ADE9000_RUN_ON 0xFFFFFFFF + +/* + * Energy Accumulation Settings + * Enable energy accumulation, accumulate samples at 8ksps + * latch energy accumulation after EGYRDY + * If accumulation is changed to half line cycle mode, change EGY_TIME + */ +#define ADE9000_EP_CFG 0x0011 + +/* Accumulate 4000 samples */ +#define ADE9000_EGY_TIME 7999 + +/* + * Constant Definitions + * ADE9000 FDSP: 8000sps, ADE9000 FDSP: 4000sps + */ +#define ADE9000_FDSP 4000 +#define ADE9000_DEFAULT_CLK_FREQ_HZ 24576000 +#define ADE9000_WFB_CFG 0x03E9 +#define ADE9000_WFB_PAGE_SIZE 128 +#define ADE9000_WFB_NR_OF_PAGES 16 +#define ADE9000_WFB_MAX_CHANNELS 8 +#define ADE9000_WFB_BYTES_IN_SAMPLE 4 +#define ADE9000_WFB_SAMPLES_IN_PAGE \ + (ADE9000_WFB_PAGE_SIZE / ADE9000_WFB_MAX_CHANNELS) +#define ADE9000_WFB_MAX_SAMPLES_CHAN \ + (ADE9000_WFB_SAMPLES_IN_PAGE * ADE9000_WFB_NR_OF_PAGES) +#define ADE9000_WFB_FULL_BUFF_NR_SAMPLES \ + (ADE9000_WFB_PAGE_SIZE * ADE9000_WFB_NR_OF_PAGES) +#define ADE9000_WFB_FULL_BUFF_SIZE \ + (ADE9000_WFB_FULL_BUFF_NR_SAMPLES * ADE9000_WFB_BYTES_IN_SAMPLE) + +#define ADE9000_SWRST_BIT BIT(0) + +/* Status and Mask register bits*/ +#define ADE9000_ST0_WFB_TRIG_BIT BIT(16) +#define ADE9000_ST0_PAGE_FULL_BIT BIT(17) +#define ADE9000_ST0_EGYRDY BIT(0) + +#define ADE9000_ST1_ZXTOVA_BIT BIT(6) +#define ADE9000_ST1_ZXTOVB_BIT BIT(7) +#define ADE9000_ST1_ZXTOVC_BIT BIT(8) +#define ADE9000_ST1_ZXVA_BIT BIT(9) +#define ADE9000_ST1_ZXVB_BIT BIT(10) +#define ADE9000_ST1_ZXVC_BIT BIT(11) +#define ADE9000_ST1_ZXIA_BIT BIT(13) +#define ADE9000_ST1_ZXIB_BIT BIT(14) +#define ADE9000_ST1_ZXIC_BIT BIT(15) +#define ADE9000_ST1_RSTDONE_BIT BIT(16) +#define ADE9000_ST1_SEQERR_BIT BIT(18) +#define ADE9000_ST1_SWELLA_BIT BIT(20) +#define ADE9000_ST1_SWELLB_BIT BIT(21) +#define ADE9000_ST1_SWELLC_BIT BIT(22) +#define ADE9000_ST1_DIPA_BIT BIT(23) +#define ADE9000_ST1_DIPB_BIT BIT(24) +#define ADE9000_ST1_DIPC_BIT BIT(25) +#define ADE9000_ST1_ERROR0_BIT BIT(28) +#define ADE9000_ST1_ERROR1_BIT BIT(29) +#define ADE9000_ST1_ERROR2_BIT BIT(30) +#define ADE9000_ST1_ERROR3_BIT BIT(31) +#define ADE9000_ST_ERROR \ + (ADE9000_ST1_ERROR0 | ADE9000_ST1_ERROR1 | \ + ADE9000_ST1_ERROR2 | ADE9000_ST1_ERROR3) +#define ADE9000_ST1_CROSSING_FIRST 6 +#define ADE9000_ST1_CROSSING_DEPTH 25 + +#define ADE9000_WFB_TRG_DIP_BIT BIT(0) +#define ADE9000_WFB_TRG_SWELL_BIT BIT(1) +#define ADE9000_WFB_TRG_ZXIA_BIT BIT(3) +#define ADE9000_WFB_TRG_ZXIB_BIT BIT(4) +#define ADE9000_WFB_TRG_ZXIC_BIT BIT(5) +#define ADE9000_WFB_TRG_ZXVA_BIT BIT(6) +#define ADE9000_WFB_TRG_ZXVB_BIT BIT(7) +#define ADE9000_WFB_TRG_ZXVC_BIT BIT(8) + +/* Stop when waveform buffer is full */ +#define ADE9000_WFB_FULL_MODE 0x0 +/* Continuous fill—stop only on enabled trigger events */ +#define ADE9000_WFB_EN_TRIG_MODE 0x1 +/* Continuous filling—center capture around enabled trigger events */ +#define ADE9000_WFB_C_EN_TRIG_MODE 0x2 +/* Continuous fill—used as streaming mode for continuous data output */ +#define ADE9000_WFB_STREAMING_MODE 0x3 + +#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 +#define ADE9000_PHASE_C_NR 2 + +#define ADE9000_SCAN_POS_IA BIT(0) +#define ADE9000_SCAN_POS_VA BIT(1) +#define ADE9000_SCAN_POS_IB BIT(2) +#define ADE9000_SCAN_POS_VB BIT(3) +#define ADE9000_SCAN_POS_IC BIT(4) +#define ADE9000_SCAN_POS_VC BIT(5) + +/* Waveform buffer configuration values */ +enum ade9000_wfb_cfg { + ADE9000_WFB_CFG_ALL_CHAN = 0x0, + ADE9000_WFB_CFG_IA_VA = 0x1, + ADE9000_WFB_CFG_IB_VB = 0x2, + ADE9000_WFB_CFG_IC_VC = 0x3, + ADE9000_WFB_CFG_IA = 0x8, + ADE9000_WFB_CFG_VA = 0x9, + ADE9000_WFB_CFG_IB = 0xA, + ADE9000_WFB_CFG_VB = 0xB, + ADE9000_WFB_CFG_IC = 0xC, + ADE9000_WFB_CFG_VC = 0xD, +}; + +#define ADE9000_PHASE_B_POS_BIT BIT(5) +#define ADE9000_PHASE_C_POS_BIT BIT(6) + +#define ADE9000_MAX_PHASE_NR 3 +#define AD9000_CHANNELS_PER_PHASE 10 + +/* + * Calculate register address for multi-phase device. + * Phase A (chan 0): base address + 0x00 + * Phase B (chan 1): base address + 0x20 + * Phase C (chan 2): base address + 0x40 + */ +#define ADE9000_ADDR_ADJUST(addr, chan) \ + (((chan) == 0 ? 0 : (chan) == 1 ? 2 : 4) << 4 | (addr)) + +struct ade9000_state { + struct completion reset_completion; + struct mutex lock; /* Protects SPI transactions */ + u8 wf_src; + u32 wfb_trg; + u8 wfb_nr_activ_chan; + u32 wfb_nr_samples; + struct spi_device *spi; + struct clk *clkin; + struct spi_transfer xfer[2]; + struct spi_message spi_msg; + struct regmap *regmap; + union{ + u8 byte[ADE9000_WFB_FULL_BUFF_SIZE]; + __be32 word[ADE9000_WFB_FULL_BUFF_NR_SAMPLES]; + } rx_buff __aligned(IIO_DMA_MINALIGN); + u8 tx_buff[2] __aligned(IIO_DMA_MINALIGN); + unsigned int bulk_read_buf[2]; +}; + +struct ade9000_irq1_event { + u32 bit_mask; + enum iio_chan_type chan_type; + u32 channel; + enum iio_event_type event_type; + enum iio_event_direction event_dir; +}; + +static const struct ade9000_irq1_event ade9000_irq1_events[] = { + { ADE9000_ST1_ZXVA_BIT, IIO_VOLTAGE, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXIA_BIT, IIO_CURRENT, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXVB_BIT, IIO_VOLTAGE, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXIB_BIT, IIO_CURRENT, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXVC_BIT, IIO_VOLTAGE, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXIC_BIT, IIO_CURRENT, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_SWELLA_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING }, + { ADE9000_ST1_SWELLB_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING }, + { ADE9000_ST1_SWELLC_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING }, + { ADE9000_ST1_DIPA_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING }, + { ADE9000_ST1_DIPB_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING }, + { ADE9000_ST1_DIPC_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING }, +}; + +/* Voltage events (zero crossing on instantaneous voltage) */ +static const struct iio_event_spec ade9000_voltage_events[] = { + { + /* Zero crossing detection - datasheet: ZXV interrupts */ + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +/* Current events (zero crossing on instantaneous current) */ +static const struct iio_event_spec ade9000_current_events[] = { + { + /* Zero crossing detection - datasheet: ZXI interrupts */ + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +/* RMS voltage events (swell/sag detection on RMS values) */ +static const struct iio_event_spec ade9000_rms_voltage_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, /* RMS swell detection */ + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, /* RMS sag/dip detection */ + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const char * const ade9000_filter_type_items[] = { + "sinc4", "sinc4+lp", +}; + +static const int ade9000_filter_type_values[] = { + 0, 2, +}; + +static int ade9000_filter_type_get(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 val; + int ret; + unsigned int i; + + ret = regmap_read(st->regmap, ADE9000_REG_WFB_CFG, &val); + if (ret) + return ret; + + val = FIELD_GET(ADE9000_WF_SRC_MASK, val); + + for (i = 0; i < ARRAY_SIZE(ade9000_filter_type_values); i++) { + if (ade9000_filter_type_values[i] == val) + return i; + } + + return -EINVAL; +} + +static int ade9000_filter_type_set(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int index) +{ + struct ade9000_state *st = iio_priv(indio_dev); + int ret, val; + + if (index >= ARRAY_SIZE(ade9000_filter_type_values)) + return -EINVAL; + + val = ade9000_filter_type_values[index]; + + /* Update the WFB_CFG register with the new filter type */ + ret = regmap_update_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_SRC_MASK, + FIELD_PREP(ADE9000_WF_SRC_MASK, val)); + if (ret) + return ret; + + /* Update cached value */ + st->wf_src = val; + + return 0; +} + +static const struct iio_enum ade9000_filter_type_enum = { + .items = ade9000_filter_type_items, + .num_items = ARRAY_SIZE(ade9000_filter_type_items), + .get = ade9000_filter_type_get, + .set = ade9000_filter_type_set, +}; + +static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { + IIO_ENUM("filter_type", IIO_SHARED_BY_ALL, &ade9000_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_ALL, &ade9000_filter_type_enum), + { } +}; + +#define ADE9000_CURRENT_CHANNEL(num) { \ + .type = IIO_CURRENT, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AI_PCF, num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + .event_spec = ade9000_current_events, \ + .num_event_specs = ARRAY_SIZE(ade9000_current_events), \ + .scan_index = num, \ + .indexed = 1, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADE9000_VOLTAGE_CHANNEL(num) { \ + .type = IIO_VOLTAGE, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AV_PCF, num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_FREQUENCY), \ + .event_spec = ade9000_voltage_events, \ + .num_event_specs = ARRAY_SIZE(ade9000_voltage_events), \ + .scan_index = num + 1, /* interleave with current channels */ \ + .indexed = 1, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = ade9000_ext_info, \ +} + +#define ADE9000_ALTCURRENT_RMS_CHANNEL(num) { \ + .type = IIO_ALTCURRENT, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AIRMS, 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_ALTVOLTAGE_RMS_CHANNEL(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), \ + .event_spec = ade9000_rms_voltage_events, \ + .num_event_specs = ARRAY_SIZE(ade9000_rms_voltage_events), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_ACTIVE_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AWATT, num), \ + .channel2 = IIO_MOD_ACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_REACTIVE_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVAR, num), \ + .channel2 = IIO_MOD_REACTIVE, \ + .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_APPARENT_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVA, num), \ + .channel2 = IIO_MOD_APPARENT, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = -1 \ +} + + #define ADE9000_ENERGY_ACTIVE_CHANNEL(num, addr) { \ + .type = IIO_ENERGY, \ + .channel = num, \ + .address = addr, \ + .channel2 = IIO_MOD_ACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +#define ADE9000_ENERGY_APPARENT_CHANNEL(num, addr) { \ + .type = IIO_ENERGY, \ + .channel = num, \ + .address = addr, \ + .channel2 = IIO_MOD_APPARENT, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +#define ADE9000_ENERGY_REACTIVE_CHANNEL(num, addr) { \ + .type = IIO_ENERGY, \ + .channel = num, \ + .address = addr, \ + .channel2 = IIO_MOD_REACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_FACTOR_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_APF, num), \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_POWERFACTOR), \ + .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), +}; + +static const struct reg_sequence ade9000_initialization_sequence[] = { + { ADE9000_REG_PGA_GAIN, ADE9000_PGA_GAIN }, + { ADE9000_REG_CONFIG0, ADE9000_CONFIG0 }, + { ADE9000_REG_CONFIG1, ADE9000_CONFIG1 }, + { ADE9000_REG_CONFIG2, ADE9000_CONFIG2 }, + { ADE9000_REG_CONFIG3, ADE9000_CONFIG3 }, + { ADE9000_REG_ACCMODE, ADE9000_ACCMODE }, + { ADE9000_REG_ZX_LP_SEL, ADE9000_ZX_LP_SEL }, + { ADE9000_REG_MASK0, ADE9000_MASK0_ALL_INT_DIS }, + { ADE9000_REG_MASK1, ADE9000_MASK1_ALL_INT_DIS }, + { ADE9000_REG_EVENT_MASK, ADE9000_EVENT_DISABLE }, + { ADE9000_REG_WFB_CFG, ADE9000_WFB_CFG }, + { ADE9000_REG_VLEVEL, ADE9000_VLEVEL }, + { ADE9000_REG_DICOEFF, ADE9000_DICOEFF }, + { ADE9000_REG_EGY_TIME, ADE9000_EGY_TIME }, + { ADE9000_REG_EP_CFG, ADE9000_EP_CFG }, + /* Clear all pending status bits by writing 1s */ + { ADE9000_REG_STATUS0, GENMASK(31, 0) }, + { ADE9000_REG_STATUS1, GENMASK(31, 0) }, + { ADE9000_REG_RUN, ADE9000_RUN_ON } +}; + +static int ade9000_spi_write_reg(void *context, unsigned int reg, + unsigned int val) +{ + struct ade9000_state *st = context; + u8 tx_buf[6]; + u16 addr; + int ret, len; + + guard(mutex)(&st->lock); + + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, reg); + put_unaligned_be16(addr, tx_buf); + + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) { + put_unaligned_be16(val, &tx_buf[2]); + len = 4; + } else { + put_unaligned_be32(val, &tx_buf[2]); + len = 6; + } + + ret = spi_write_then_read(st->spi, tx_buf, len, NULL, 0); + if (ret) + dev_err(&st->spi->dev, "problem when writing register 0x%x\n", reg); + + return ret; +} + +static int ade9000_spi_read_reg(void *context, unsigned int reg, + unsigned int *val) +{ + struct ade9000_state *st = context; + u8 tx_buf[2]; + u8 rx_buf[4]; + u16 addr; + int ret, rx_len; + + guard(mutex)(&st->lock); + + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, reg) | + ADE9000_REG_READ_BIT_MASK; + + put_unaligned_be16(addr, tx_buf); + + /* Skip CRC bytes - only read actual data */ + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) + rx_len = 2; + else + rx_len = 4; + + ret = spi_write_then_read(st->spi, tx_buf, 2, rx_buf, rx_len); + if (ret) { + dev_err(&st->spi->dev, "error reading register 0x%x\n", reg); + return ret; + } + + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) + *val = get_unaligned_be16(rx_buf); + else + *val = get_unaligned_be32(rx_buf); + + return 0; +} + +static bool ade9000_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + /* Interrupt/error status registers - volatile */ + case ADE9000_REG_STATUS0: + case ADE9000_REG_STATUS1: + return true; + default: + /* All other registers are non-volatile */ + return false; + } +} + +static void ade9000_configure_scan(struct iio_dev *indio_dev, u32 wfb_addr) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u16 addr; + + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, wfb_addr) | + ADE9000_REG_READ_BIT_MASK; + + put_unaligned_be16(addr, st->tx_buff); + + st->xfer[0].tx_buf = &st->tx_buff[0]; + st->xfer[0].len = 2; + + st->xfer[1].rx_buf = st->rx_buff.byte; + + /* Always use streaming mode */ + st->xfer[1].len = (st->wfb_nr_samples / 2) * 4; + + spi_message_init_with_transfers(&st->spi_msg, st->xfer, ARRAY_SIZE(st->xfer)); +} + +static int ade9000_iio_push_streaming(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 current_page, i; + int ret; + + guard(mutex)(&st->lock); + + ret = spi_sync(st->spi, &st->spi_msg); + if (ret) { + dev_err_ratelimited(dev, "SPI fail in trigger handler\n"); + return ret; + } + + /* In streaming mode, only half the buffer is filled per interrupt */ + for (i = 0; i < st->wfb_nr_samples / 2; i += st->wfb_nr_activ_chan) + iio_push_to_buffers(indio_dev, &st->rx_buff.word[i]); + + ret = regmap_read(st->regmap, ADE9000_REG_WFB_PG_IRQEN, ¤t_page); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 WFB read fail\n"); + return ret; + } + + if (current_page & ADE9000_MIDDLE_PAGE_BIT) { + ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN, + ADE9000_LAST_PAGE_BIT); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 WFB write fail\n"); + return ret; + } + + ade9000_configure_scan(indio_dev, + ADE9000_REG_WF_HALF_BUFF); + } else { + ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN, + ADE9000_MIDDLE_PAGE_BIT); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 WFB write fail"); + return IRQ_HANDLED; + } + + ade9000_configure_scan(indio_dev, ADE9000_REG_WF_BUFF); + } + + return 0; +} + +static int ade9000_iio_push_buffer(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + int ret; + u32 i; + + guard(mutex)(&st->lock); + + ret = spi_sync(st->spi, &st->spi_msg); + if (ret) { + dev_err_ratelimited(&st->spi->dev, + "SPI fail in trigger handler\n"); + return ret; + } + + for (i = 0; i < st->wfb_nr_samples; i += st->wfb_nr_activ_chan) + iio_push_to_buffers(indio_dev, &st->rx_buff.word[i]); + + return 0; +} + +static irqreturn_t ade9000_irq0_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ade9000_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 handled_irq = 0; + u32 interrupts, status; + int ret; + + ret = regmap_read(st->regmap, ADE9000_REG_STATUS0, &status); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 read status fail\n"); + return IRQ_HANDLED; + } + + ret = regmap_read(st->regmap, ADE9000_REG_MASK0, &interrupts); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 read mask fail\n"); + return IRQ_HANDLED; + } + + if ((status & ADE9000_ST0_PAGE_FULL_BIT) && + (interrupts & ADE9000_ST0_PAGE_FULL_BIT)) { + /* Always use streaming mode */ + ret = ade9000_iio_push_streaming(indio_dev); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 IIO push fail\n"); + return IRQ_HANDLED; + } + + handled_irq |= ADE9000_ST0_PAGE_FULL_BIT; + } + + if ((status & ADE9000_ST0_WFB_TRIG_BIT) && + (interrupts & ADE9000_ST0_WFB_TRIG_BIT)) { + ret = regmap_update_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_CAP_EN_MASK, 0); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 WFB fail\n"); + return IRQ_HANDLED; + } + + if (iio_buffer_enabled(indio_dev)) { + ret = ade9000_iio_push_buffer(indio_dev); + if (ret) { + dev_err_ratelimited(dev, + "IRQ0 IIO push fail @ WFB TRIG\n"); + return IRQ_HANDLED; + } + } + + handled_irq |= ADE9000_ST0_WFB_TRIG_BIT; + } + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS0, handled_irq); + if (ret) + dev_err_ratelimited(dev, "IRQ0 write status fail\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t ade9000_irq1_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ade9000_state *st = iio_priv(indio_dev); + unsigned int bit = ADE9000_ST1_CROSSING_FIRST; + s64 timestamp = iio_get_time_ns(indio_dev); + u32 handled_irq = 0; + u32 interrupts, result, status, tmp; + DECLARE_BITMAP(interrupt_bits, ADE9000_ST1_CROSSING_DEPTH); + const struct ade9000_irq1_event *event; + int ret, i; + + if (!completion_done(&st->reset_completion)) { + ret = regmap_read(st->regmap, ADE9000_REG_STATUS1, &result); + if (ret) { + dev_err_ratelimited(&st->spi->dev, "IRQ1 read status fail\n"); + return IRQ_HANDLED; + } + + if (result & ADE9000_ST1_RSTDONE_BIT) { + complete(&st->reset_completion); + /* Clear the reset done status bit */ + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, ADE9000_ST1_RSTDONE_BIT); + if (ret) + dev_err_ratelimited(&st->spi->dev, + "IRQ1 clear reset status fail\n"); + } else { + dev_err_ratelimited(&st->spi->dev, + "Error testing reset done\n"); + } + + return IRQ_HANDLED; + } + + ret = regmap_read(st->regmap, ADE9000_REG_STATUS1, &status); + if (ret) { + dev_err_ratelimited(&st->spi->dev, "IRQ1 read status fail\n"); + return IRQ_HANDLED; + } + + ret = regmap_read(st->regmap, ADE9000_REG_MASK1, &interrupts); + if (ret) { + dev_err_ratelimited(&st->spi->dev, "IRQ1 read mask fail\n"); + return IRQ_HANDLED; + } + + bitmap_from_arr32(interrupt_bits, &interrupts, ADE9000_ST1_CROSSING_DEPTH); + for_each_set_bit_from(bit, interrupt_bits, + ADE9000_ST1_CROSSING_DEPTH) { + tmp = status & BIT(bit); + if (!tmp) + continue; + + event = NULL; + + /* Find corresponding event in lookup table */ + for (i = 0; i < ARRAY_SIZE(ade9000_irq1_events); i++) { + if (ade9000_irq1_events[i].bit_mask == tmp) { + event = &ade9000_irq1_events[i]; + break; + } + } + + if (event) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(event->chan_type, + event->channel, + event->event_type, + event->event_dir), + timestamp); + } + handled_irq |= tmp; + } + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, handled_irq); + if (ret) + dev_err_ratelimited(&st->spi->dev, "IRQ1 write status fail\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t ade9000_dready_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + + /* Handle data ready interrupt from C4/EVENT/DREADY pin */ + if (!iio_device_claim_buffer_mode(indio_dev)) { + ade9000_iio_push_buffer(indio_dev); + iio_device_release_buffer_mode(indio_dev); + } + + return IRQ_HANDLED; +} + +static int ade9000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct ade9000_state *st = iio_priv(indio_dev); + unsigned int measured; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_FREQUENCY: + if (chan->type == IIO_VOLTAGE) { + int period_reg; + int period; + + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + period_reg = ADE9000_REG_APERIOD; + break; + case ADE9000_PHASE_B_NR: + period_reg = ADE9000_REG_BPERIOD; + break; + case ADE9000_PHASE_C_NR: + period_reg = ADE9000_REG_CPERIOD; + break; + default: + return -EINVAL; + } + ret = regmap_read(st->regmap, period_reg, &period); + if (ret) + return ret; + /* + * Frequency = (4MHz * 65536) / (PERIOD + 1) + * 4MHz = ADC sample rate, 65536 = 2^16 period register scaling + * See ADE9000 datasheet section on period measurement + */ + *val = 4000 * 65536; + *val2 = period + 1; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_ENERGY) { + u16 lo_reg = chan->address; + + ret = regmap_bulk_read(st->regmap, lo_reg, + st->bulk_read_buf, 2); + if (ret) + return ret; + + *val = st->bulk_read_buf[0]; /* Lower 32 bits */ + *val2 = st->bulk_read_buf[1]; /* Upper 32 bits */ + return IIO_VAL_INT_64; + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, chan->address, &measured); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + *val = measured; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_POWERFACTOR: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, chan->address, &measured); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + *val = measured; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_CURRENT: + case IIO_VOLTAGE: + case IIO_ALTVOLTAGE: + case IIO_ALTCURRENT: + switch (chan->address) { + case ADE9000_REG_AI_PCF: + case ADE9000_REG_AV_PCF: + case ADE9000_REG_BI_PCF: + case ADE9000_REG_BV_PCF: + case ADE9000_REG_CI_PCF: + case ADE9000_REG_CV_PCF: + *val = 1; + *val2 = ADE9000_PCF_FULL_SCALE_CODES; + return IIO_VAL_FRACTIONAL; + case ADE9000_REG_AIRMS: + case ADE9000_REG_AVRMS: + case ADE9000_REG_BIRMS: + case ADE9000_REG_BVRMS: + case ADE9000_REG_CIRMS: + case ADE9000_REG_CVRMS: + *val = 1; + *val2 = ADE9000_RMS_FULL_SCALE_CODES; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_POWER: + *val = 1; + *val2 = ADE9000_WATT_FULL_SCALE_CODES; + return IIO_VAL_FRACTIONAL; + default: + break; + } + + return -EINVAL; + default: + return -EINVAL; + } +} + +static int ade9000_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 tmp; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_CURRENT: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AIRMSOS, + chan->channel), val); + case IIO_VOLTAGE: + case IIO_ALTVOLTAGE: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AVRMSOS, + chan->channel), val); + case IIO_POWER: + tmp = chan->address; + tmp &= ~ADE9000_PHASE_B_POS_BIT; + tmp &= ~ADE9000_PHASE_C_POS_BIT; + + switch (tmp) { + case ADE9000_REG_AWATTOS: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AWATTOS, + chan->channel), val); + case ADE9000_REG_AVAR: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AVAROS, + chan->channel), val); + case ADE9000_REG_AFVAR: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AFVAROS, + chan->channel), val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBSCALE: + /* + * Calibration gain registers for fine-tuning measurements. + * These are separate from PGA gain and applied in the digital domain. + */ + switch (chan->type) { + case IIO_CURRENT: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AIGAIN, + chan->channel), val); + case IIO_VOLTAGE: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AVGAIN, + chan->channel), val); + case IIO_POWER: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_APGAIN, + chan->channel), val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + /* Per-channel scales are read-only */ + return -EINVAL; + default: + return -EINVAL; + } +} + +static int ade9000_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int tx_val, + unsigned int *rx_val) +{ + struct ade9000_state *st = iio_priv(indio_dev); + + if (rx_val) + return regmap_read(st->regmap, reg, rx_val); + + return regmap_write(st->regmap, reg, tx_val); +} + +static int ade9000_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 interrupts1; + int ret; + + /* All events use MASK1 register */ + ret = regmap_read(st->regmap, ADE9000_REG_MASK1, &interrupts1); + if (ret) + return ret; + + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXVA_BIT); + else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXIA_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) + return !!(interrupts1 & ADE9000_ST1_SWELLA_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) + return !!(interrupts1 & ADE9000_ST1_DIPA_BIT); + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase A\n", chan->type, dir); + return -EINVAL; + case ADE9000_PHASE_B_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXVB_BIT); + else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXIB_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) + return !!(interrupts1 & ADE9000_ST1_SWELLB_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) + return !!(interrupts1 & ADE9000_ST1_DIPB_BIT); + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase B\n", chan->type, dir); + return -EINVAL; + case ADE9000_PHASE_C_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXVC_BIT); + else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXIC_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) + return !!(interrupts1 & ADE9000_ST1_SWELLC_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) + return !!(interrupts1 & ADE9000_ST1_DIPC_BIT); + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase C\n", chan->type, dir); + return -EINVAL; + default: + return -EINVAL; + } +} + +static int ade9000_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 bit_mask; + int ret; + + /* Clear all pending events in STATUS1 register (write 1 to clear) */ + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, GENMASK(31, 0)); + if (ret) + return ret; + + /* Determine which interrupt bit to enable/disable */ + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXVA_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXVA_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXVA_BIT; + } else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXIA_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXIA_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXIA_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) { + bit_mask = ADE9000_ST1_SWELLA_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) { + bit_mask = ADE9000_ST1_DIPA_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT; + } else { + dev_err_ratelimited(&indio_dev->dev, "Invalid channel type %d or direction %d for phase A\n", + chan->type, dir); + return -EINVAL; + } + break; + case ADE9000_PHASE_B_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXVB_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXVB_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXVB_BIT; + } else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXIB_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXIB_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXIB_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) { + bit_mask = ADE9000_ST1_SWELLB_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) { + bit_mask = ADE9000_ST1_DIPB_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT; + } else { + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase B\n", + chan->type, dir); + return -EINVAL; + } + break; + case ADE9000_PHASE_C_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXVC_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXVC_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXVC_BIT; + } else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXIC_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXIC_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXIC_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) { + bit_mask = ADE9000_ST1_SWELLC_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) { + bit_mask = ADE9000_ST1_DIPC_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT; + } else { + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase C\n", + chan->type, dir); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + /* Set bits if enabling event, clear bits if disabling */ + return regmap_assign_bits(st->regmap, ADE9000_REG_MASK1, bit_mask, state ? bit_mask : 0); +} + +static int ade9000_write_event_value(struct iio_dev *indio_dev, + 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) +{ + struct ade9000_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_FALLING: + return regmap_write(st->regmap, ADE9000_REG_DIP_LVL, val); + case IIO_EV_DIR_RISING: + return regmap_write(st->regmap, ADE9000_REG_SWELL_LVL, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ade9000_read_event_value(struct iio_dev *indio_dev, + 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) +{ + struct ade9000_state *st = iio_priv(indio_dev); + unsigned int data; + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_FALLING: + ret = regmap_read(st->regmap, ADE9000_REG_DIP_LVL, &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_EV_DIR_RISING: + ret = regmap_read(st->regmap, ADE9000_REG_SWELL_LVL, &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ade9000_waveform_buffer_config(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 wfb_cfg_val; + u32 active_scans; + + bitmap_to_arr32(&active_scans, indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + + switch (active_scans) { + case ADE9000_SCAN_POS_IA | ADE9000_SCAN_POS_VA: + wfb_cfg_val = ADE9000_WFB_CFG_IA_VA; + st->wfb_nr_activ_chan = 2; + break; + case ADE9000_SCAN_POS_IB | ADE9000_SCAN_POS_VB: + wfb_cfg_val = ADE9000_WFB_CFG_IB_VB; + st->wfb_nr_activ_chan = 2; + break; + case ADE9000_SCAN_POS_IC | ADE9000_SCAN_POS_VC: + wfb_cfg_val = ADE9000_WFB_CFG_IC_VC; + st->wfb_nr_activ_chan = 2; + break; + case ADE9000_SCAN_POS_IA: + wfb_cfg_val = ADE9000_WFB_CFG_IA; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_VA: + wfb_cfg_val = ADE9000_WFB_CFG_VA; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_IB: + wfb_cfg_val = ADE9000_WFB_CFG_IB; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_VB: + wfb_cfg_val = ADE9000_WFB_CFG_VB; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_IC: + wfb_cfg_val = ADE9000_WFB_CFG_IC; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_VC: + wfb_cfg_val = ADE9000_WFB_CFG_VC; + st->wfb_nr_activ_chan = 1; + break; + case (ADE9000_SCAN_POS_IA | ADE9000_SCAN_POS_VA | ADE9000_SCAN_POS_IB | + ADE9000_SCAN_POS_VB | ADE9000_SCAN_POS_IC | ADE9000_SCAN_POS_VC): + wfb_cfg_val = ADE9000_WFB_CFG_ALL_CHAN; + st->wfb_nr_activ_chan = 6; + break; + default: + dev_err(&st->spi->dev, "Unsupported combination of scans\n"); + return -EINVAL; + } + + wfb_cfg_val |= FIELD_PREP(ADE9000_WF_SRC_MASK, st->wf_src); + + return regmap_write(st->regmap, ADE9000_REG_WFB_CFG, wfb_cfg_val); +} + +static int ade9000_waveform_buffer_interrupt_setup(struct ade9000_state *st) +{ + int ret; + + ret = regmap_write(st->regmap, ADE9000_REG_WFB_TRG_CFG, 0x0); + if (ret) + return ret; + + /* Always use streaming mode setup */ + ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN, + ADE9000_MIDDLE_PAGE_BIT); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS0, GENMASK(31, 0)); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, ADE9000_REG_MASK0, + ADE9000_ST0_PAGE_FULL_BIT); +} + +static int ade9000_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + int ret; + + ret = ade9000_waveform_buffer_config(indio_dev); + if (ret) + return ret; + + st->wfb_nr_samples = ADE9000_WFB_MAX_SAMPLES_CHAN * st->wfb_nr_activ_chan; + + ade9000_configure_scan(indio_dev, ADE9000_REG_WF_BUFF); + + ret = ade9000_waveform_buffer_interrupt_setup(st); + if (ret) + return ret; + + ret = regmap_set_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_CAP_EN_MASK); + if (ret) { + dev_err(&st->spi->dev, "Post-enable waveform buffer enable fail\n"); + return ret; + } + + return 0; +} + +static int ade9000_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 interrupts; + int ret; + + ret = regmap_clear_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_CAP_EN_MASK); + if (ret) { + dev_err(dev, "Post-disable waveform buffer disable fail\n"); + return ret; + } + + ret = regmap_write(st->regmap, ADE9000_REG_WFB_TRG_CFG, 0x0); + if (ret) + return ret; + + interrupts = ADE9000_ST0_WFB_TRIG_BIT | ADE9000_ST0_PAGE_FULL_BIT; + + ret = regmap_clear_bits(st->regmap, ADE9000_REG_MASK0, interrupts); + if (ret) { + dev_err(dev, "Post-disable update maks0 fail\n"); + return ret; + } + + return regmap_write(st->regmap, ADE9000_REG_STATUS0, GENMASK(31, 0)); +} + +static const struct iio_buffer_setup_ops ade9000_buffer_ops = { + .preenable = &ade9000_buffer_preenable, + .postdisable = &ade9000_buffer_postdisable, +}; + +static int ade9000_reset(struct ade9000_state *st) +{ + struct device *dev = &st->spi->dev; + struct gpio_desc *gpio_reset; + int ret; + + gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpio_reset)) + return PTR_ERR(gpio_reset); + + /* Software reset via register if no GPIO available */ + if (!gpio_reset) { + ret = regmap_set_bits(st->regmap, ADE9000_REG_CONFIG1, + ADE9000_SWRST_BIT); + if (ret) + return ret; + fsleep(90); + return 0; + } + + /* Hardware reset via GPIO */ + fsleep(10); + gpiod_set_value_cansleep(gpio_reset, 0); + fsleep(50000); + + /* Only wait for completion if IRQ1 is available to signal reset done */ + if (fwnode_irq_get_byname(dev_fwnode(dev), "irq1") >= 0) { + if (!wait_for_completion_timeout(&st->reset_completion, + msecs_to_jiffies(1000))) { + dev_err(dev, "Reset timeout after 1s\n"); + return -ETIMEDOUT; + } + } + /* If no IRQ available, reset is already complete after the 50ms delay above */ + + return 0; +} + +static int ade9000_setup(struct ade9000_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + ret = regmap_multi_reg_write(st->regmap, ade9000_initialization_sequence, + ARRAY_SIZE(ade9000_initialization_sequence)); + if (ret) + return dev_err_probe(dev, ret, "Failed to write register sequence"); + + fsleep(2000); + + return 0; +} + +static const struct iio_info ade9000_info = { + .read_raw = ade9000_read_raw, + .write_raw = ade9000_write_raw, + .debugfs_reg_access = ade9000_reg_access, + .write_event_config = ade9000_write_event_config, + .read_event_config = ade9000_read_event_config, + .write_event_value = ade9000_write_event_value, + .read_event_value = ade9000_read_event_value, +}; + +static const struct regmap_config ade9000_regmap_config = { + .reg_bits = 16, + .val_bits = 32, + .max_register = 0x6bc, + .zero_flag_mask = true, + .cache_type = REGCACHE_RBTREE, + .reg_read = ade9000_spi_read_reg, + .reg_write = ade9000_spi_write_reg, + .volatile_reg = ade9000_is_volatile_reg, +}; + +static int ade9000_setup_clkout(struct device *dev, struct ade9000_state *st) +{ + struct clk_hw *clkout_hw; + int ret; + + if (!IS_ENABLED(CONFIG_COMMON_CLK)) + return 0; + + /* + * Only provide clock output when using external CMOS clock. + * When using crystal, CLKOUT is connected to crystal and shouldn't + * be used as clock provider for other devices. + */ + if (!device_property_present(dev, "#clock-cells") || !st->clkin) + return 0; + + /* CLKOUT passes through CLKIN with divider of 1 */ + clkout_hw = devm_clk_hw_register_divider(dev, "clkout", __clk_get_name(st->clkin), + CLK_SET_RATE_PARENT, NULL, 0, 1, 0, NULL); + if (IS_ERR(clkout_hw)) + return dev_err_probe(dev, PTR_ERR(clkout_hw), "Failed to register clkout"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, clkout_hw); + if (ret) + return dev_err_probe(dev, ret, "Failed to add clock provider"); + + return 0; +} + +static int ade9000_request_irq(struct device *dev, const char *name, + irq_handler_t handler, void *dev_id) +{ + int irq, ret; + + irq = fwnode_irq_get_byname(dev_fwnode(dev), name); + if (irq == -EINVAL) + return 0; /* interrupts are optional */ + if (irq < 0) + return dev_err_probe(dev, irq, "Failed to get %s irq", name); + + ret = devm_request_threaded_irq(dev, irq, NULL, handler, + IRQF_ONESHOT, KBUILD_MODNAME, dev_id); + if (ret) + return dev_err_probe(dev, ret, "Failed to request %s irq", name); + + return 0; +} + +static int ade9000_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ade9000_state *st; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + 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"); + + st->regmap = regmap; + st->spi = spi; + + init_completion(&st->reset_completion); + + ret = ade9000_request_irq(dev, "irq0", ade9000_irq0_thread, indio_dev); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "irq1", ade9000_irq1_thread, indio_dev); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "dready", ade9000_dready_thread, indio_dev); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + /* External CMOS clock input (optional - crystal can be used instead) */ + st->clkin = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(st->clkin)) + return dev_err_probe(dev, PTR_ERR(st->clkin), "Failed to get and enable clkin"); + + ret = ade9000_setup_clkout(dev, st); + if (ret) + return ret; + + indio_dev->name = "ade9000"; + indio_dev->info = &ade9000_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->setup_ops = &ade9000_buffer_ops; + + ret = devm_regulator_get_enable(&spi->dev, "vdd"); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to get and enable vdd regulator\n"); + + indio_dev->channels = ade9000_channels; + indio_dev->num_channels = ARRAY_SIZE(ade9000_channels); + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, + &ade9000_buffer_ops); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup IIO buffer"); + + ret = ade9000_reset(st); + if (ret) + return ret; + + /* Configure reference selection if vref regulator is available */ + ret = devm_regulator_get_enable_optional(dev, "vref"); + if (ret != -ENODEV && ret >= 0) { + ret = regmap_set_bits(st->regmap, ADE9000_REG_CONFIG1, + ADE9000_EXT_REF_MASK); + if (ret) + return ret; + } else if (ret < 0 && ret != -ENODEV) { + return dev_err_probe(dev, ret, + "Failed to get and enable vref regulator\n"); + } + + ret = ade9000_setup(st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +}; + +static const struct spi_device_id ade9000_id[] = { + { "ade9000", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ade9000_id); + +static const struct of_device_id ade9000_of_match[] = { + { .compatible = "adi,ade9000" }, + { } +}; +MODULE_DEVICE_TABLE(of, ade9000_of_match); + +static struct spi_driver ade9000_driver = { + .driver = { + .name = "ade9000", + .of_match_table = ade9000_of_match, + }, + .probe = ade9000_probe, + .id_table = ade9000_id, +}; +module_spi_driver(ade9000_driver); + +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>"); +MODULE_DESCRIPTION("Analog Devices ADE9000"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index eb42e29960e4..14fa4238c2b9 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -618,6 +618,7 @@ static const struct iio_backend_ops adi_axi_adc_ops = { .chan_status = axi_adc_chan_status, .interface_type_get = axi_adc_interface_type_get, .oversampling_ratio_set = axi_adc_oversampling_ratio_set, + .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), }; diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c index c3450246730e..b4c36e6a7490 100644 --- a/drivers/iio/adc/at91-sama5d2_adc.c +++ b/drivers/iio/adc/at91-sama5d2_adc.c @@ -896,7 +896,6 @@ static int at91_adc_config_emr(struct at91_adc_state *st, emr |= osr | AT91_SAMA5D2_TRACKX(trackx); at91_adc_writel(st, EMR, emr); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); st->oversampling_ratio = oversampling_ratio; @@ -971,7 +970,6 @@ static int at91_adc_configure_touch(struct at91_adc_state *st, bool state) AT91_SAMA5D2_IER_PEN | AT91_SAMA5D2_IER_NOPEN); at91_adc_writel(st, TSMR, 0); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return 0; } @@ -1142,10 +1140,8 @@ static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state) at91_adc_configure_trigger_registers(st, state); - if (!state) { - pm_runtime_mark_last_busy(st->dev); + if (!state) pm_runtime_put_autosuspend(st->dev); - } return 0; } @@ -1336,7 +1332,6 @@ static int at91_adc_buffer_prepare(struct iio_dev *indio_dev) at91_adc_writel(st, IER, AT91_SAMA5D2_IER_DRDY); pm_runtime_put: - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return ret; } @@ -1394,7 +1389,6 @@ static int at91_adc_buffer_postdisable(struct iio_dev *indio_dev) if (st->dma_st.dma_chan) dmaengine_terminate_sync(st->dma_st.dma_chan); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return 0; @@ -1603,7 +1597,6 @@ static void at91_adc_setup_samp_freq(struct iio_dev *indio_dev, unsigned freq, mr |= AT91_SAMA5D2_MR_TRACKTIM(tracktim); at91_adc_writel(st, MR, mr); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); dev_dbg(&indio_dev->dev, "freq: %u, startup: %u, prescal: %u, tracktim=%u\n", @@ -1809,7 +1802,6 @@ static int at91_adc_read_info_raw(struct iio_dev *indio_dev, at91_adc_readl(st, LCDR); pm_runtime_put: - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return ret; } @@ -1890,7 +1882,6 @@ static int at91_adc_read_temp(struct iio_dev *indio_dev, restore_config: /* Revert previous settings. */ at91_adc_temp_sensor_configure(st, false); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); if (ret < 0) return ret; @@ -2465,7 +2456,6 @@ static int at91_adc_probe(struct platform_device *pdev) dev_info(&pdev->dev, "version: %x\n", readl_relaxed(st->base + st->soc_info.platform->layout->VERSION)); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return 0; @@ -2567,7 +2557,6 @@ static int at91_adc_resume(struct device *dev) at91_adc_configure_trigger_registers(st, true); } - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return 0; diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c index f258668b0dc7..6426c9e6ccc9 100644 --- a/drivers/iio/adc/bcm_iproc_adc.c +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -511,10 +511,8 @@ static int iproc_adc_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc_priv)); - if (!indio_dev) { - dev_err(&pdev->dev, "failed to allocate iio device\n"); + if (!indio_dev) return -ENOMEM; - } adc_priv = iio_priv(indio_dev); platform_set_drvdata(pdev, indio_dev); diff --git a/drivers/iio/adc/cpcap-adc.c b/drivers/iio/adc/cpcap-adc.c index ba7cbd3b4822..d9ee2ea116a7 100644 --- a/drivers/iio/adc/cpcap-adc.c +++ b/drivers/iio/adc/cpcap-adc.c @@ -953,11 +953,9 @@ static int cpcap_adc_probe(struct platform_device *pdev) int error; indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*ddata)); - if (!indio_dev) { - dev_err(&pdev->dev, "failed to allocate iio device\n"); - + if (!indio_dev) return -ENOMEM; - } + ddata = iio_priv(indio_dev); ddata->ato = device_get_match_data(&pdev->dev); if (!ddata->ato) diff --git a/drivers/iio/adc/da9150-gpadc.c b/drivers/iio/adc/da9150-gpadc.c index b99291ce2a45..625e3a8e4d03 100644 --- a/drivers/iio/adc/da9150-gpadc.c +++ b/drivers/iio/adc/da9150-gpadc.c @@ -308,10 +308,9 @@ static int da9150_gpadc_probe(struct platform_device *pdev) int irq, ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*gpadc)); - if (!indio_dev) { - dev_err(&pdev->dev, "Failed to allocate IIO device\n"); + if (!indio_dev) return -ENOMEM; - } + gpadc = iio_priv(indio_dev); gpadc->da9150 = da9150; diff --git a/drivers/iio/adc/dln2-adc.c b/drivers/iio/adc/dln2-adc.c index 5aea7644780f..eb902a946efe 100644 --- a/drivers/iio/adc/dln2-adc.c +++ b/drivers/iio/adc/dln2-adc.c @@ -584,10 +584,8 @@ static int dln2_adc_probe(struct platform_device *pdev) int i, ret, chans; indio_dev = devm_iio_device_alloc(dev, sizeof(*dln2)); - if (!indio_dev) { - dev_err(dev, "failed allocating iio device\n"); + if (!indio_dev) return -ENOMEM; - } dln2 = iio_priv(indio_dev); dln2->pdev = pdev; @@ -628,10 +626,9 @@ static int dln2_adc_probe(struct platform_device *pdev) dln2->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); - if (!dln2->trig) { - dev_err(dev, "failed to allocate trigger\n"); + if (!dln2->trig) return -ENOMEM; - } + iio_trigger_set_drvdata(dln2->trig, dln2); ret = devm_iio_trigger_register(dev, dln2->trig); if (ret) { diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c index 4614cf848535..1484adff00df 100644 --- a/drivers/iio/adc/exynos_adc.c +++ b/drivers/iio/adc/exynos_adc.c @@ -19,11 +19,9 @@ #include <linux/clk.h> #include <linux/completion.h> #include <linux/of.h> -#include <linux/of_irq.h> #include <linux/regulator/consumer.h> #include <linux/of_platform.h> #include <linux/err.h> -#include <linux/input.h> #include <linux/iio/iio.h> #include <linux/iio/machine.h> @@ -31,21 +29,14 @@ #include <linux/mfd/syscon.h> #include <linux/regmap.h> -#include <linux/platform_data/touchscreen-s3c2410.h> - /* S3C/EXYNOS4412/5250 ADC_V1 registers definitions */ #define ADC_V1_CON(x) ((x) + 0x00) -#define ADC_V1_TSC(x) ((x) + 0x04) #define ADC_V1_DLY(x) ((x) + 0x08) #define ADC_V1_DATX(x) ((x) + 0x0C) #define ADC_V1_DATY(x) ((x) + 0x10) #define ADC_V1_UPDN(x) ((x) + 0x14) #define ADC_V1_INTCLR(x) ((x) + 0x18) #define ADC_V1_MUX(x) ((x) + 0x1c) -#define ADC_V1_CLRINTPNDNUP(x) ((x) + 0x20) - -/* S3C2410 ADC registers definitions */ -#define ADC_S3C2410_MUX(x) ((x) + 0x18) /* Future ADC_V2 registers definitions */ #define ADC_V2_CON1(x) ((x) + 0x00) @@ -61,13 +52,8 @@ #define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6) #define ADC_V1_CON_STANDBY (1u << 2) -/* Bit definitions for S3C2410 ADC */ +/* Bit definitions for S3C2410 / S3C6410 ADC */ #define ADC_S3C2410_CON_SELMUX(x) (((x) & 7) << 3) -#define ADC_S3C2410_DATX_MASK 0x3FF -#define ADC_S3C2416_CON_RES_SEL (1u << 3) - -/* touch screen always uses channel 0 */ -#define ADC_S3C2410_MUX_TS 0 /* ADCTSC Register Bits */ #define ADC_S3C2443_TSC_UD_SEN (1u << 8) @@ -75,8 +61,6 @@ #define ADC_S3C2410_TSC_YP_SEN (1u << 6) #define ADC_S3C2410_TSC_XM_SEN (1u << 5) #define ADC_S3C2410_TSC_XP_SEN (1u << 4) -#define ADC_S3C2410_TSC_PULL_UP_DISABLE (1u << 3) -#define ADC_S3C2410_TSC_AUTO_PST (1u << 2) #define ADC_S3C2410_TSC_XY_PST(x) (((x) & 0x3) << 0) #define ADC_TSC_WAIT4INT (ADC_S3C2410_TSC_YM_SEN | \ @@ -84,12 +68,6 @@ ADC_S3C2410_TSC_XP_SEN | \ ADC_S3C2410_TSC_XY_PST(3)) -#define ADC_TSC_AUTOPST (ADC_S3C2410_TSC_YM_SEN | \ - ADC_S3C2410_TSC_YP_SEN | \ - ADC_S3C2410_TSC_XP_SEN | \ - ADC_S3C2410_TSC_AUTO_PST | \ - ADC_S3C2410_TSC_XY_PST(0)) - /* Bit definitions for ADC_V2 */ #define ADC_V2_CON1_SOFT_RESET (1u << 2) @@ -121,14 +99,11 @@ struct exynos_adc { struct exynos_adc_data *data; struct device *dev; - struct input_dev *input; void __iomem *regs; struct regmap *pmu_map; struct clk *clk; struct clk *sclk; unsigned int irq; - unsigned int tsirq; - unsigned int delay; struct regulator *vdd; struct completion completion; @@ -136,12 +111,6 @@ struct exynos_adc { u32 value; unsigned int version; - bool ts_enabled; - - bool read_ts; - u32 ts_x; - u32 ts_y; - /* * Lock to protect from potential concurrent access to the * completion callback during a manual conversion. For this driver @@ -241,7 +210,7 @@ static void exynos_adc_v1_init_hw(struct exynos_adc *info) writel(con1, ADC_V1_CON(info->regs)); /* set touchscreen delay */ - writel(info->delay, ADC_V1_DLY(info->regs)); + writel(10000, ADC_V1_DLY(info->regs)); } static void exynos_adc_v1_exit_hw(struct exynos_adc *info) @@ -307,53 +276,6 @@ static const struct exynos_adc_data exynos_adc_s5pv210_data = { .start_conv = exynos_adc_v1_start_conv, }; -static void exynos_adc_s3c2416_start_conv(struct exynos_adc *info, - unsigned long addr) -{ - u32 con1; - - /* Enable 12 bit ADC resolution */ - con1 = readl(ADC_V1_CON(info->regs)); - con1 |= ADC_S3C2416_CON_RES_SEL; - writel(con1, ADC_V1_CON(info->regs)); - - /* Select channel for S3C2416 */ - writel(addr, ADC_S3C2410_MUX(info->regs)); - - con1 = readl(ADC_V1_CON(info->regs)); - writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs)); -} - -static struct exynos_adc_data const exynos_adc_s3c2416_data = { - .num_channels = MAX_ADC_V1_CHANNELS, - .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ - - .init_hw = exynos_adc_v1_init_hw, - .exit_hw = exynos_adc_v1_exit_hw, - .start_conv = exynos_adc_s3c2416_start_conv, -}; - -static void exynos_adc_s3c2443_start_conv(struct exynos_adc *info, - unsigned long addr) -{ - u32 con1; - - /* Select channel for S3C2433 */ - writel(addr, ADC_S3C2410_MUX(info->regs)); - - con1 = readl(ADC_V1_CON(info->regs)); - writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs)); -} - -static struct exynos_adc_data const exynos_adc_s3c2443_data = { - .num_channels = MAX_ADC_V1_CHANNELS, - .mask = ADC_S3C2410_DATX_MASK, /* 10 bit ADC resolution */ - - .init_hw = exynos_adc_v1_init_hw, - .exit_hw = exynos_adc_v1_exit_hw, - .start_conv = exynos_adc_s3c2443_start_conv, -}; - static void exynos_adc_s3c64xx_start_conv(struct exynos_adc *info, unsigned long addr) { @@ -365,15 +287,6 @@ static void exynos_adc_s3c64xx_start_conv(struct exynos_adc *info, writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs)); } -static struct exynos_adc_data const exynos_adc_s3c24xx_data = { - .num_channels = MAX_ADC_V1_CHANNELS, - .mask = ADC_S3C2410_DATX_MASK, /* 10 bit ADC resolution */ - - .init_hw = exynos_adc_v1_init_hw, - .exit_hw = exynos_adc_v1_exit_hw, - .start_conv = exynos_adc_s3c64xx_start_conv, -}; - static struct exynos_adc_data const exynos_adc_s3c64xx_data = { .num_channels = MAX_ADC_V1_CHANNELS, .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ @@ -486,18 +399,6 @@ static const struct exynos_adc_data exynos7_adc_data = { static const struct of_device_id exynos_adc_match[] = { { - .compatible = "samsung,s3c2410-adc", - .data = &exynos_adc_s3c24xx_data, - }, { - .compatible = "samsung,s3c2416-adc", - .data = &exynos_adc_s3c2416_data, - }, { - .compatible = "samsung,s3c2440-adc", - .data = &exynos_adc_s3c24xx_data, - }, { - .compatible = "samsung,s3c2443-adc", - .data = &exynos_adc_s3c2443_data, - }, { .compatible = "samsung,s3c6410-adc", .data = &exynos_adc_s3c64xx_data, }, { @@ -580,55 +481,13 @@ static int exynos_read_raw(struct iio_dev *indio_dev, return ret; } -static int exynos_read_s3c64xx_ts(struct iio_dev *indio_dev, int *x, int *y) -{ - struct exynos_adc *info = iio_priv(indio_dev); - unsigned long time_left; - int ret; - - mutex_lock(&info->lock); - info->read_ts = true; - - reinit_completion(&info->completion); - - writel(ADC_S3C2410_TSC_PULL_UP_DISABLE | ADC_TSC_AUTOPST, - ADC_V1_TSC(info->regs)); - - /* Select the ts channel to be used and Trigger conversion */ - info->data->start_conv(info, ADC_S3C2410_MUX_TS); - - time_left = wait_for_completion_timeout(&info->completion, - EXYNOS_ADC_TIMEOUT); - if (time_left == 0) { - dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n"); - if (info->data->init_hw) - info->data->init_hw(info); - ret = -ETIMEDOUT; - } else { - *x = info->ts_x; - *y = info->ts_y; - ret = 0; - } - - info->read_ts = false; - mutex_unlock(&info->lock); - - return ret; -} - static irqreturn_t exynos_adc_isr(int irq, void *dev_id) { struct exynos_adc *info = dev_id; u32 mask = info->data->mask; /* Read value */ - if (info->read_ts) { - info->ts_x = readl(ADC_V1_DATX(info->regs)); - info->ts_y = readl(ADC_V1_DATY(info->regs)); - writel(ADC_TSC_WAIT4INT | ADC_S3C2443_TSC_UD_SEN, ADC_V1_TSC(info->regs)); - } else { - info->value = readl(ADC_V1_DATX(info->regs)) & mask; - } + info->value = readl(ADC_V1_DATX(info->regs)) & mask; /* clear irq */ if (info->data->clear_irq) @@ -639,46 +498,6 @@ static irqreturn_t exynos_adc_isr(int irq, void *dev_id) return IRQ_HANDLED; } -/* - * Here we (ab)use a threaded interrupt handler to stay running - * for as long as the touchscreen remains pressed, we report - * a new event with the latest data and then sleep until the - * next timer tick. This mirrors the behavior of the old - * driver, with much less code. - */ -static irqreturn_t exynos_ts_isr(int irq, void *dev_id) -{ - struct exynos_adc *info = dev_id; - struct iio_dev *dev = dev_get_drvdata(info->dev); - u32 x, y; - bool pressed; - int ret; - - while (READ_ONCE(info->ts_enabled)) { - ret = exynos_read_s3c64xx_ts(dev, &x, &y); - if (ret == -ETIMEDOUT) - break; - - pressed = x & y & ADC_DATX_PRESSED; - if (!pressed) { - input_report_key(info->input, BTN_TOUCH, 0); - input_sync(info->input); - break; - } - - input_report_abs(info->input, ABS_X, x & ADC_DATX_MASK); - input_report_abs(info->input, ABS_Y, y & ADC_DATY_MASK); - input_report_key(info->input, BTN_TOUCH, 1); - input_sync(info->input); - - usleep_range(1000, 1100); - } - - writel(0, ADC_V1_CLRINTPNDNUP(info->regs)); - - return IRQ_HANDLED; -} - static int exynos_adc_reg_access(struct iio_dev *indio_dev, unsigned reg, unsigned writeval, unsigned *readval) @@ -730,78 +549,17 @@ static int exynos_adc_remove_devices(struct device *dev, void *c) return 0; } -static int exynos_adc_ts_open(struct input_dev *dev) -{ - struct exynos_adc *info = input_get_drvdata(dev); - - WRITE_ONCE(info->ts_enabled, true); - enable_irq(info->tsirq); - - return 0; -} - -static void exynos_adc_ts_close(struct input_dev *dev) -{ - struct exynos_adc *info = input_get_drvdata(dev); - - WRITE_ONCE(info->ts_enabled, false); - disable_irq(info->tsirq); -} - -static int exynos_adc_ts_init(struct exynos_adc *info) -{ - int ret; - - if (info->tsirq <= 0) - return -ENODEV; - - info->input = input_allocate_device(); - if (!info->input) - return -ENOMEM; - - info->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); - info->input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); - - input_set_abs_params(info->input, ABS_X, 0, 0x3FF, 0, 0); - input_set_abs_params(info->input, ABS_Y, 0, 0x3FF, 0, 0); - - info->input->name = "S3C24xx TouchScreen"; - info->input->id.bustype = BUS_HOST; - info->input->open = exynos_adc_ts_open; - info->input->close = exynos_adc_ts_close; - - input_set_drvdata(info->input, info); - - ret = input_register_device(info->input); - if (ret) { - input_free_device(info->input); - return ret; - } - - ret = request_threaded_irq(info->tsirq, NULL, exynos_ts_isr, - IRQF_ONESHOT | IRQF_NO_AUTOEN, - "touchscreen", info); - if (ret) - input_unregister_device(info->input); - - return ret; -} - static int exynos_adc_probe(struct platform_device *pdev) { struct exynos_adc *info = NULL; struct device_node *np = pdev->dev.of_node; - struct s3c2410_ts_mach_info *pdata = dev_get_platdata(&pdev->dev); struct iio_dev *indio_dev = NULL; - bool has_ts = false; int ret; int irq; indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct exynos_adc)); - if (!indio_dev) { - dev_err(&pdev->dev, "failed allocating iio device\n"); + if (!indio_dev) return -ENOMEM; - } info = iio_priv(indio_dev); @@ -826,27 +584,10 @@ static int exynos_adc_probe(struct platform_device *pdev) } } - /* leave out any TS related code if unreachable */ - if (IS_REACHABLE(CONFIG_INPUT)) { - has_ts = of_property_read_bool(pdev->dev.of_node, - "has-touchscreen") || pdata; - } - irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; info->irq = irq; - - if (has_ts) { - irq = platform_get_irq(pdev, 1); - if (irq == -EPROBE_DEFER) - return irq; - - info->tsirq = irq; - } else { - info->tsirq = -1; - } - info->dev = &pdev->dev; init_completion(&info->completion); @@ -910,16 +651,6 @@ static int exynos_adc_probe(struct platform_device *pdev) if (info->data->init_hw) info->data->init_hw(info); - if (pdata) - info->delay = pdata->delay; - else - info->delay = 10000; - - if (has_ts) - ret = exynos_adc_ts_init(info); - if (ret) - goto err_iio; - ret = of_platform_populate(np, exynos_adc_match, NULL, &indio_dev->dev); if (ret < 0) { dev_err(&pdev->dev, "failed adding child nodes\n"); @@ -931,11 +662,6 @@ static int exynos_adc_probe(struct platform_device *pdev) err_of_populate: device_for_each_child(&indio_dev->dev, NULL, exynos_adc_remove_devices); - if (has_ts) { - input_unregister_device(info->input); - free_irq(info->tsirq, info); - } -err_iio: iio_device_unregister(indio_dev); err_irq: free_irq(info->irq, info); @@ -955,10 +681,6 @@ static void exynos_adc_remove(struct platform_device *pdev) struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct exynos_adc *info = iio_priv(indio_dev); - if (IS_REACHABLE(CONFIG_INPUT) && info->input) { - free_irq(info->tsirq, info); - input_unregister_device(info->input); - } device_for_each_child(&indio_dev->dev, NULL, exynos_adc_remove_devices); iio_device_unregister(indio_dev); diff --git a/drivers/iio/adc/hx711.c b/drivers/iio/adc/hx711.c index 7235fa9e13d5..1db8b68a8f64 100644 --- a/drivers/iio/adc/hx711.c +++ b/drivers/iio/adc/hx711.c @@ -465,7 +465,7 @@ static int hx711_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(dev, sizeof(struct hx711_data)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, "failed to allocate IIO device\n"); + return -ENOMEM; hx711_data = iio_priv(indio_dev); hx711_data->dev = dev; diff --git a/drivers/iio/adc/imx7d_adc.c b/drivers/iio/adc/imx7d_adc.c index 09ce71f6e941..039c0387da23 100644 --- a/drivers/iio/adc/imx7d_adc.c +++ b/drivers/iio/adc/imx7d_adc.c @@ -482,10 +482,8 @@ static int imx7d_adc_probe(struct platform_device *pdev) int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); - if (!indio_dev) { - dev_err(&pdev->dev, "Failed allocating iio device\n"); + if (!indio_dev) return -ENOMEM; - } info = iio_priv(indio_dev); info->dev = dev; diff --git a/drivers/iio/adc/imx8qxp-adc.c b/drivers/iio/adc/imx8qxp-adc.c index be13a6ed7e00..6fc50394ad90 100644 --- a/drivers/iio/adc/imx8qxp-adc.c +++ b/drivers/iio/adc/imx8qxp-adc.c @@ -229,7 +229,6 @@ static int imx8qxp_adc_read_raw(struct iio_dev *indio_dev, ret = wait_for_completion_interruptible_timeout(&adc->completion, IMX8QXP_ADC_TIMEOUT); - pm_runtime_mark_last_busy(dev); pm_runtime_put_sync_autosuspend(dev); if (ret == 0) { @@ -295,7 +294,6 @@ static int imx8qxp_adc_reg_access(struct iio_dev *indio_dev, unsigned int reg, *readval = readl(adc->regs + reg); - pm_runtime_mark_last_busy(dev); pm_runtime_put_sync_autosuspend(dev); return 0; @@ -315,10 +313,8 @@ static int imx8qxp_adc_probe(struct platform_device *pdev) int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); - if (!indio_dev) { - dev_err(dev, "Failed allocating iio device\n"); + if (!indio_dev) return -ENOMEM; - } adc = iio_priv(indio_dev); adc->dev = dev; diff --git a/drivers/iio/adc/imx93_adc.c b/drivers/iio/adc/imx93_adc.c index 7feaafd2316f..787e80db5de3 100644 --- a/drivers/iio/adc/imx93_adc.c +++ b/drivers/iio/adc/imx93_adc.c @@ -32,12 +32,13 @@ #define IMX93_ADC_PCDR0 0x100 #define IMX93_ADC_PCDR1 0x104 #define IMX93_ADC_PCDR2 0x108 -#define IMX93_ADC_PCDR3 0x10c +#define IMX93_ADC_PCDR3 0x10C #define IMX93_ADC_PCDR4 0x110 #define IMX93_ADC_PCDR5 0x114 #define IMX93_ADC_PCDR6 0x118 -#define IMX93_ADC_PCDR7 0x11c +#define IMX93_ADC_PCDR7 0x11C #define IMX93_ADC_CALSTAT 0x39C +#define IMX93_ADC_CALCFG0 0x3A0 /* ADC bit shift */ #define IMX93_ADC_MCR_MODE_MASK BIT(29) @@ -58,6 +59,8 @@ #define IMX93_ADC_IMR_ECH_MASK BIT(0) #define IMX93_ADC_PCDR_CDATA_MASK GENMASK(11, 0) +#define IMX93_ADC_CALCFG0_LDFAIL_MASK BIT(4) + /* ADC status */ #define IMX93_ADC_MSR_ADCSTATUS_IDLE 0 #define IMX93_ADC_MSR_ADCSTATUS_POWER_DOWN 1 @@ -145,7 +148,7 @@ static void imx93_adc_config_ad_clk(struct imx93_adc *adc) static int imx93_adc_calibration(struct imx93_adc *adc) { - u32 mcr, msr; + u32 mcr, msr, calcfg; int ret; /* make sure ADC in power down mode */ @@ -158,6 +161,11 @@ static int imx93_adc_calibration(struct imx93_adc *adc) imx93_adc_power_up(adc); + /* Enable loading of calibrated values even in fail condition */ + calcfg = readl(adc->regs + IMX93_ADC_CALCFG0); + calcfg |= IMX93_ADC_CALCFG0_LDFAIL_MASK; + writel(calcfg, adc->regs + IMX93_ADC_CALCFG0); + /* * TODO: we use the default TSAMP/NRSMPL/AVGEN in MCR, * can add the setting of these bit if need in future. @@ -180,9 +188,13 @@ static int imx93_adc_calibration(struct imx93_adc *adc) /* check whether calbration is success or not */ msr = readl(adc->regs + IMX93_ADC_MSR); if (msr & IMX93_ADC_MSR_CALFAIL_MASK) { + /* + * Only give warning here, this means the noise of the + * reference voltage do not meet the requirement: + * ADC reference voltage Noise < 1.8V * 1/2^ENOB + * And the resault of ADC is not that accurate. + */ dev_warn(adc->dev, "ADC calibration failed!\n"); - imx93_adc_power_down(adc); - return -EAGAIN; } return 0; @@ -248,7 +260,6 @@ static int imx93_adc_read_raw(struct iio_dev *indio_dev, mutex_lock(&adc->lock); ret = imx93_adc_read_channel_conversion(adc, chan->channel, val); mutex_unlock(&adc->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_sync_autosuspend(dev); if (ret < 0) return ret; @@ -308,8 +319,7 @@ static int imx93_adc_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "Failed allocating iio device\n"); + return -ENOMEM; adc = iio_priv(indio_dev); adc->dev = dev; diff --git a/drivers/iio/adc/intel_dc_ti_adc.c b/drivers/iio/adc/intel_dc_ti_adc.c new file mode 100644 index 000000000000..0fe34f1c338e --- /dev/null +++ b/drivers/iio/adc/intel_dc_ti_adc.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel Dollar Cove TI PMIC GPADC Driver + * + * Copyright (C) 2014 Intel Corporation (Ramakrishna Pallala <ramakrishna.pallala@intel.com>) + * Copyright (C) 2024 - 2025 Hans de Goede <hansg@kernel.org> + */ + +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/wait.h> + +#include <linux/iio/driver.h> +#include <linux/iio/iio.h> +#include <linux/iio/machine.h> + +#define DC_TI_ADC_CNTL_REG 0x50 +#define DC_TI_ADC_START BIT(0) +#define DC_TI_ADC_CH_SEL GENMASK(2, 1) +#define DC_TI_ADC_EN BIT(5) +#define DC_TI_ADC_EN_EXT_BPTH_BIAS BIT(6) + +#define DC_TI_VBAT_ZSE_GE_REG 0x53 +#define DC_TI_VBAT_GE GENMASK(3, 0) +#define DC_TI_VBAT_ZSE GENMASK(7, 4) + +/* VBAT GE gain correction is in 0.0015 increments, ZSE is in 1.0 increments */ +#define DC_TI_VBAT_GE_STEP 15 +#define DC_TI_VBAT_GE_DIV 10000 + +#define DC_TI_ADC_DATA_REG_CH(x) (0x54 + 2 * (x)) + +enum dc_ti_adc_id { + DC_TI_ADC_VBAT, + DC_TI_ADC_PMICTEMP, + DC_TI_ADC_BATTEMP, + DC_TI_ADC_SYSTEMP0, +}; + +struct dc_ti_adc_info { + struct mutex lock; /* Protects against concurrent accesses to the ADC */ + wait_queue_head_t wait; + struct device *dev; + struct regmap *regmap; + int vbat_zse; + int vbat_ge; + bool conversion_done; +}; + +static const struct iio_chan_spec dc_ti_adc_channels[] = { + { + .indexed = 1, + .type = IIO_VOLTAGE, + .channel = DC_TI_ADC_VBAT, + .address = DC_TI_ADC_DATA_REG_CH(0), + .datasheet_name = "CH0", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = DC_TI_ADC_PMICTEMP, + .address = DC_TI_ADC_DATA_REG_CH(1), + .datasheet_name = "CH1", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = DC_TI_ADC_BATTEMP, + .address = DC_TI_ADC_DATA_REG_CH(2), + .datasheet_name = "CH2", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = DC_TI_ADC_SYSTEMP0, + .address = DC_TI_ADC_DATA_REG_CH(3), + .datasheet_name = "CH3", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static struct iio_map dc_ti_adc_default_maps[] = { + IIO_MAP("CH0", "chtdc_ti_battery", "VBAT"), + IIO_MAP("CH1", "chtdc_ti_battery", "PMICTEMP"), + IIO_MAP("CH2", "chtdc_ti_battery", "BATTEMP"), + IIO_MAP("CH3", "chtdc_ti_battery", "SYSTEMP0"), + { } +}; + +static irqreturn_t dc_ti_adc_isr(int irq, void *data) +{ + struct dc_ti_adc_info *info = data; + + info->conversion_done = true; + wake_up(&info->wait); + return IRQ_HANDLED; +} + +static int dc_ti_adc_scale(struct dc_ti_adc_info *info, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + if (chan->channel != DC_TI_ADC_VBAT) + return -EINVAL; + + /* Vbat ADC scale is 4.6875 mV / unit */ + *val = 4; + *val2 = 687500; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int dc_ti_adc_raw_to_processed(struct dc_ti_adc_info *info, + struct iio_chan_spec const *chan, + int raw, int *val, int *val2) +{ + if (chan->channel != DC_TI_ADC_VBAT) + return -EINVAL; + + /* Apply calibration */ + raw -= info->vbat_zse; + raw = raw * (DC_TI_VBAT_GE_DIV - info->vbat_ge * DC_TI_VBAT_GE_STEP) / + DC_TI_VBAT_GE_DIV; + /* Vbat ADC scale is 4.6875 mV / unit */ + raw *= 46875; + + /* raw is now in 10000 units / mV, convert to milli + milli/1e6 */ + *val = raw / 10000; + *val2 = (raw % 10000) * 100; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int dc_ti_adc_sample(struct dc_ti_adc_info *info, + struct iio_chan_spec const *chan, int *val) +{ + int ret, ch = chan->channel; + __be16 buf; + + info->conversion_done = false; + + /* + * As per TI (PMIC Vendor), the ADC enable and ADC start commands should + * not be sent together. Hence send the commands separately. + */ + ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, DC_TI_ADC_EN); + if (ret) + return ret; + + ret = regmap_update_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_CH_SEL, + FIELD_PREP(DC_TI_ADC_CH_SEL, ch)); + if (ret) + return ret; + + /* + * As per PMIC Vendor, a minimum of 50 ųs delay is required between ADC + * Enable and ADC START commands. This is also recommended by Intel + * Hardware team after the timing analysis of GPADC signals. Since the + * I2C Write transaction to set the channel number also imparts 25 ųs + * delay, we need to wait for another 25 ųs before issuing ADC START. + */ + fsleep(25); + + ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_START); + if (ret) + return ret; + + /* TI (PMIC Vendor) recommends 5 s timeout for conversion */ + ret = wait_event_timeout(info->wait, info->conversion_done, 5 * HZ); + if (ret == 0) { + ret = -ETIMEDOUT; + goto disable_adc; + } + + ret = regmap_bulk_read(info->regmap, chan->address, &buf, sizeof(buf)); + if (ret) + goto disable_adc; + + /* The ADC values are 10 bits wide */ + *val = be16_to_cpu(buf) & GENMASK(9, 0); + +disable_adc: + regmap_clear_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_START | DC_TI_ADC_EN); + return ret; +} + +static int dc_ti_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dc_ti_adc_info *info = iio_priv(indio_dev); + int ret; + + if (mask == IIO_CHAN_INFO_SCALE) + return dc_ti_adc_scale(info, chan, val, val2); + + guard(mutex)(&info->lock); + + /* + * If channel BPTHERM has been selected, first enable the BPTHERM BIAS + * which provides the VREF Voltage reference to convert BPTHERM Input + * voltage to temperature. + */ + if (chan->channel == DC_TI_ADC_BATTEMP) { + ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_EN_EXT_BPTH_BIAS); + if (ret) + return ret; + /* + * As per PMIC Vendor specifications, BPTHERM BIAS should be + * enabled 35 ms before ADC_EN command. + */ + msleep(35); + } + + ret = dc_ti_adc_sample(info, chan, val); + + if (chan->channel == DC_TI_ADC_BATTEMP) + regmap_clear_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_EN_EXT_BPTH_BIAS); + + if (ret) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + return dc_ti_adc_raw_to_processed(info, chan, *val, val, val2); + } + + return -EINVAL; +} + +static const struct iio_info dc_ti_adc_iio_info = { + .read_raw = dc_ti_adc_read_raw, +}; + +static int dc_ti_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent); + struct dc_ti_adc_info *info; + struct iio_dev *indio_dev; + unsigned int val; + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + + ret = devm_mutex_init(dev, &info->lock); + if (ret) + return ret; + + init_waitqueue_head(&info->wait); + + info->dev = dev; + info->regmap = pmic->regmap; + + indio_dev->name = "dc_ti_adc"; + indio_dev->channels = dc_ti_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(dc_ti_adc_channels); + indio_dev->info = &dc_ti_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = regmap_read(info->regmap, DC_TI_VBAT_ZSE_GE_REG, &val); + if (ret) + return ret; + + info->vbat_zse = sign_extend32(FIELD_GET(DC_TI_VBAT_ZSE, val), 3); + info->vbat_ge = sign_extend32(FIELD_GET(DC_TI_VBAT_GE, val), 3); + + dev_dbg(dev, "vbat-zse %d vbat-ge %d\n", info->vbat_zse, info->vbat_ge); + + ret = devm_iio_map_array_register(dev, indio_dev, dc_ti_adc_default_maps); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, irq, NULL, dc_ti_adc_isr, + IRQF_ONESHOT, indio_dev->name, info); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct platform_device_id dc_ti_adc_ids[] = { + { .name = "chtdc_ti_adc" }, + { } +}; +MODULE_DEVICE_TABLE(platform, dc_ti_adc_ids); + +static struct platform_driver dc_ti_adc_driver = { + .driver = { + .name = "dc_ti_adc", + }, + .probe = dc_ti_adc_probe, + .id_table = dc_ti_adc_ids, +}; +module_platform_driver(dc_ti_adc_driver); + +MODULE_AUTHOR("Ramakrishna Pallala (Intel)"); +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_DESCRIPTION("Intel Dollar Cove (TI) GPADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/mcp3564.c b/drivers/iio/adc/mcp3564.c index a68f1cd6883e..cd679ff10a97 100644 --- a/drivers/iio/adc/mcp3564.c +++ b/drivers/iio/adc/mcp3564.c @@ -1019,7 +1019,7 @@ static int mcp3564_parse_fw_children(struct iio_dev *indio_dev) channels = devm_kcalloc(dev, num_ch, sizeof(*channels), GFP_KERNEL); if (!channels) - return dev_err_probe(dev, -ENOMEM, "Can't allocate memory\n"); + return -ENOMEM; device_for_each_child_node_scoped(dev, child) { node_name = fwnode_get_name(child); diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c index 4ff88603e4fc..f7e7172ef4f6 100644 --- a/drivers/iio/adc/meson_saradc.c +++ b/drivers/iio/adc/meson_saradc.c @@ -1357,7 +1357,7 @@ static int meson_sar_adc_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, "failed allocating iio device\n"); + return -ENOMEM; priv = iio_priv(indio_dev); init_completion(&priv->done); diff --git a/drivers/iio/adc/mt6577_auxadc.c b/drivers/iio/adc/mt6577_auxadc.c index 3343b54e8e44..fe9e3ece3fda 100644 --- a/drivers/iio/adc/mt6577_auxadc.c +++ b/drivers/iio/adc/mt6577_auxadc.c @@ -297,8 +297,7 @@ static int mt6577_auxadc_probe(struct platform_device *pdev) ret = devm_add_action_or_reset(&pdev->dev, mt6577_power_off, adc_dev); if (ret) - return dev_err_probe(&pdev->dev, ret, - "Failed to add action to managed power off\n"); + return ret; ret = devm_iio_device_register(&pdev->dev, indio_dev); if (ret < 0) diff --git a/drivers/iio/adc/mxs-lradc-adc.c b/drivers/iio/adc/mxs-lradc-adc.c index 92baf3f5f560..dda5182a5076 100644 --- a/drivers/iio/adc/mxs-lradc-adc.c +++ b/drivers/iio/adc/mxs-lradc-adc.c @@ -697,10 +697,8 @@ static int mxs_lradc_adc_probe(struct platform_device *pdev) /* Allocate the IIO device. */ iio = devm_iio_device_alloc(dev, sizeof(*adc)); - if (!iio) { - dev_err(dev, "Failed to allocate IIO device\n"); + if (!iio) return -ENOMEM; - } adc = iio_priv(iio); adc->lradc = lradc; diff --git a/drivers/iio/adc/pac1921.c b/drivers/iio/adc/pac1921.c index 72aa4ca2e5a4..35433250b008 100644 --- a/drivers/iio/adc/pac1921.c +++ b/drivers/iio/adc/pac1921.c @@ -1279,8 +1279,7 @@ static int pac1921_probe(struct i2c_client *client) ret = devm_add_action_or_reset(dev, pac1921_regulator_disable, priv->vdd); if (ret) - return dev_err_probe(dev, ret, - "Cannot add action for vdd regulator disposal\n"); + return ret; msleep(PAC1921_POWERUP_TIME_MS); diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c index 09fe88eb3fb0..48df16509260 100644 --- a/drivers/iio/adc/pac1934.c +++ b/drivers/iio/adc/pac1934.c @@ -88,6 +88,7 @@ #define PAC1934_VPOWER_3_ADDR 0x19 #define PAC1934_VPOWER_4_ADDR 0x1A #define PAC1934_REFRESH_V_REG_ADDR 0x1F +#define PAC1934_SLOW_REG_ADDR 0x20 #define PAC1934_CTRL_STAT_REGS_ADDR 0x1C #define PAC1934_PID_REG_ADDR 0xFD #define PAC1934_MID_REG_ADDR 0xFE @@ -1265,8 +1266,23 @@ static int pac1934_chip_configure(struct pac1934_chip_info *info) /* no SLOW triggered REFRESH, clear POR */ regs[PAC1934_SLOW_REG_OFF] = 0; - ret = i2c_smbus_write_block_data(client, PAC1934_CTRL_STAT_REGS_ADDR, - ARRAY_SIZE(regs), (u8 *)regs); + /* + * Write the three bytes sequentially, as the device does not support + * block write. + */ + ret = i2c_smbus_write_byte_data(client, PAC1934_CTRL_STAT_REGS_ADDR, + regs[PAC1934_CHANNEL_DIS_REG_OFF]); + if (ret) + return ret; + + ret = i2c_smbus_write_byte_data(client, + PAC1934_CTRL_STAT_REGS_ADDR + PAC1934_NEG_PWR_REG_OFF, + regs[PAC1934_NEG_PWR_REG_OFF]); + if (ret) + return ret; + + ret = i2c_smbus_write_byte_data(client, PAC1934_SLOW_REG_ADDR, + regs[PAC1934_SLOW_REG_OFF]); if (ret) return ret; @@ -1455,13 +1471,6 @@ static int pac1934_prep_custom_attributes(struct pac1934_chip_info *info, return 0; } -static void pac1934_mutex_destroy(void *data) -{ - struct mutex *lock = data; - - mutex_destroy(lock); -} - static const struct iio_info pac1934_info = { .read_raw = pac1934_read_raw, .write_raw = pac1934_write_raw, @@ -1520,9 +1529,7 @@ static int pac1934_probe(struct i2c_client *client) return dev_err_probe(dev, ret, "parameter parsing returned an error\n"); - mutex_init(&info->lock); - ret = devm_add_action_or_reset(dev, pac1934_mutex_destroy, - &info->lock); + ret = devm_mutex_init(dev, &info->lock); if (ret < 0) return ret; diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c index 7c01e33be04c..3f433064618e 100644 --- a/drivers/iio/adc/palmas_gpadc.c +++ b/drivers/iio/adc/palmas_gpadc.c @@ -885,10 +885,8 @@ static int palmas_gpadc_probe(struct platform_device *pdev) return -EINVAL; indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); - if (!indio_dev) { - dev_err(&pdev->dev, "iio_device_alloc failed\n"); + if (!indio_dev) return -ENOMEM; - } adc = iio_priv(indio_dev); adc->dev = &pdev->dev; diff --git a/drivers/iio/adc/rcar-gyroadc.c b/drivers/iio/adc/rcar-gyroadc.c index cc326f21d398..3a17b3898bf6 100644 --- a/drivers/iio/adc/rcar-gyroadc.c +++ b/drivers/iio/adc/rcar-gyroadc.c @@ -163,12 +163,10 @@ static int rcar_gyroadc_set_power(struct rcar_gyroadc *priv, bool on) { struct device *dev = priv->dev; - if (on) { + if (on) return pm_runtime_resume_and_get(dev); - } else { - pm_runtime_mark_last_busy(dev); - return pm_runtime_put_autosuspend(dev); - } + + return pm_runtime_put_autosuspend(dev); } static int rcar_gyroadc_read_raw(struct iio_dev *indio_dev, diff --git a/drivers/iio/adc/rn5t618-adc.c b/drivers/iio/adc/rn5t618-adc.c index d6f6b351f2af..f78fc795b69a 100644 --- a/drivers/iio/adc/rn5t618-adc.c +++ b/drivers/iio/adc/rn5t618-adc.c @@ -199,10 +199,8 @@ static int rn5t618_adc_probe(struct platform_device *pdev) struct rn5t618 *rn5t618 = dev_get_drvdata(pdev->dev.parent); iio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); - if (!iio_dev) { - dev_err(&pdev->dev, "failed allocating iio device\n"); + if (!iio_dev) return -ENOMEM; - } adc = iio_priv(iio_dev); adc->dev = &pdev->dev; diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c index bd62daea0a3e..6721da0ed7bb 100644 --- a/drivers/iio/adc/rockchip_saradc.c +++ b/drivers/iio/adc/rockchip_saradc.c @@ -466,8 +466,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); if (!indio_dev) - return dev_err_probe(&pdev->dev, -ENOMEM, - "failed allocating iio device\n"); + return -ENOMEM; info = iio_priv(indio_dev); @@ -527,8 +526,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev) ret = devm_add_action_or_reset(&pdev->dev, rockchip_saradc_regulator_disable, info); if (ret) - return dev_err_probe(&pdev->dev, ret, - "failed to register devm action\n"); + return ret; ret = regulator_get_voltage(info->vref); if (ret < 0) diff --git a/drivers/iio/adc/rohm-bd79112.c b/drivers/iio/adc/rohm-bd79112.c new file mode 100644 index 000000000000..d15e06c8b94d --- /dev/null +++ b/drivers/iio/adc/rohm-bd79112.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ROHM ADC driver for BD79112 signal monitoring hub. + * Copyright (C) 2025, ROHM Semiconductor. + * + * SPI communication derived from ad7923.c and ti-ads7950.c + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/bits.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/gpio/driver.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <linux/types.h> +#include <asm/byteorder.h> + +#include <linux/iio/adc-helpers.h> +#include <linux/iio/iio.h> + +#define BD79112_MAX_NUM_CHANNELS 32 + +struct bd79112_data { + struct spi_device *spi; + struct regmap *map; + struct device *dev; + struct gpio_chip gc; + unsigned long gpio_valid_mask; + unsigned int vref_mv; + struct spi_transfer read_xfer[2]; + struct spi_transfer write_xfer; + struct spi_message read_msg; + struct spi_message write_msg; + /* 16-bit TX, valid data in high byte */ + u8 read_tx[2] __aligned(IIO_DMA_MINALIGN); + /* 8-bit address followed by 8-bit data */ + u8 reg_write_tx[2]; + /* 12-bit of ADC data or 8 bit of reg data */ + __be16 read_rx; +}; + +/* + * The ADC data is read issuing SPI-command matching the channel number. + * We treat this as a register address. + */ +#define BD79112_REG_AGIO0A 0x00 +#define BD79112_REG_AGIO15B 0x1f + +/* + * ADC STATUS_FLAG appended to ADC data will be set, if the ADC result is being + * read for a channel, which input pin is muxed to be a GPIO. + */ +#define BD79112_ADC_STATUS_FLAG BIT(14) + +/* + * The BD79112 requires "R/W bit" to be set for SPI register (not ADC data) + * reads and an "IOSET bit" to be set for read/write operations (which aren't + * reading the ADC data). + */ +#define BD79112_BIT_RW BIT(4) +#define BD79112_BIT_IO BIT(5) + +#define BD79112_REG_GPI_VALUE_B8_15 (BD79112_BIT_IO | 0x0) +#define BD79112_REG_GPI_VALUE_B0_B7 (BD79112_BIT_IO | 0x1) +#define BD79112_REG_GPI_VALUE_A8_15 (BD79112_BIT_IO | 0x2) +#define BD79112_REG_GPI_VALUE_A0_A7 (BD79112_BIT_IO | 0x3) + +#define BD79112_REG_GPI_EN_B7_B15 (BD79112_BIT_IO | 0x4) +#define BD79112_REG_GPI_EN_B0_B7 (BD79112_BIT_IO | 0x5) +#define BD79112_REG_GPI_EN_A8_A15 (BD79112_BIT_IO | 0x6) +#define BD79112_REG_GPI_EN_A0_A7 (BD79112_BIT_IO | 0x7) + +#define BD79112_REG_GPO_EN_B7_B15 (BD79112_BIT_IO | 0x8) +#define BD79112_REG_GPO_EN_B0_B7 (BD79112_BIT_IO | 0x9) +#define BD79112_REG_GPO_EN_A8_A15 (BD79112_BIT_IO | 0xa) +#define BD79112_REG_GPO_EN_A0_A7 (BD79112_BIT_IO | 0xb) + +#define BD79112_NUM_GPIO_EN_REGS 8 +#define BD79112_FIRST_GPIO_EN_REG BD79112_REG_GPI_EN_B7_B15 + +#define BD79112_REG_GPO_VALUE_B8_15 (BD79112_BIT_IO | 0xc) +#define BD79112_REG_GPO_VALUE_B0_B7 (BD79112_BIT_IO | 0xd) +#define BD79112_REG_GPO_VALUE_A8_15 (BD79112_BIT_IO | 0xe) +#define BD79112_REG_GPO_VALUE_A0_A7 (BD79112_BIT_IO | 0xf) + +#define BD79112_REG_MAX BD79112_REG_GPO_VALUE_A0_A7 + +/* + * Read transaction consists of two 16-bit sequences separated by CSB. + * For register read, 'IOSET' bit must be set. For ADC read, IOSET is cleared + * and ADDR equals the channel number (0 ... 31). + * + * First 16-bit sequence, MOSI as below, MISO data ignored: + * - SCK: | 1 | 2 | 3 | 4 | 5 .. 8 | 9 .. 16 | + * - MOSI:| 0 | 0 | IOSET | RW (1) | ADDR | 8'b0 | + * + * CSB released and re-acquired between these sequences + * + * Second 16-bit sequence, MISO as below, MOSI data ignored: + * For Register read data is 8 bits: + * - SCK: | 1 .. 8 | 9 .. 16 | + * - MISO:| 8'b0 | 8-bit data | + * + * For ADC read data is 12 bits: + * - SCK: | 1 | 2 | 3 4 | 4 .. 16 | + * - MISO:| 0 | STATUS_FLAG | 2'b0 | 12-bit data | + * The 'STATUS_FLAG' is set if the read input pin was configured as a GPIO. + */ +static int bd79112_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct bd79112_data *data = context; + int ret; + + if (reg & BD79112_BIT_IO) + reg |= BD79112_BIT_RW; + + data->read_tx[0] = reg; + + ret = spi_sync(data->spi, &data->read_msg); + if (!ret) + *val = be16_to_cpu(data->read_rx); + + return ret; +} + +/* + * Write, single 16-bit sequence (broken down below): + * + * First 8-bit, MOSI as below, MISO data ignored: + * - SCK: | 1 | 2 | 3 | 4 | 5 .. 8 | + * - MOSI:| 0 | 0 |IOSET| RW(0) | ADDR | + * + * Last 8 SCK cycles (b8 ... b15), MISO contains register data, MOSI ignored. + * - SCK: | 9 .. 16 | + * - MISO:| data | + */ +static int bd79112_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct bd79112_data *data = context; + + data->reg_write_tx[0] = reg; + data->reg_write_tx[1] = val; + + return spi_sync(data->spi, &data->write_msg); +} + +static int _get_gpio_reg(unsigned int offset, unsigned int base) +{ + int regoffset = offset / 8; + + if (offset > 31) + return -EINVAL; + + return base - regoffset; +} + +#define GET_GPIO_BIT(offset) BIT((offset) % 8) +#define GET_GPO_EN_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPO_EN_A0_A7) +#define GET_GPI_EN_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPI_EN_A0_A7) +#define GET_GPO_VAL_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPO_VALUE_A0_A7) +#define GET_GPI_VAL_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPI_VALUE_A0_A7) + +static const struct regmap_range bd71815_volatile_ro_ranges[] = { + { + /* Read ADC data */ + .range_min = BD79112_REG_AGIO0A, + .range_max = BD79112_REG_AGIO15B, + }, { + /* GPI state */ + .range_min = BD79112_REG_GPI_VALUE_B8_15, + .range_max = BD79112_REG_GPI_VALUE_A0_A7, + }, +}; + +static const struct regmap_access_table bd79112_volatile_regs = { + .yes_ranges = &bd71815_volatile_ro_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bd71815_volatile_ro_ranges), +}; + +static const struct regmap_access_table bd79112_ro_regs = { + .no_ranges = &bd71815_volatile_ro_ranges[0], + .n_no_ranges = ARRAY_SIZE(bd71815_volatile_ro_ranges), +}; + +static const struct regmap_config bd79112_regmap = { + .reg_read = bd79112_reg_read, + .reg_write = bd79112_reg_write, + .volatile_table = &bd79112_volatile_regs, + .wr_table = &bd79112_ro_regs, + .cache_type = REGCACHE_MAPLE, + .max_register = BD79112_REG_MAX, +}; + +static int bd79112_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long m) +{ + struct bd79112_data *data = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(data->map, chan->channel, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = data->vref_mv; + *val2 = 12; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info bd79112_info = { + .read_raw = bd79112_read_raw, +}; + +static const struct iio_chan_spec bd79112_chan_template = { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, +}; + +static int bd79112_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + + *valid_mask = data->gpio_valid_mask; + + return 0; +} + +static int bd79112_gpio_dir_get(struct gpio_chip *gc, unsigned int offset) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + unsigned int reg, bit, val; + int ret; + + bit = GET_GPIO_BIT(offset); + reg = GET_GPO_EN_REG(offset); + + ret = regmap_read(data->map, reg, &val); + if (ret) + return ret; + + if (bit & val) + return GPIO_LINE_DIRECTION_OUT; + + reg = GET_GPI_EN_REG(offset); + ret = regmap_read(data->map, reg, &val); + if (ret) + return ret; + + if (bit & val) + return GPIO_LINE_DIRECTION_IN; + + /* + * Ouch. Seems the pin is ADC input - shouldn't happen as changing mux + * at runtime is not supported and non GPIO pins should be invalidated + * by the valid_mask at probe. Maybe someone wrote a register bypassing + * the driver? + */ + dev_err(data->dev, "Pin not a GPIO\n"); + + return -EINVAL; +} + +static int bd79112_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + unsigned int reg, bit, val; + int ret; + + bit = GET_GPIO_BIT(offset); + reg = GET_GPI_VAL_REG(offset); + + ret = regmap_read(data->map, reg, &val); + if (ret) + return ret; + + return !!(val & bit); +} + +static int bd79112_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + unsigned int reg, bit; + + bit = GET_GPIO_BIT(offset); + reg = GET_GPO_VAL_REG(offset); + + return regmap_assign_bits(data->map, reg, bit, value); +} + +static int bd79112_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + unsigned long i, bank_mask; + + for_each_set_clump8(i, bank_mask, mask, gc->ngpio) { + unsigned long bank_bits; + unsigned int reg; + int ret; + + bank_bits = bitmap_get_value8(bits, i); + reg = BD79112_REG_GPO_VALUE_A0_A7 - i / 8; + ret = regmap_update_bits(data->map, reg, bank_mask, bank_bits); + if (ret) + return ret; + } + + return 0; +} + +static int bd79112_gpio_dir_set(struct bd79112_data *data, unsigned int offset, + int dir) +{ + unsigned int gpi_reg, gpo_reg, bit; + int ret; + + bit = GET_GPIO_BIT(offset); + gpi_reg = GET_GPI_EN_REG(offset); + gpo_reg = GET_GPO_EN_REG(offset); + + if (dir == GPIO_LINE_DIRECTION_OUT) { + ret = regmap_clear_bits(data->map, gpi_reg, bit); + if (ret) + return ret; + + return regmap_set_bits(data->map, gpo_reg, bit); + } + + ret = regmap_set_bits(data->map, gpi_reg, bit); + if (ret) + return ret; + + return regmap_clear_bits(data->map, gpo_reg, bit); +} + +static int bd79112_gpio_input(struct gpio_chip *gc, unsigned int offset) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + + return bd79112_gpio_dir_set(data, offset, GPIO_LINE_DIRECTION_IN); +} + +static int bd79112_gpio_output(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + int ret; + + ret = bd79112_gpio_set(gc, offset, value); + if (ret) + return ret; + + return bd79112_gpio_dir_set(data, offset, GPIO_LINE_DIRECTION_OUT); +} + +static const struct gpio_chip bd79112_gpio_chip = { + .label = "bd79112-gpio", + .get_direction = bd79112_gpio_dir_get, + .direction_input = bd79112_gpio_input, + .direction_output = bd79112_gpio_output, + .get = bd79112_gpio_get, + .set = bd79112_gpio_set, + .set_multiple = bd79112_gpio_set_multiple, + .init_valid_mask = bd79112_gpio_init_valid_mask, + .can_sleep = true, + .ngpio = 32, + .base = -1, +}; + +static unsigned int bd79112_get_gpio_pins(const struct iio_chan_spec *cs, int num_channels) +{ + unsigned int i, gpio_channels; + + /* + * Let's initialize the mux config to say that all 32 channels are + * GPIOs. Then we can just loop through the iio_chan_spec and clear the + * bits for found ADC channels. + */ + gpio_channels = GENMASK(31, 0); + for (i = 0; i < num_channels; i++) + gpio_channels &= ~BIT(cs[i].channel); + + return gpio_channels; +} + +/* ADC channels as named in the data-sheet */ +static const char * const bd79112_chan_names[] = { + "AGIO0A", "AGIO1A", "AGIO2A", "AGIO3A", /* 0 - 3 */ + "AGIO4A", "AGIO5A", "AGIO6A", "AGIO7A", /* 4 - 7 */ + "AGIO8A", "AGIO9A", "AGIO10A", "AGIO11A", /* 8 - 11 */ + "AGIO12A", "AGIO13A", "AGIO14A", "AGIO15A", /* 12 - 15 */ + "AGIO0B", "AGIO1B", "AGIO2B", "AGIO3B", /* 16 - 19 */ + "AGIO4B", "AGIO5B", "AGIO6B", "AGIO7B", /* 20 - 23 */ + "AGIO8B", "AGIO9B", "AGIO10B", "AGIO11B", /* 24 - 27 */ + "AGIO12B", "AGIO13B", "AGIO14B", "AGIO15B", /* 28 - 31 */ +}; + +static int bd79112_probe(struct spi_device *spi) +{ + struct bd79112_data *data; + struct iio_dev *iio_dev; + struct iio_chan_spec *cs; + struct device *dev = &spi->dev; + unsigned long gpio_pins, pin; + unsigned int i; + int ret; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!iio_dev) + return -ENOMEM; + + data = iio_priv(iio_dev); + data->spi = spi; + data->dev = dev; + data->map = devm_regmap_init(dev, NULL, data, &bd79112_regmap); + if (IS_ERR(data->map)) + return dev_err_probe(dev, PTR_ERR(data->map), + "Failed to initialize Regmap\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "vdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get the Vdd\n"); + + data->vref_mv = ret / 1000; + + ret = devm_regulator_get_enable(dev, "iovdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to enable I/O voltage\n"); + + data->read_xfer[0].tx_buf = &data->read_tx[0]; + data->read_xfer[0].len = sizeof(data->read_tx); + data->read_xfer[0].cs_change = 1; + data->read_xfer[1].rx_buf = &data->read_rx; + data->read_xfer[1].len = sizeof(data->read_rx); + spi_message_init_with_transfers(&data->read_msg, data->read_xfer, 2); + ret = devm_spi_optimize_message(dev, spi, &data->read_msg); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to optimize SPI read message\n"); + + data->write_xfer.tx_buf = &data->reg_write_tx[0]; + data->write_xfer.len = sizeof(data->reg_write_tx); + spi_message_init_with_transfers(&data->write_msg, &data->write_xfer, 1); + ret = devm_spi_optimize_message(dev, spi, &data->write_msg); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to optimize SPI write message\n"); + + ret = devm_iio_adc_device_alloc_chaninfo_se(dev, &bd79112_chan_template, + BD79112_MAX_NUM_CHANNELS - 1, + &cs); + + /* Register all pins as GPIOs if there are no ADC channels */ + if (ret == -ENOENT) + goto register_gpios; + + if (ret < 0) + return ret; + + iio_dev->num_channels = ret; + iio_dev->channels = cs; + + for (i = 0; i < iio_dev->num_channels; i++) + cs[i].datasheet_name = bd79112_chan_names[cs[i].channel]; + + iio_dev->info = &bd79112_info; + iio_dev->name = "bd79112"; + iio_dev->modes = INDIO_DIRECT_MODE; + + /* + * Ensure all channels are ADCs. This allows us to register the IIO + * device early (before checking which pins are to be used for GPIO) + * without having to worry about some pins being initially used for + * GPIO. + */ + for (i = 0; i < BD79112_NUM_GPIO_EN_REGS; i++) { + ret = regmap_write(data->map, BD79112_FIRST_GPIO_EN_REG + i, 0); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize channels\n"); + } + + ret = devm_iio_device_register(data->dev, iio_dev); + if (ret) + return dev_err_probe(data->dev, ret, "Failed to register ADC\n"); + +register_gpios: + gpio_pins = bd79112_get_gpio_pins(iio_dev->channels, + iio_dev->num_channels); + + /* If all channels are reserved for ADC, then we're done. */ + if (!gpio_pins) + return 0; + + /* Default all the GPIO pins to GPI */ + for_each_set_bit(pin, &gpio_pins, BD79112_MAX_NUM_CHANNELS) { + ret = bd79112_gpio_dir_set(data, pin, GPIO_LINE_DIRECTION_IN); + if (ret) + return dev_err_probe(dev, ret, + "Failed to mark pin as GPI\n"); + } + + data->gpio_valid_mask = gpio_pins; + data->gc = bd79112_gpio_chip; + data->gc.parent = dev; + + return devm_gpiochip_add_data(dev, &data->gc, data); +} + +static const struct of_device_id bd79112_of_match[] = { + { .compatible = "rohm,bd79112" }, + { } +}; +MODULE_DEVICE_TABLE(of, bd79112_of_match); + +static const struct spi_device_id bd79112_id[] = { + { "bd79112" }, + { } +}; +MODULE_DEVICE_TABLE(spi, bd79112_id); + +static struct spi_driver bd79112_driver = { + .driver = { + .name = "bd79112", + .of_match_table = bd79112_of_match, + }, + .probe = bd79112_probe, + .id_table = bd79112_id, +}; +module_spi_driver(bd79112_driver); + +MODULE_AUTHOR("Matti Vaittinen <mazziesaccount@gmail.com>"); +MODULE_DESCRIPTION("Driver for ROHM BD79112 ADC/GPIO"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DRIVER"); diff --git a/drivers/iio/adc/rzg2l_adc.c b/drivers/iio/adc/rzg2l_adc.c index cadb0446bc29..1010e0511b3e 100644 --- a/drivers/iio/adc/rzg2l_adc.c +++ b/drivers/iio/adc/rzg2l_adc.c @@ -248,7 +248,6 @@ static int rzg2l_adc_conversion(struct iio_dev *indio_dev, struct rzg2l_adc *adc rzg2l_adc_start_stop(adc, false); rpm_put: - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } @@ -410,7 +409,6 @@ static int rzg2l_adc_hw_init(struct device *dev, struct rzg2l_adc *adc) rzg2l_adc_writel(adc, RZG2L_ADM(3), reg); exit_hw_init: - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } diff --git a/drivers/iio/adc/spear_adc.c b/drivers/iio/adc/spear_adc.c index e3a865c79686..50b0a607baeb 100644 --- a/drivers/iio/adc/spear_adc.c +++ b/drivers/iio/adc/spear_adc.c @@ -14,6 +14,7 @@ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/io.h> +#include <linux/bitfield.h> #include <linux/clk.h> #include <linux/err.h> #include <linux/completion.h> @@ -29,9 +30,9 @@ /* Bit definitions for SPEAR_ADC_STATUS */ #define SPEAR_ADC_STATUS_START_CONVERSION BIT(0) -#define SPEAR_ADC_STATUS_CHANNEL_NUM(x) ((x) << 1) +#define SPEAR_ADC_STATUS_CHANNEL_NUM_MASK GENMASK(3, 1) #define SPEAR_ADC_STATUS_ADC_ENABLE BIT(4) -#define SPEAR_ADC_STATUS_AVG_SAMPLE(x) ((x) << 5) +#define SPEAR_ADC_STATUS_AVG_SAMPLE_MASK GENMASK(8, 5) #define SPEAR_ADC_STATUS_VREF_INTERNAL BIT(9) #define SPEAR_ADC_DATA_MASK 0x03ff @@ -157,8 +158,8 @@ static int spear_adc_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_RAW: mutex_lock(&st->lock); - status = SPEAR_ADC_STATUS_CHANNEL_NUM(chan->channel) | - SPEAR_ADC_STATUS_AVG_SAMPLE(st->avg_samples) | + status = FIELD_PREP(SPEAR_ADC_STATUS_CHANNEL_NUM_MASK, chan->channel) | + FIELD_PREP(SPEAR_ADC_STATUS_AVG_SAMPLE_MASK, st->avg_samples) | SPEAR_ADC_STATUS_START_CONVERSION | SPEAR_ADC_STATUS_ADC_ENABLE; if (st->vref_external == 0) @@ -274,8 +275,7 @@ static int spear_adc_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(dev, sizeof(struct spear_adc_state)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "failed allocating iio device\n"); + return -ENOMEM; st = iio_priv(indio_dev); st->dev = dev; diff --git a/drivers/iio/adc/stm32-adc-core.c b/drivers/iio/adc/stm32-adc-core.c index 3d800762c5fc..e39a4c0db25e 100644 --- a/drivers/iio/adc/stm32-adc-core.c +++ b/drivers/iio/adc/stm32-adc-core.c @@ -794,7 +794,6 @@ static int stm32_adc_probe(struct platform_device *pdev) goto err_irq_remove; } - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index b9f93116e114..2d7f88459c7c 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -1528,7 +1528,6 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev, stm32_adc_conv_irq_disable(adc); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -1564,7 +1563,6 @@ static int stm32_adc_write_raw(struct iio_dev *indio_dev, adc->cfg->set_ovs(indio_dev, idx); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); adc->ovs_idx = idx; @@ -1759,7 +1757,6 @@ static int stm32_adc_update_scan_mode(struct iio_dev *indio_dev, adc->num_conv = bitmap_weight(scan_mask, iio_get_masklength(indio_dev)); ret = stm32_adc_conf_scan_seq(indio_dev, scan_mask); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -1808,7 +1805,6 @@ static int stm32_adc_debugfs_reg_access(struct iio_dev *indio_dev, else *readval = stm32_adc_readl(adc, reg); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; @@ -1954,7 +1950,6 @@ static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev) err_clr_trig: stm32_adc_set_trig(indio_dev, NULL); err_pm_put: - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -1977,7 +1972,6 @@ static int stm32_adc_buffer_predisable(struct iio_dev *indio_dev) if (stm32_adc_set_trig(indio_dev, NULL)) dev_err(&indio_dev->dev, "Can't clear trigger\n"); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; @@ -2614,7 +2608,6 @@ static int stm32_adc_probe(struct platform_device *pdev) goto err_hw_stop; } - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); if (IS_ENABLED(CONFIG_DEBUG_FS)) diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c index c2d21eecafe7..74b1b4dc6e81 100644 --- a/drivers/iio/adc/stm32-dfsdm-adc.c +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -1764,10 +1764,8 @@ static int stm32_dfsdm_adc_probe(struct platform_device *pdev) dev_data = of_device_get_match_data(dev); iio = devm_iio_device_alloc(dev, sizeof(*adc)); - if (!iio) { - dev_err(dev, "%s: Failed to allocate IIO\n", __func__); + if (!iio) return -ENOMEM; - } adc = iio_priv(iio); adc->dfsdm = dev_get_drvdata(dev->parent); diff --git a/drivers/iio/adc/stmpe-adc.c b/drivers/iio/adc/stmpe-adc.c index b0add5a2eab5..8e26c47edc08 100644 --- a/drivers/iio/adc/stmpe-adc.c +++ b/drivers/iio/adc/stmpe-adc.c @@ -267,10 +267,8 @@ static int stmpe_adc_probe(struct platform_device *pdev) return irq_adc; indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct stmpe_adc)); - if (!indio_dev) { - dev_err(&pdev->dev, "failed allocating iio device\n"); + if (!indio_dev) return -ENOMEM; - } info = iio_priv(indio_dev); mutex_init(&info->lock); diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c index 6b8d6bee1873..479115ea50bf 100644 --- a/drivers/iio/adc/sun4i-gpadc-iio.c +++ b/drivers/iio/adc/sun4i-gpadc-iio.c @@ -154,7 +154,6 @@ static const struct regmap_config sun4i_gpadc_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, - .fast_io = true, }; static int sun4i_prepare_for_irq(struct iio_dev *indio_dev, int channel, @@ -245,7 +244,6 @@ static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val, *val = info->temp_data; ret = 0; - pm_runtime_mark_last_busy(indio_dev->dev.parent); err: pm_runtime_put_autosuspend(indio_dev->dev.parent); @@ -272,7 +270,6 @@ static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val) regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, val); - pm_runtime_mark_last_busy(indio_dev->dev.parent); pm_runtime_put_autosuspend(indio_dev->dev.parent); return 0; diff --git a/drivers/iio/adc/ti-adc081c.c b/drivers/iio/adc/ti-adc081c.c index 4f514db5c26e..8ef51c57912d 100644 --- a/drivers/iio/adc/ti-adc081c.c +++ b/drivers/iio/adc/ti-adc081c.c @@ -102,27 +102,23 @@ struct adcxx1c_model { int bits; }; -#define ADCxx1C_MODEL(_name, _bits) \ - { \ - .channels = _name ## _channels, \ - .bits = (_bits), \ - } - DEFINE_ADCxx1C_CHANNELS(adc081c, 8); DEFINE_ADCxx1C_CHANNELS(adc101c, 10); DEFINE_ADCxx1C_CHANNELS(adc121c, 12); -/* Model ids are indexes in _models array */ -enum adcxx1c_model_id { - ADC081C = 0, - ADC101C = 1, - ADC121C = 2, +static const struct adcxx1c_model adc081c_model = { + .channels = adc081c_channels, + .bits = 8, +}; + +static const struct adcxx1c_model adc101c_model = { + .channels = adc101c_channels, + .bits = 10, }; -static struct adcxx1c_model adcxx1c_models[] = { - ADCxx1C_MODEL(adc081c, 8), - ADCxx1C_MODEL(adc101c, 10), - ADCxx1C_MODEL(adc121c, 12), +static const struct adcxx1c_model adc121c_model = { + .channels = adc121c_channels, + .bits = 12, }; static const struct iio_info adc081c_info = { @@ -203,24 +199,24 @@ static int adc081c_probe(struct i2c_client *client) } static const struct i2c_device_id adc081c_id[] = { - { "adc081c", (kernel_ulong_t)&adcxx1c_models[ADC081C] }, - { "adc101c", (kernel_ulong_t)&adcxx1c_models[ADC101C] }, - { "adc121c", (kernel_ulong_t)&adcxx1c_models[ADC121C] }, + { "adc081c", (kernel_ulong_t)&adc081c_model }, + { "adc101c", (kernel_ulong_t)&adc101c_model }, + { "adc121c", (kernel_ulong_t)&adc121c_model }, { } }; MODULE_DEVICE_TABLE(i2c, adc081c_id); static const struct acpi_device_id adc081c_acpi_match[] = { /* Used on some AAEON boards */ - { "ADC081C", (kernel_ulong_t)&adcxx1c_models[ADC081C] }, + { "ADC081C", (kernel_ulong_t)&adc081c_model }, { } }; MODULE_DEVICE_TABLE(acpi, adc081c_acpi_match); static const struct of_device_id adc081c_of_match[] = { - { .compatible = "ti,adc081c", .data = &adcxx1c_models[ADC081C] }, - { .compatible = "ti,adc101c", .data = &adcxx1c_models[ADC101C] }, - { .compatible = "ti,adc121c", .data = &adcxx1c_models[ADC121C] }, + { .compatible = "ti,adc081c", .data = &adc081c_model }, + { .compatible = "ti,adc101c", .data = &adc101c_model }, + { .compatible = "ti,adc121c", .data = &adc121c_model }, { } }; MODULE_DEVICE_TABLE(of, adc081c_of_match); diff --git a/drivers/iio/adc/ti-adc084s021.c b/drivers/iio/adc/ti-adc084s021.c index 50a474f4d9f5..a100f770fa1c 100644 --- a/drivers/iio/adc/ti-adc084s021.c +++ b/drivers/iio/adc/ti-adc084s021.c @@ -200,10 +200,8 @@ static int adc084s021_probe(struct spi_device *spi) int ret; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); - if (!indio_dev) { - dev_err(&spi->dev, "Failed to allocate IIO device\n"); + if (!indio_dev) return -ENOMEM; - } adc = iio_priv(indio_dev); adc->spi = spi; diff --git a/drivers/iio/adc/ti-adc12138.c b/drivers/iio/adc/ti-adc12138.c index 9dc465a10ffc..e5ec4b073daa 100644 --- a/drivers/iio/adc/ti-adc12138.c +++ b/drivers/iio/adc/ti-adc12138.c @@ -38,15 +38,13 @@ enum { struct adc12138 { struct spi_device *spi; unsigned int id; - /* conversion clock */ - struct clk *cclk; /* positive analog voltage reference */ struct regulator *vref_p; /* negative analog voltage reference */ struct regulator *vref_n; struct mutex lock; struct completion complete; - /* The number of cclk periods for the S/H's acquisition time */ + /* The number of conversion clock periods for the S/H's acquisition time */ unsigned int acquisition_time; /* * Maximum size needed: 16x 2 bytes ADC data + 8 bytes timestamp. @@ -400,6 +398,7 @@ static int adc12138_probe(struct spi_device *spi) { struct iio_dev *indio_dev; struct adc12138 *adc; + struct clk *cclk; int ret; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); @@ -435,9 +434,14 @@ static int adc12138_probe(struct spi_device *spi) if (ret) adc->acquisition_time = 10; - adc->cclk = devm_clk_get(&spi->dev, NULL); - if (IS_ERR(adc->cclk)) - return PTR_ERR(adc->cclk); + ret = devm_request_irq(&spi->dev, spi->irq, adc12138_eoc_handler, + IRQF_TRIGGER_RISING, indio_dev->name, indio_dev); + if (ret) + return ret; + + cclk = devm_clk_get_enabled(&spi->dev, NULL); + if (IS_ERR(cclk)) + return PTR_ERR(cclk); adc->vref_p = devm_regulator_get(&spi->dev, "vref-p"); if (IS_ERR(adc->vref_p)) @@ -454,18 +458,9 @@ static int adc12138_probe(struct spi_device *spi) return ret; } - ret = devm_request_irq(&spi->dev, spi->irq, adc12138_eoc_handler, - IRQF_TRIGGER_RISING, indio_dev->name, indio_dev); - if (ret) - return ret; - - ret = clk_prepare_enable(adc->cclk); - if (ret) - return ret; - ret = regulator_enable(adc->vref_p); if (ret) - goto err_clk_disable; + return ret; if (!IS_ERR(adc->vref_n)) { ret = regulator_enable(adc->vref_n); @@ -496,8 +491,6 @@ err_vref_n_disable: regulator_disable(adc->vref_n); err_vref_p_disable: regulator_disable(adc->vref_p); -err_clk_disable: - clk_disable_unprepare(adc->cclk); return ret; } @@ -512,7 +505,6 @@ static void adc12138_remove(struct spi_device *spi) if (!IS_ERR(adc->vref_n)) regulator_disable(adc->vref_n); regulator_disable(adc->vref_p); - clk_disable_unprepare(adc->cclk); } static const struct of_device_id adc12138_dt_ids[] = { diff --git a/drivers/iio/adc/ti-adc128s052.c b/drivers/iio/adc/ti-adc128s052.c index 1b46a8155803..4ae65793ad9b 100644 --- a/drivers/iio/adc/ti-adc128s052.c +++ b/drivers/iio/adc/ti-adc128s052.c @@ -99,51 +99,83 @@ static int adc128_read_raw(struct iio_dev *indio_dev, .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ } -static const struct iio_chan_spec adc128s052_channels[] = { +static const struct iio_chan_spec simple_1chan_adc_channels[] = { + ADC128_VOLTAGE_CHANNEL(0), +}; + +static const struct iio_chan_spec simple_2chan_adc_channels[] = { ADC128_VOLTAGE_CHANNEL(0), ADC128_VOLTAGE_CHANNEL(1), - ADC128_VOLTAGE_CHANNEL(2), - ADC128_VOLTAGE_CHANNEL(3), - ADC128_VOLTAGE_CHANNEL(4), - ADC128_VOLTAGE_CHANNEL(5), - ADC128_VOLTAGE_CHANNEL(6), - ADC128_VOLTAGE_CHANNEL(7), }; -static const struct iio_chan_spec adc122s021_channels[] = { +static const struct iio_chan_spec simple_4chan_adc_channels[] = { ADC128_VOLTAGE_CHANNEL(0), ADC128_VOLTAGE_CHANNEL(1), + ADC128_VOLTAGE_CHANNEL(2), + ADC128_VOLTAGE_CHANNEL(3), }; -static const struct iio_chan_spec adc124s021_channels[] = { +static const struct iio_chan_spec simple_8chan_adc_channels[] = { ADC128_VOLTAGE_CHANNEL(0), ADC128_VOLTAGE_CHANNEL(1), ADC128_VOLTAGE_CHANNEL(2), ADC128_VOLTAGE_CHANNEL(3), + ADC128_VOLTAGE_CHANNEL(4), + ADC128_VOLTAGE_CHANNEL(5), + ADC128_VOLTAGE_CHANNEL(6), + ADC128_VOLTAGE_CHANNEL(7), }; static const char * const bd79104_regulators[] = { "iovdd" }; -static const struct adc128_configuration adc128_config[] = { - { - .channels = adc128s052_channels, - .num_channels = ARRAY_SIZE(adc128s052_channels), - .refname = "vref", - }, { - .channels = adc122s021_channels, - .num_channels = ARRAY_SIZE(adc122s021_channels), - .refname = "vref", - }, { - .channels = adc124s021_channels, - .num_channels = ARRAY_SIZE(adc124s021_channels), - .refname = "vref", - }, { - .channels = adc128s052_channels, - .num_channels = ARRAY_SIZE(adc128s052_channels), - .refname = "vdd", - .other_regulators = &bd79104_regulators, - .num_other_regulators = 1, - }, +static const struct adc128_configuration adc122s_config = { + .channels = simple_2chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_2chan_adc_channels), + .refname = "vref", +}; + +static const struct adc128_configuration adc124s_config = { + .channels = simple_4chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_4chan_adc_channels), + .refname = "vref", +}; + +static const struct adc128_configuration adc128s_config = { + .channels = simple_8chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_8chan_adc_channels), + .refname = "vref", +}; + +static const struct adc128_configuration bd79100_config = { + .channels = simple_1chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_1chan_adc_channels), + .refname = "vdd", + .other_regulators = &bd79104_regulators, + .num_other_regulators = 1, +}; + +static const struct adc128_configuration bd79101_config = { + .channels = simple_2chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_2chan_adc_channels), + .refname = "vdd", + .other_regulators = &bd79104_regulators, + .num_other_regulators = 1, +}; + +static const struct adc128_configuration bd79102_config = { + .channels = simple_4chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_4chan_adc_channels), + .refname = "vdd", + .other_regulators = &bd79104_regulators, + .num_other_regulators = 1, +}; + +static const struct adc128_configuration bd79104_config = { + .channels = simple_8chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_8chan_adc_channels), + .refname = "vdd", + .other_regulators = &bd79104_regulators, + .num_other_regulators = 1, }; static const struct iio_info adc128_info = { @@ -199,33 +231,41 @@ static int adc128_probe(struct spi_device *spi) } static const struct of_device_id adc128_of_match[] = { - { .compatible = "ti,adc128s052", .data = &adc128_config[0] }, - { .compatible = "ti,adc122s021", .data = &adc128_config[1] }, - { .compatible = "ti,adc122s051", .data = &adc128_config[1] }, - { .compatible = "ti,adc122s101", .data = &adc128_config[1] }, - { .compatible = "ti,adc124s021", .data = &adc128_config[2] }, - { .compatible = "ti,adc124s051", .data = &adc128_config[2] }, - { .compatible = "ti,adc124s101", .data = &adc128_config[2] }, - { .compatible = "rohm,bd79104", .data = &adc128_config[3] }, + { .compatible = "ti,adc128s052", .data = &adc128s_config }, + { .compatible = "ti,adc122s021", .data = &adc122s_config }, + { .compatible = "ti,adc122s051", .data = &adc122s_config }, + { .compatible = "ti,adc122s101", .data = &adc122s_config }, + { .compatible = "ti,adc124s021", .data = &adc124s_config }, + { .compatible = "ti,adc124s051", .data = &adc124s_config }, + { .compatible = "ti,adc124s101", .data = &adc124s_config }, + { .compatible = "rohm,bd79100", .data = &bd79100_config }, + { .compatible = "rohm,bd79101", .data = &bd79101_config }, + { .compatible = "rohm,bd79102", .data = &bd79102_config }, + { .compatible = "rohm,bd79103", .data = &bd79104_config }, + { .compatible = "rohm,bd79104", .data = &bd79104_config }, { } }; MODULE_DEVICE_TABLE(of, adc128_of_match); static const struct spi_device_id adc128_id[] = { - { "adc128s052", (kernel_ulong_t)&adc128_config[0] }, - { "adc122s021", (kernel_ulong_t)&adc128_config[1] }, - { "adc122s051", (kernel_ulong_t)&adc128_config[1] }, - { "adc122s101", (kernel_ulong_t)&adc128_config[1] }, - { "adc124s021", (kernel_ulong_t)&adc128_config[2] }, - { "adc124s051", (kernel_ulong_t)&adc128_config[2] }, - { "adc124s101", (kernel_ulong_t)&adc128_config[2] }, - { "bd79104", (kernel_ulong_t)&adc128_config[3] }, + { "adc128s052", (kernel_ulong_t)&adc128s_config }, + { "adc122s021", (kernel_ulong_t)&adc122s_config }, + { "adc122s051", (kernel_ulong_t)&adc122s_config }, + { "adc122s101", (kernel_ulong_t)&adc122s_config }, + { "adc124s021", (kernel_ulong_t)&adc124s_config }, + { "adc124s051", (kernel_ulong_t)&adc124s_config }, + { "adc124s101", (kernel_ulong_t)&adc124s_config }, + { "bd79100", (kernel_ulong_t)&bd79100_config }, + { "bd79101", (kernel_ulong_t)&bd79101_config }, + { "bd79102", (kernel_ulong_t)&bd79102_config }, + { "bd79103", (kernel_ulong_t)&bd79104_config }, + { "bd79104", (kernel_ulong_t)&bd79104_config }, { } }; MODULE_DEVICE_TABLE(spi, adc128_id); static const struct acpi_device_id adc128_acpi_match[] = { - { "AANT1280", (kernel_ulong_t)&adc128_config[2] }, + { "AANT1280", (kernel_ulong_t)&adc124s_config }, { } }; MODULE_DEVICE_TABLE(acpi, adc128_acpi_match); diff --git a/drivers/iio/adc/ti-ads1015.c b/drivers/iio/adc/ti-ads1015.c index 48549d617e5f..f2a93c63ca14 100644 --- a/drivers/iio/adc/ti-ads1015.c +++ b/drivers/iio/adc/ti-ads1015.c @@ -374,12 +374,10 @@ static int ads1015_set_power_state(struct ads1015_data *data, bool on) int ret; struct device *dev = regmap_get_device(data->regmap); - if (on) { + if (on) ret = pm_runtime_resume_and_get(dev); - } else { - pm_runtime_mark_last_busy(dev); + else ret = pm_runtime_put_autosuspend(dev); - } return ret < 0 ? ret : 0; } diff --git a/drivers/iio/adc/ti-ads1100.c b/drivers/iio/adc/ti-ads1100.c index b0790e300b18..aa8946063c7d 100644 --- a/drivers/iio/adc/ti-ads1100.c +++ b/drivers/iio/adc/ti-ads1100.c @@ -105,7 +105,6 @@ static int ads1100_get_adc_result(struct ads1100_data *data, int chan, int *val) ret = i2c_master_recv(data->client, (char *)&buffer, sizeof(buffer)); - pm_runtime_mark_last_busy(&data->client->dev); pm_runtime_put_autosuspend(&data->client->dev); if (ret < 0) { diff --git a/drivers/iio/adc/ti-ads1119.c b/drivers/iio/adc/ti-ads1119.c index d2f86e1ec656..c9cedc59cdcd 100644 --- a/drivers/iio/adc/ti-ads1119.c +++ b/drivers/iio/adc/ti-ads1119.c @@ -291,7 +291,6 @@ static int ads1119_single_conversion(struct ads1119_state *st, *val = sign_extend32(sample, chan->scan_type.realbits - 1); ret = IIO_VAL_INT; pdown: - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } @@ -470,7 +469,6 @@ static int ads1119_triggered_buffer_postdisable(struct iio_dev *indio_dev) if (ret) return ret; - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; @@ -693,8 +691,7 @@ static int ads1119_probe(struct i2c_client *client) indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "Failed to allocate IIO device\n"); + return -ENOMEM; st = iio_priv(indio_dev); st->client = client; @@ -750,8 +747,7 @@ static int ads1119_probe(struct i2c_client *client) indio_dev->name, iio_device_id(indio_dev)); if (!st->trig) - return dev_err_probe(dev, -ENOMEM, - "Failed to allocate IIO trigger\n"); + return -ENOMEM; st->trig->ops = &ads1119_trigger_ops; iio_trigger_set_drvdata(st->trig, indio_dev); @@ -778,8 +774,7 @@ static int ads1119_probe(struct i2c_client *client) ret = devm_add_action_or_reset(dev, ads1119_powerdown, st); if (ret) - return dev_err_probe(dev, ret, - "Failed to add powerdown action\n"); + return ret; return devm_iio_device_register(dev, indio_dev); } diff --git a/drivers/iio/adc/ti-ads131e08.c b/drivers/iio/adc/ti-ads131e08.c index b18f30d3fdbe..742acc6d8cf9 100644 --- a/drivers/iio/adc/ti-ads131e08.c +++ b/drivers/iio/adc/ti-ads131e08.c @@ -807,10 +807,8 @@ static int ads131e08_probe(struct spi_device *spi) } indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); - if (!indio_dev) { - dev_err(&spi->dev, "failed to allocate IIO device\n"); + if (!indio_dev) return -ENOMEM; - } st = iio_priv(indio_dev); st->info = info; @@ -841,10 +839,8 @@ static int ads131e08_probe(struct spi_device *spi) st->trig = devm_iio_trigger_alloc(&spi->dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); - if (!st->trig) { - dev_err(&spi->dev, "failed to allocate IIO trigger\n"); + if (!st->trig) return -ENOMEM; - } st->trig->ops = &ads131e08_trigger_ops; st->trig->dev.parent = &spi->dev; diff --git a/drivers/iio/adc/ti-ads7924.c b/drivers/iio/adc/ti-ads7924.c index b1f745f75dbe..bbcc4fc22b6e 100644 --- a/drivers/iio/adc/ti-ads7924.c +++ b/drivers/iio/adc/ti-ads7924.c @@ -355,8 +355,7 @@ static int ads7924_probe(struct i2c_client *client) indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "failed to allocate iio device\n"); + return -ENOMEM; data = iio_priv(indio_dev); @@ -399,8 +398,7 @@ static int ads7924_probe(struct i2c_client *client) ret = devm_add_action_or_reset(dev, ads7924_reg_disable, data->vref_reg); if (ret) - return dev_err_probe(dev, ret, - "failed to add regulator disable action\n"); + return ret; ret = ads7924_reset(indio_dev); if (ret < 0) @@ -414,8 +412,7 @@ static int ads7924_probe(struct i2c_client *client) ret = devm_add_action_or_reset(dev, ads7924_set_idle_mode, data); if (ret) - return dev_err_probe(dev, ret, - "failed to add idle mode action\n"); + return ret; /* Use minimum signal acquire time. */ ret = regmap_update_bits(data->regmap, ADS7924_ACQCONFIG_REG, diff --git a/drivers/iio/adc/ti-tsc2046.c b/drivers/iio/adc/ti-tsc2046.c index 74471f08662e..8eb717b11cff 100644 --- a/drivers/iio/adc/ti-tsc2046.c +++ b/drivers/iio/adc/ti-tsc2046.c @@ -535,8 +535,7 @@ static enum hrtimer_restart tsc2046_adc_timer(struct hrtimer *hrtimer) if (priv->poll_cnt < TI_TSC2046_POLL_CNT) { priv->poll_cnt++; hrtimer_start(&priv->trig_timer, - ns_to_ktime(priv->scan_interval_us * - NSEC_PER_USEC), + us_to_ktime(priv->scan_interval_us), HRTIMER_MODE_REL_SOFT); if (priv->poll_cnt >= TI_TSC2046_MIN_POLL_CNT) { @@ -605,8 +604,7 @@ static void tsc2046_adc_reenable_trigger(struct iio_trigger *trig) * many samples. Reduce the sample rate for default (touchscreen) use * case. */ - tim = ns_to_ktime((priv->scan_interval_us - priv->time_per_scan_us) * - NSEC_PER_USEC); + tim = us_to_ktime(priv->scan_interval_us - priv->time_per_scan_us); hrtimer_start(&priv->trig_timer, tim, HRTIMER_MODE_REL_SOFT); } diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c index fe1509d3b1e7..99f274adc870 100644 --- a/drivers/iio/adc/ti_am335x_adc.c +++ b/drivers/iio/adc/ti_am335x_adc.c @@ -631,10 +631,9 @@ static int tiadc_probe(struct platform_device *pdev) } indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc_dev)); - if (!indio_dev) { - dev_err(&pdev->dev, "failed to allocate iio device\n"); + if (!indio_dev) return -ENOMEM; - } + adc_dev = iio_priv(indio_dev); adc_dev->mfd_tscadc = ti_tscadc_dev_get(pdev); diff --git a/drivers/iio/adc/twl4030-madc.c b/drivers/iio/adc/twl4030-madc.c index 0ea51ddeaa0a..fe3b31ec976e 100644 --- a/drivers/iio/adc/twl4030-madc.c +++ b/drivers/iio/adc/twl4030-madc.c @@ -758,10 +758,8 @@ static int twl4030_madc_probe(struct platform_device *pdev) } iio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*madc)); - if (!iio_dev) { - dev_err(&pdev->dev, "failed allocating iio device\n"); + if (!iio_dev) return -ENOMEM; - } madc = iio_priv(iio_dev); madc->dev = &pdev->dev; diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c index 1b3b1843a801..d7182ed0d2a7 100644 --- a/drivers/iio/adc/vf610_adc.c +++ b/drivers/iio/adc/vf610_adc.c @@ -832,7 +832,7 @@ static int vf610_adc_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc)); if (!indio_dev) - return dev_err_probe(&pdev->dev, -ENOMEM, "Failed allocating iio device\n"); + return -ENOMEM; info = iio_priv(indio_dev); info->dev = &pdev->dev; diff --git a/drivers/iio/adc/viperboard_adc.c b/drivers/iio/adc/viperboard_adc.c index 1028b101cf56..9bb0b83c8f67 100644 --- a/drivers/iio/adc/viperboard_adc.c +++ b/drivers/iio/adc/viperboard_adc.c @@ -113,10 +113,8 @@ static int vprbrd_adc_probe(struct platform_device *pdev) /* registering iio */ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); - if (!indio_dev) { - dev_err(&pdev->dev, "failed allocating iio device\n"); + if (!indio_dev) return -ENOMEM; - } adc = iio_priv(indio_dev); adc->vb = vb; diff --git a/drivers/iio/adc/xilinx-ams.c b/drivers/iio/adc/xilinx-ams.c index 76dd0343f5f7..124470c92529 100644 --- a/drivers/iio/adc/xilinx-ams.c +++ b/drivers/iio/adc/xilinx-ams.c @@ -118,7 +118,7 @@ #define AMS_ALARM_THRESHOLD_OFF_10 0x10 #define AMS_ALARM_THRESHOLD_OFF_20 0x20 -#define AMS_ALARM_THR_DIRECT_MASK BIT(1) +#define AMS_ALARM_THR_DIRECT_MASK BIT(0) #define AMS_ALARM_THR_MIN 0x0000 #define AMS_ALARM_THR_MAX (BIT(16) - 1) @@ -389,6 +389,29 @@ static void ams_update_pl_alarm(struct ams *ams, unsigned long alarm_mask) ams_pl_update_reg(ams, AMS_REG_CONFIG3, AMS_REGCFG3_ALARM_MASK, cfg); } +static void ams_unmask(struct ams *ams) +{ + unsigned int status, unmask; + + status = readl(ams->base + AMS_ISR_0); + + /* Clear those bits which are not active anymore */ + unmask = (ams->current_masked_alarm ^ status) & ams->current_masked_alarm; + + /* Clear status of disabled alarm */ + unmask |= ams->intr_mask; + + ams->current_masked_alarm &= status; + + /* Also clear those which are masked out anyway */ + ams->current_masked_alarm &= ~ams->intr_mask; + + /* Clear the interrupts before we unmask them */ + writel(unmask, ams->base + AMS_ISR_0); + + ams_update_intrmask(ams, ~AMS_ALARM_MASK, ~AMS_ALARM_MASK); +} + static void ams_update_alarm(struct ams *ams, unsigned long alarm_mask) { unsigned long flags; @@ -401,6 +424,7 @@ static void ams_update_alarm(struct ams *ams, unsigned long alarm_mask) spin_lock_irqsave(&ams->intr_lock, flags); ams_update_intrmask(ams, AMS_ISR0_ALARM_MASK, ~alarm_mask); + ams_unmask(ams); spin_unlock_irqrestore(&ams->intr_lock, flags); } @@ -1035,28 +1059,9 @@ static void ams_handle_events(struct iio_dev *indio_dev, unsigned long events) static void ams_unmask_worker(struct work_struct *work) { struct ams *ams = container_of(work, struct ams, ams_unmask_work.work); - unsigned int status, unmask; spin_lock_irq(&ams->intr_lock); - - status = readl(ams->base + AMS_ISR_0); - - /* Clear those bits which are not active anymore */ - unmask = (ams->current_masked_alarm ^ status) & ams->current_masked_alarm; - - /* Clear status of disabled alarm */ - unmask |= ams->intr_mask; - - ams->current_masked_alarm &= status; - - /* Also clear those which are masked out anyway */ - ams->current_masked_alarm &= ~ams->intr_mask; - - /* Clear the interrupts before we unmask them */ - writel(unmask, ams->base + AMS_ISR_0); - - ams_update_intrmask(ams, ~AMS_ALARM_MASK, ~AMS_ALARM_MASK); - + ams_unmask(ams); spin_unlock_irq(&ams->intr_lock); /* If still pending some alarm re-trigger the timer */ diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c index 4befc9f55201..3e27385069ed 100644 --- a/drivers/iio/buffer/industrialio-buffer-cb.c +++ b/drivers/iio/buffer/industrialio-buffer-cb.c @@ -68,7 +68,6 @@ struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, cb_buff->private = private; cb_buff->cb = cb; cb_buff->buffer.access = &iio_cb_access; - INIT_LIST_HEAD(&cb_buff->buffer.demux_list); cb_buff->channels = iio_channel_get_all(dev); if (IS_ERR(cb_buff->channels)) { diff --git a/drivers/iio/chemical/atlas-sensor.c b/drivers/iio/chemical/atlas-sensor.c index 1daaa36f87a9..8bbba85af699 100644 --- a/drivers/iio/chemical/atlas-sensor.c +++ b/drivers/iio/chemical/atlas-sensor.c @@ -425,7 +425,6 @@ static int atlas_buffer_predisable(struct iio_dev *indio_dev) if (ret) return ret; - pm_runtime_mark_last_busy(&data->client->dev); ret = pm_runtime_put_autosuspend(&data->client->dev); if (ret) return ret; @@ -491,7 +490,6 @@ static int atlas_read_measurement(struct atlas_data *data, int reg, __be32 *val) ret = regmap_bulk_read(data->regmap, reg, val, sizeof(*val)); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; diff --git a/drivers/iio/chemical/bme680_core.c b/drivers/iio/chemical/bme680_core.c index 61d446fd456c..70f81c4a96ba 100644 --- a/drivers/iio/chemical/bme680_core.c +++ b/drivers/iio/chemical/bme680_core.c @@ -950,7 +950,6 @@ static int bme680_read_raw(struct iio_dev *indio_dev, return ret; ret = __bme680_read_raw(indio_dev, chan, val, val2, mask); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -1021,7 +1020,6 @@ static int bme680_write_raw(struct iio_dev *indio_dev, return ret; ret = __bme680_write_raw(indio_dev, chan, val, val2, mask); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -1140,7 +1138,6 @@ static int bme680_buffer_postdisable(struct iio_dev *indio_dev) struct bme680_data *data = iio_priv(indio_dev); struct device *dev = regmap_get_device(data->regmap); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; } diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c index 6cec60074827..86bde4a91bf7 100644 --- a/drivers/iio/chemical/ens160_core.c +++ b/drivers/iio/chemical/ens160_core.c @@ -305,8 +305,7 @@ static int ens160_setup_trigger(struct iio_dev *indio_dev, int irq) trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); if (!trig) - return dev_err_probe(dev, -ENOMEM, - "failed to allocate trigger\n"); + return -ENOMEM; trig->ops = &ens160_trigger_ops; iio_trigger_set_drvdata(trig, indio_dev); diff --git a/drivers/iio/chemical/scd30_core.c b/drivers/iio/chemical/scd30_core.c index 5df1926cd5d9..a665fcb78806 100644 --- a/drivers/iio/chemical/scd30_core.c +++ b/drivers/iio/chemical/scd30_core.c @@ -635,7 +635,7 @@ static int scd30_setup_trigger(struct iio_dev *indio_dev) trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); if (!trig) - return dev_err_probe(dev, -ENOMEM, "failed to allocate trigger\n"); + return -ENOMEM; trig->ops = &scd30_trigger_ops; iio_trigger_set_drvdata(trig, indio_dev); diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c index 48193937275b..5540e2d28f4a 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -163,7 +163,6 @@ int hid_sensor_power_state(struct hid_sensor_common *st, bool state) ret = pm_runtime_resume_and_get(&st->pdev->dev); } else { atomic_dec(&st->user_requested_state); - pm_runtime_mark_last_busy(&st->pdev->dev); pm_runtime_use_autosuspend(&st->pdev->dev); ret = pm_runtime_put_autosuspend(&st->pdev->dev); } diff --git a/drivers/iio/common/scmi_sensors/scmi_iio.c b/drivers/iio/common/scmi_sensors/scmi_iio.c index da516c46e057..39c61c47022a 100644 --- a/drivers/iio/common/scmi_sensors/scmi_iio.c +++ b/drivers/iio/common/scmi_sensors/scmi_iio.c @@ -521,9 +521,9 @@ static int scmi_iio_set_sampling_freq_avail(struct iio_dev *iio_dev) int i; sensor->freq_avail = - devm_kzalloc(&iio_dev->dev, - sizeof(*sensor->freq_avail) * - (sensor->sensor_info->intervals.count * 2), + devm_kcalloc(&iio_dev->dev, + array_size(sensor->sensor_info->intervals.count, 2), + sizeof(*sensor->freq_avail), GFP_KERNEL); if (!sensor->freq_avail) return -ENOMEM; @@ -597,8 +597,8 @@ scmi_alloc_iiodev(struct scmi_device *sdev, iiodev->info = &scmi_iio_info; iio_channels = - devm_kzalloc(dev, - sizeof(*iio_channels) * (iiodev->num_channels), + devm_kcalloc(dev, iiodev->num_channels, + sizeof(*iio_channels), GFP_KERNEL); if (!iio_channels) return ERR_PTR(-ENOMEM); diff --git a/drivers/iio/dac/ad5360.c b/drivers/iio/dac/ad5360.c index a57b0a093112..8271849b1c83 100644 --- a/drivers/iio/dac/ad5360.c +++ b/drivers/iio/dac/ad5360.c @@ -262,7 +262,7 @@ static int ad5360_update_ctrl(struct iio_dev *indio_dev, unsigned int set, unsigned int clr) { struct ad5360_state *st = iio_priv(indio_dev); - unsigned int ret; + int ret; mutex_lock(&st->lock); diff --git a/drivers/iio/dac/ad5380.c b/drivers/iio/dac/ad5380.c index 0ddce7b218e3..8b813cee7625 100644 --- a/drivers/iio/dac/ad5380.c +++ b/drivers/iio/dac/ad5380.c @@ -371,10 +371,8 @@ static int ad5380_probe(struct device *dev, struct regmap *regmap, int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); - if (indio_dev == NULL) { - dev_err(dev, "Failed to allocate iio device\n"); + if (indio_dev == NULL) return -ENOMEM; - } st = iio_priv(indio_dev); diff --git a/drivers/iio/dac/ad5421.c b/drivers/iio/dac/ad5421.c index 1462ee640b16..d9d7031c4432 100644 --- a/drivers/iio/dac/ad5421.c +++ b/drivers/iio/dac/ad5421.c @@ -186,7 +186,7 @@ static int ad5421_update_ctrl(struct iio_dev *indio_dev, unsigned int set, unsigned int clr) { struct ad5421_state *st = iio_priv(indio_dev); - unsigned int ret; + int ret; mutex_lock(&st->lock); diff --git a/drivers/iio/dac/ad5764.c b/drivers/iio/dac/ad5764.c index 26c049d5b73a..fbbd7105a80c 100644 --- a/drivers/iio/dac/ad5764.c +++ b/drivers/iio/dac/ad5764.c @@ -278,10 +278,8 @@ static int ad5764_probe(struct spi_device *spi) int ret; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); - if (indio_dev == NULL) { - dev_err(&spi->dev, "Failed to allocate iio device\n"); + if (indio_dev == NULL) return -ENOMEM; - } st = iio_priv(indio_dev); spi_set_drvdata(spi, indio_dev); diff --git a/drivers/iio/dac/ad5791.c b/drivers/iio/dac/ad5791.c index 41582f2b90fb..ae7297f08398 100644 --- a/drivers/iio/dac/ad5791.c +++ b/drivers/iio/dac/ad5791.c @@ -80,8 +80,6 @@ struct ad5791_chip_info { /** * struct ad5791_state - driver instance specific data * @spi: spi_device - * @reg_vdd: positive supply regulator - * @reg_vss: negative supply regulator * @gpio_reset: reset gpio * @gpio_clear: clear gpio * @gpio_ldac: load dac gpio @@ -100,8 +98,6 @@ struct ad5791_chip_info { */ struct ad5791_state { struct spi_device *spi; - struct regulator *reg_vdd; - struct regulator *reg_vss; struct gpio_desc *gpio_reset; struct gpio_desc *gpio_clear; struct gpio_desc *gpio_ldac; diff --git a/drivers/iio/dac/ds4424.c b/drivers/iio/dac/ds4424.c index a26a99753418..a8198ba4f98a 100644 --- a/drivers/iio/dac/ds4424.c +++ b/drivers/iio/dac/ds4424.c @@ -221,10 +221,8 @@ static int ds4424_probe(struct i2c_client *client) int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio dev alloc failed.\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); diff --git a/drivers/iio/dac/stm32-dac.c b/drivers/iio/dac/stm32-dac.c index 344388338d9b..b860e18d52a1 100644 --- a/drivers/iio/dac/stm32-dac.c +++ b/drivers/iio/dac/stm32-dac.c @@ -82,9 +82,11 @@ static int stm32_dac_set_enable_state(struct iio_dev *indio_dev, int ch, ret = regmap_update_bits(dac->common->regmap, STM32_DAC_CR, msk, en); mutex_unlock(&dac->lock); - if (ret < 0) { + if (ret) { dev_err(&indio_dev->dev, "%s failed\n", str_enable_disable(en)); - goto err_put_pm; + if (enable) + pm_runtime_put_autosuspend(dev); + return ret; } /* @@ -95,20 +97,10 @@ static int stm32_dac_set_enable_state(struct iio_dev *indio_dev, int ch, if (en && dac->common->hfsel) udelay(1); - if (!enable) { - pm_runtime_mark_last_busy(dev); + if (!enable) pm_runtime_put_autosuspend(dev); - } return 0; - -err_put_pm: - if (enable) { - pm_runtime_mark_last_busy(dev); - pm_runtime_put_autosuspend(dev); - } - - return ret; } static int stm32_dac_get_value(struct stm32_dac *dac, int channel, int *val) @@ -349,7 +341,6 @@ static int stm32_dac_probe(struct platform_device *pdev) if (ret) goto err_pm_put; - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; diff --git a/drivers/iio/dac/ti-dac7311.c b/drivers/iio/dac/ti-dac7311.c index 3d2ce61f0db6..5c1c5213962f 100644 --- a/drivers/iio/dac/ti-dac7311.c +++ b/drivers/iio/dac/ti-dac7311.c @@ -242,10 +242,8 @@ static int ti_dac_probe(struct spi_device *spi) int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*ti_dac)); - if (!indio_dev) { - dev_err(dev, "can not allocate iio device\n"); + if (!indio_dev) return -ENOMEM; - } spi->mode = SPI_MODE_1; spi->bits_per_word = 16; diff --git a/drivers/iio/frequency/adf4350.c b/drivers/iio/frequency/adf4350.c index 47f1c7e9efa9..ed1741165f55 100644 --- a/drivers/iio/frequency/adf4350.c +++ b/drivers/iio/frequency/adf4350.c @@ -149,6 +149,19 @@ static int adf4350_set_freq(struct adf4350_state *st, unsigned long long freq) if (freq > ADF4350_MAX_OUT_FREQ || freq < st->min_out_freq) return -EINVAL; + st->r4_rf_div_sel = 0; + + /* + * !\TODO: The below computation is making sure we get a power of 2 + * shift (st->r4_rf_div_sel) so that freq becomes higher or equal to + * ADF4350_MIN_VCO_FREQ. This might be simplified with fls()/fls_long() + * and friends. + */ + while (freq < ADF4350_MIN_VCO_FREQ) { + freq <<= 1; + st->r4_rf_div_sel++; + } + if (freq > ADF4350_MAX_FREQ_45_PRESC) { prescaler = ADF4350_REG1_PRESCALER; mdiv = 75; @@ -157,13 +170,6 @@ static int adf4350_set_freq(struct adf4350_state *st, unsigned long long freq) mdiv = 23; } - st->r4_rf_div_sel = 0; - - while (freq < ADF4350_MIN_VCO_FREQ) { - freq <<= 1; - st->r4_rf_div_sel++; - } - /* * Allow a predefined reference division factor * if not set, compute our own @@ -673,8 +679,7 @@ static int adf4350_probe(struct spi_device *spi) ret = devm_add_action_or_reset(&spi->dev, adf4350_power_down, indio_dev); if (ret) - return dev_err_probe(&spi->dev, ret, - "Failed to add action to managed power down\n"); + return ret; return devm_iio_device_register(&spi->dev, indio_dev); } diff --git a/drivers/iio/gyro/bmg160_core.c b/drivers/iio/gyro/bmg160_core.c index 781d3e96645f..38394b5f3275 100644 --- a/drivers/iio/gyro/bmg160_core.c +++ b/drivers/iio/gyro/bmg160_core.c @@ -309,10 +309,8 @@ static int bmg160_set_power_state(struct bmg160_data *data, bool on) if (on) ret = pm_runtime_get_sync(dev); - else { - pm_runtime_mark_last_busy(dev); + else ret = pm_runtime_put_autosuspend(dev); - } if (ret < 0) { dev_err(dev, "Failed: bmg160_set_power_state for %d\n", on); diff --git a/drivers/iio/gyro/fxas21002c_core.c b/drivers/iio/gyro/fxas21002c_core.c index 754c8a564ba4..a88670207cec 100644 --- a/drivers/iio/gyro/fxas21002c_core.c +++ b/drivers/iio/gyro/fxas21002c_core.c @@ -373,8 +373,6 @@ static int fxas21002c_pm_put(struct fxas21002c_data *data) { struct device *dev = regmap_get_device(data->regmap); - pm_runtime_mark_last_busy(dev); - return pm_runtime_put_autosuspend(dev); } diff --git a/drivers/iio/gyro/mpu3050-core.c b/drivers/iio/gyro/mpu3050-core.c index 16553948c5c3..67ae7d1012bc 100644 --- a/drivers/iio/gyro/mpu3050-core.c +++ b/drivers/iio/gyro/mpu3050-core.c @@ -370,7 +370,6 @@ static int mpu3050_read_raw(struct iio_dev *indio_dev, out_read_raw_unlock: mutex_unlock(&mpu3050->lock); - pm_runtime_mark_last_busy(mpu3050->dev); pm_runtime_put_autosuspend(mpu3050->dev); return ret; @@ -662,7 +661,6 @@ static int mpu3050_buffer_postdisable(struct iio_dev *indio_dev) { struct mpu3050 *mpu3050 = iio_priv(indio_dev); - pm_runtime_mark_last_busy(mpu3050->dev); pm_runtime_put_autosuspend(mpu3050->dev); return 0; @@ -976,7 +974,6 @@ static int mpu3050_drdy_trigger_set_state(struct iio_trigger *trig, if (ret) dev_err(mpu3050->dev, "error resetting FIFO\n"); - pm_runtime_mark_last_busy(mpu3050->dev); pm_runtime_put_autosuspend(mpu3050->dev); mpu3050->hw_irq_trigger = false; diff --git a/drivers/iio/gyro/mpu3050-i2c.c b/drivers/iio/gyro/mpu3050-i2c.c index 8e284f47242c..092878f2c886 100644 --- a/drivers/iio/gyro/mpu3050-i2c.c +++ b/drivers/iio/gyro/mpu3050-i2c.c @@ -27,7 +27,6 @@ static int mpu3050_i2c_bypass_deselect(struct i2c_mux_core *mux, u32 chan_id) { struct mpu3050 *mpu3050 = i2c_mux_priv(mux); - pm_runtime_mark_last_busy(mpu3050->dev); pm_runtime_put_autosuspend(mpu3050->dev); return 0; } diff --git a/drivers/iio/health/afe4403.c b/drivers/iio/health/afe4403.c index 30d3f984b032..0e5a512e3bb8 100644 --- a/drivers/iio/health/afe4403.c +++ b/drivers/iio/health/afe4403.c @@ -58,7 +58,6 @@ static const struct reg_field afe4403_reg_fields[] = { /** * struct afe4403_data - AFE4403 device instance data - * @dev: Device structure * @spi: SPI device handle * @regmap: Register map of the device * @fields: Register fields of the device @@ -68,7 +67,6 @@ static const struct reg_field afe4403_reg_fields[] = { * @buffer: Used to construct data layout to push into IIO buffer. */ struct afe4403_data { - struct device *dev; struct spi_device *spi; struct regmap *regmap; struct regmap_field *fields[F_MAX_FIELDS]; @@ -460,63 +458,63 @@ static DEFINE_SIMPLE_DEV_PM_OPS(afe4403_pm_ops, afe4403_suspend, static int afe4403_probe(struct spi_device *spi) { + struct device *dev = &spi->dev; struct iio_dev *indio_dev; struct afe4403_data *afe; int i, ret; - indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*afe)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*afe)); if (!indio_dev) return -ENOMEM; afe = iio_priv(indio_dev); spi_set_drvdata(spi, indio_dev); - afe->dev = &spi->dev; afe->spi = spi; afe->irq = spi->irq; afe->regmap = devm_regmap_init_spi(spi, &afe4403_regmap_config); if (IS_ERR(afe->regmap)) { - dev_err(afe->dev, "Unable to allocate register map\n"); + dev_err(dev, "Unable to allocate register map\n"); return PTR_ERR(afe->regmap); } for (i = 0; i < F_MAX_FIELDS; i++) { - afe->fields[i] = devm_regmap_field_alloc(afe->dev, afe->regmap, + afe->fields[i] = devm_regmap_field_alloc(dev, afe->regmap, afe4403_reg_fields[i]); if (IS_ERR(afe->fields[i])) { - dev_err(afe->dev, "Unable to allocate regmap fields\n"); + dev_err(dev, "Unable to allocate regmap fields\n"); return PTR_ERR(afe->fields[i]); } } - afe->regulator = devm_regulator_get(afe->dev, "tx_sup"); + afe->regulator = devm_regulator_get(dev, "tx_sup"); if (IS_ERR(afe->regulator)) - return dev_err_probe(afe->dev, PTR_ERR(afe->regulator), + return dev_err_probe(dev, PTR_ERR(afe->regulator), "Unable to get regulator\n"); ret = regulator_enable(afe->regulator); if (ret) { - dev_err(afe->dev, "Unable to enable regulator\n"); + dev_err(dev, "Unable to enable regulator\n"); return ret; } - ret = devm_add_action_or_reset(afe->dev, afe4403_regulator_disable, afe->regulator); + ret = devm_add_action_or_reset(dev, afe4403_regulator_disable, afe->regulator); if (ret) { - dev_err(afe->dev, "Unable to add regulator disable action\n"); + dev_err(dev, "Unable to add regulator disable action\n"); return ret; } ret = regmap_write(afe->regmap, AFE440X_CONTROL0, AFE440X_CONTROL0_SW_RESET); if (ret) { - dev_err(afe->dev, "Unable to reset device\n"); + dev_err(dev, "Unable to reset device\n"); return ret; } ret = regmap_multi_reg_write(afe->regmap, afe4403_reg_sequences, ARRAY_SIZE(afe4403_reg_sequences)); if (ret) { - dev_err(afe->dev, "Unable to set register defaults\n"); + dev_err(dev, "Unable to set register defaults\n"); return ret; } @@ -527,45 +525,43 @@ static int afe4403_probe(struct spi_device *spi) indio_dev->info = &afe4403_iio_info; if (afe->irq > 0) { - afe->trig = devm_iio_trigger_alloc(afe->dev, + afe->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); - if (!afe->trig) { - dev_err(afe->dev, "Unable to allocate IIO trigger\n"); + if (!afe->trig) return -ENOMEM; - } iio_trigger_set_drvdata(afe->trig, indio_dev); - ret = devm_iio_trigger_register(afe->dev, afe->trig); + ret = devm_iio_trigger_register(dev, afe->trig); if (ret) { - dev_err(afe->dev, "Unable to register IIO trigger\n"); + dev_err(dev, "Unable to register IIO trigger\n"); return ret; } - ret = devm_request_threaded_irq(afe->dev, afe->irq, + ret = devm_request_threaded_irq(dev, afe->irq, iio_trigger_generic_data_rdy_poll, NULL, IRQF_ONESHOT, AFE4403_DRIVER_NAME, afe->trig); if (ret) { - dev_err(afe->dev, "Unable to request IRQ\n"); + dev_err(dev, "Unable to request IRQ\n"); return ret; } } - ret = devm_iio_triggered_buffer_setup(afe->dev, indio_dev, + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, &iio_pollfunc_store_time, afe4403_trigger_handler, NULL); if (ret) { - dev_err(afe->dev, "Unable to setup buffer\n"); + dev_err(dev, "Unable to setup buffer\n"); return ret; } - ret = devm_iio_device_register(afe->dev, indio_dev); + ret = devm_iio_device_register(dev, indio_dev); if (ret) { - dev_err(afe->dev, "Unable to register IIO device\n"); + dev_err(dev, "Unable to register IIO device\n"); return ret; } diff --git a/drivers/iio/health/afe4404.c b/drivers/iio/health/afe4404.c index b2727effecaa..768d794e574b 100644 --- a/drivers/iio/health/afe4404.c +++ b/drivers/iio/health/afe4404.c @@ -77,7 +77,6 @@ static const struct reg_field afe4404_reg_fields[] = { /** * struct afe4404_data - AFE4404 device instance data - * @dev: Device structure * @regmap: Register map of the device * @fields: Register fields of the device * @regulator: Pointer to the regulator for the IC @@ -86,7 +85,6 @@ static const struct reg_field afe4404_reg_fields[] = { * @buffer: Used to construct a scan to push to the iio buffer. */ struct afe4404_data { - struct device *dev; struct regmap *regmap; struct regmap_field *fields[F_MAX_FIELDS]; struct regulator *regulator; @@ -468,62 +466,62 @@ static DEFINE_SIMPLE_DEV_PM_OPS(afe4404_pm_ops, afe4404_suspend, static int afe4404_probe(struct i2c_client *client) { + struct device *dev = &client->dev; struct iio_dev *indio_dev; struct afe4404_data *afe; int i, ret; - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*afe)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*afe)); if (!indio_dev) return -ENOMEM; afe = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); - afe->dev = &client->dev; afe->irq = client->irq; afe->regmap = devm_regmap_init_i2c(client, &afe4404_regmap_config); if (IS_ERR(afe->regmap)) { - dev_err(afe->dev, "Unable to allocate register map\n"); + dev_err(dev, "Unable to allocate register map\n"); return PTR_ERR(afe->regmap); } for (i = 0; i < F_MAX_FIELDS; i++) { - afe->fields[i] = devm_regmap_field_alloc(afe->dev, afe->regmap, + afe->fields[i] = devm_regmap_field_alloc(dev, afe->regmap, afe4404_reg_fields[i]); if (IS_ERR(afe->fields[i])) { - dev_err(afe->dev, "Unable to allocate regmap fields\n"); + dev_err(dev, "Unable to allocate regmap fields\n"); return PTR_ERR(afe->fields[i]); } } - afe->regulator = devm_regulator_get(afe->dev, "tx_sup"); + afe->regulator = devm_regulator_get(dev, "tx_sup"); if (IS_ERR(afe->regulator)) - return dev_err_probe(afe->dev, PTR_ERR(afe->regulator), + return dev_err_probe(dev, PTR_ERR(afe->regulator), "Unable to get regulator\n"); ret = regulator_enable(afe->regulator); if (ret) { - dev_err(afe->dev, "Unable to enable regulator\n"); + dev_err(dev, "Unable to enable regulator\n"); return ret; } - ret = devm_add_action_or_reset(afe->dev, afe4404_regulator_disable, afe->regulator); + ret = devm_add_action_or_reset(dev, afe4404_regulator_disable, afe->regulator); if (ret) { - dev_err(afe->dev, "Unable to enable regulator\n"); + dev_err(dev, "Unable to enable regulator\n"); return ret; } ret = regmap_write(afe->regmap, AFE440X_CONTROL0, AFE440X_CONTROL0_SW_RESET); if (ret) { - dev_err(afe->dev, "Unable to reset device\n"); + dev_err(dev, "Unable to reset device\n"); return ret; } ret = regmap_multi_reg_write(afe->regmap, afe4404_reg_sequences, ARRAY_SIZE(afe4404_reg_sequences)); if (ret) { - dev_err(afe->dev, "Unable to set register defaults\n"); + dev_err(dev, "Unable to set register defaults\n"); return ret; } @@ -534,45 +532,43 @@ static int afe4404_probe(struct i2c_client *client) indio_dev->info = &afe4404_iio_info; if (afe->irq > 0) { - afe->trig = devm_iio_trigger_alloc(afe->dev, + afe->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); - if (!afe->trig) { - dev_err(afe->dev, "Unable to allocate IIO trigger\n"); + if (!afe->trig) return -ENOMEM; - } iio_trigger_set_drvdata(afe->trig, indio_dev); - ret = devm_iio_trigger_register(afe->dev, afe->trig); + ret = devm_iio_trigger_register(dev, afe->trig); if (ret) { - dev_err(afe->dev, "Unable to register IIO trigger\n"); + dev_err(dev, "Unable to register IIO trigger\n"); return ret; } - ret = devm_request_threaded_irq(afe->dev, afe->irq, + ret = devm_request_threaded_irq(dev, afe->irq, iio_trigger_generic_data_rdy_poll, NULL, IRQF_ONESHOT, AFE4404_DRIVER_NAME, afe->trig); if (ret) { - dev_err(afe->dev, "Unable to request IRQ\n"); + dev_err(dev, "Unable to request IRQ\n"); return ret; } } - ret = devm_iio_triggered_buffer_setup(afe->dev, indio_dev, + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, &iio_pollfunc_store_time, afe4404_trigger_handler, NULL); if (ret) { - dev_err(afe->dev, "Unable to setup buffer\n"); + dev_err(dev, "Unable to setup buffer\n"); return ret; } - ret = devm_iio_device_register(afe->dev, indio_dev); + ret = devm_iio_device_register(dev, indio_dev); if (ret) { - dev_err(afe->dev, "Unable to register IIO device\n"); + dev_err(dev, "Unable to register IIO device\n"); return ret; } diff --git a/drivers/iio/humidity/am2315.c b/drivers/iio/humidity/am2315.c index f021c3e6d886..02ca23eb8991 100644 --- a/drivers/iio/humidity/am2315.c +++ b/drivers/iio/humidity/am2315.c @@ -224,10 +224,8 @@ static int am2315_probe(struct i2c_client *client) struct am2315_data *data; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/humidity/dht11.c b/drivers/iio/humidity/dht11.c index 73d2033954e7..980cb946bbf7 100644 --- a/drivers/iio/humidity/dht11.c +++ b/drivers/iio/humidity/dht11.c @@ -294,10 +294,8 @@ static int dht11_probe(struct platform_device *pdev) struct iio_dev *iio; iio = devm_iio_device_alloc(dev, sizeof(*dht11)); - if (!iio) { - dev_err(dev, "Failed to allocate IIO device\n"); + if (!iio) return -ENOMEM; - } dht11 = iio_priv(iio); dht11->dev = dev; diff --git a/drivers/iio/imu/adis16475.c b/drivers/iio/imu/adis16475.c index 924395b7e3b4..ab39bea1e729 100644 --- a/drivers/iio/imu/adis16475.c +++ b/drivers/iio/imu/adis16475.c @@ -1930,7 +1930,6 @@ static int adis16475_config_irq_pin(struct adis16475 *st) return 0; } - static int adis16475_probe(struct spi_device *spi) { struct iio_dev *indio_dev; diff --git a/drivers/iio/imu/bmi270/bmi270_i2c.c b/drivers/iio/imu/bmi270/bmi270_i2c.c index c77839b03a96..b909a421ad01 100644 --- a/drivers/iio/imu/bmi270/bmi270_i2c.c +++ b/drivers/iio/imu/bmi270/bmi270_i2c.c @@ -41,6 +41,8 @@ static const struct i2c_device_id bmi270_i2c_id[] = { static const struct acpi_device_id bmi270_acpi_match[] = { /* GPD Win Mini, Aya Neo AIR Pro, OXP Mini Pro, etc. */ { "BMI0160", (kernel_ulong_t)&bmi260_chip_info }, + /* GPD Win Max 2 2023(sincice BIOS v0.40), etc. */ + { "BMI0260", (kernel_ulong_t)&bmi260_chip_info }, { } }; diff --git a/drivers/iio/imu/bmi323/bmi323_core.c b/drivers/iio/imu/bmi323/bmi323_core.c index fc54d464a3ae..6bcb9a436581 100644 --- a/drivers/iio/imu/bmi323/bmi323_core.c +++ b/drivers/iio/imu/bmi323/bmi323_core.c @@ -2112,8 +2112,7 @@ int bmi323_core_probe(struct device *dev) indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "Failed to allocate device\n"); + return -ENOMEM; ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), regulator_names); diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h index 1430ab4f1dea..c8b48a5c5ed0 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h @@ -167,7 +167,6 @@ struct inv_icm42600_state { enum inv_icm42600_chip chip; const char *name; struct regmap *map; - struct regulator *vdd_supply; struct regulator *vddio_supply; int irq; struct iio_mount_matrix orientation; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c index 7a28051330b7..54760d8f92a2 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c @@ -315,7 +315,6 @@ static int inv_icm42600_accel_read_sensor(struct iio_dev *indio_dev, ret = -EINVAL; exit: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } @@ -562,12 +561,10 @@ static int inv_icm42600_accel_write_scale(struct iio_dev *indio_dev, conf.fs = idx / 2; pm_runtime_get_sync(dev); - mutex_lock(&st->lock); - ret = inv_icm42600_set_accel_conf(st, &conf, NULL); + scoped_guard(mutex, &st->lock) + ret = inv_icm42600_set_accel_conf(st, &conf, NULL); - mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -675,7 +672,6 @@ static int inv_icm42600_accel_write_odr(struct iio_dev *indio_dev, out_unlock: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -727,7 +723,6 @@ static int inv_icm42600_accel_read_offset(struct inv_icm42600_state *st, memcpy(data, st->buffer, sizeof(data)); mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); if (ret) return ret; @@ -865,7 +860,6 @@ static int inv_icm42600_accel_write_offset(struct inv_icm42600_state *st, out_unlock: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } @@ -991,16 +985,11 @@ static int inv_icm42600_accel_hwfifo_set_watermark(struct iio_dev *indio_dev, unsigned int val) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); - int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); st->fifo.watermark.accel = val; - ret = inv_icm42600_buffer_update_watermark(st); - - mutex_unlock(&st->lock); - - return ret; + return inv_icm42600_buffer_update_watermark(st); } static int inv_icm42600_accel_hwfifo_flush(struct iio_dev *indio_dev, @@ -1012,15 +1001,13 @@ static int inv_icm42600_accel_hwfifo_flush(struct iio_dev *indio_dev, if (count == 0) return 0; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); ret = inv_icm42600_buffer_hwfifo_flush(st, count); - if (!ret) - ret = st->fifo.nb.accel; - - mutex_unlock(&st->lock); + if (ret) + return ret; - return ret; + return st->fifo.nb.accel; } static int inv_icm42600_accel_read_event_config(struct iio_dev *indio_dev, diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c index 7c4ed981db04..ada968be954d 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/kernel.h> #include <linux/device.h> +#include <linux/minmax.h> #include <linux/mutex.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> @@ -100,7 +101,7 @@ ssize_t inv_icm42600_fifo_decode_packet(const void *packet, const void **accel, void inv_icm42600_buffer_update_fifo_period(struct inv_icm42600_state *st) { - u32 period_gyro, period_accel, period; + u32 period_gyro, period_accel; if (st->fifo.en & INV_ICM42600_SENSOR_GYRO) period_gyro = inv_icm42600_odr_to_period(st->conf.gyro.odr); @@ -112,12 +113,7 @@ void inv_icm42600_buffer_update_fifo_period(struct inv_icm42600_state *st) else period_accel = U32_MAX; - if (period_gyro <= period_accel) - period = period_gyro; - else - period = period_accel; - - st->fifo.period = period; + st->fifo.period = min(period_gyro, period_accel); } int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st, @@ -204,7 +200,7 @@ 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, period; + u32 period_gyro, period_accel; u32 latency_gyro, latency_accel, latency; bool restore; __le16 raw_wm; @@ -237,13 +233,8 @@ int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st) latency = latency_gyro - (latency_accel % latency_gyro); else latency = latency_accel - (latency_gyro % latency_accel); - /* use the shortest period */ - if (period_gyro <= period_accel) - period = period_gyro; - else - period = period_accel; /* all this works because periods are multiple of each others */ - watermark = latency / period; + watermark = latency / min(period_gyro, period_accel); if (watermark < 1) watermark = 1; /* update effective watermark */ @@ -292,9 +283,8 @@ static int inv_icm42600_buffer_preenable(struct iio_dev *indio_dev) pm_runtime_get_sync(dev); - mutex_lock(&st->lock); + guard(mutex)(&st->lock); inv_sensors_timestamp_reset(ts); - mutex_unlock(&st->lock); return 0; } @@ -308,43 +298,39 @@ static int inv_icm42600_buffer_postenable(struct iio_dev *indio_dev) struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); - /* exit if FIFO is already on */ if (st->fifo.on) { - ret = 0; - goto out_on; + st->fifo.on++; + return 0; } /* set FIFO threshold interrupt */ ret = regmap_set_bits(st->map, INV_ICM42600_REG_INT_SOURCE0, INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN); if (ret) - goto out_unlock; + return ret; /* flush FIFO data */ ret = regmap_write(st->map, INV_ICM42600_REG_SIGNAL_PATH_RESET, INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH); if (ret) - goto out_unlock; + return ret; /* set FIFO in streaming mode */ ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, INV_ICM42600_FIFO_CONFIG_STREAM); if (ret) - goto out_unlock; + return ret; /* workaround: first read of FIFO count after reset is always 0 */ ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT, st->buffer, 2); if (ret) - goto out_unlock; + return ret; -out_on: - /* increase FIFO on counter */ st->fifo.on++; -out_unlock: - mutex_unlock(&st->lock); - return ret; + + return 0; } static int inv_icm42600_buffer_predisable(struct iio_dev *indio_dev) @@ -352,38 +338,34 @@ static int inv_icm42600_buffer_predisable(struct iio_dev *indio_dev) struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); - /* exit if there are several sensors using the FIFO */ if (st->fifo.on > 1) { - ret = 0; - goto out_off; + st->fifo.on--; + return 0; } /* set FIFO in bypass mode */ ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, INV_ICM42600_FIFO_CONFIG_BYPASS); if (ret) - goto out_unlock; + return ret; /* flush FIFO data */ ret = regmap_write(st->map, INV_ICM42600_REG_SIGNAL_PATH_RESET, INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH); if (ret) - goto out_unlock; + return ret; /* disable FIFO threshold interrupt */ ret = regmap_clear_bits(st->map, INV_ICM42600_REG_INT_SOURCE0, INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN); if (ret) - goto out_unlock; + return ret; -out_off: - /* decrease FIFO on counter */ st->fifo.on--; -out_unlock: - mutex_unlock(&st->lock); - return ret; + + return 0; } static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev) @@ -439,7 +421,6 @@ out_unlock: if (sleep) msleep(sleep); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c index a4d42e7e2180..76eb22488e5f 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c @@ -439,18 +439,13 @@ int inv_icm42600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); - int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); if (readval) - ret = regmap_read(st->map, reg, readval); - else - ret = regmap_write(st->map, reg, writeval); + return regmap_read(st->map, reg, readval); - mutex_unlock(&st->lock); - - return ret; + return regmap_write(st->map, reg, writeval); } static int inv_icm42600_set_conf(struct inv_icm42600_state *st, @@ -697,34 +692,15 @@ static int inv_icm42600_enable_regulator_vddio(struct inv_icm42600_state *st) return 0; } -static void inv_icm42600_disable_vdd_reg(void *_data) -{ - struct inv_icm42600_state *st = _data; - const struct device *dev = regmap_get_device(st->map); - int ret; - - ret = regulator_disable(st->vdd_supply); - if (ret) - dev_err(dev, "failed to disable vdd error %d\n", ret); -} - static void inv_icm42600_disable_vddio_reg(void *_data) { struct inv_icm42600_state *st = _data; - const struct device *dev = regmap_get_device(st->map); - int ret; - - ret = regulator_disable(st->vddio_supply); - if (ret) - dev_err(dev, "failed to disable vddio error %d\n", ret); -} + struct device *dev = regmap_get_device(st->map); -static void inv_icm42600_disable_pm(void *_data) -{ - struct device *dev = _data; + if (pm_runtime_status_suspended(dev)) + return; - pm_runtime_put_sync(dev); - pm_runtime_disable(dev); + regulator_disable(st->vddio_supply); } int inv_icm42600_core_probe(struct regmap *regmap, int chip, @@ -773,23 +749,17 @@ int inv_icm42600_core_probe(struct regmap *regmap, int chip, return ret; } - st->vdd_supply = devm_regulator_get(dev, "vdd"); - if (IS_ERR(st->vdd_supply)) - return PTR_ERR(st->vdd_supply); + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get vdd regulator\n"); + + msleep(INV_ICM42600_POWER_UP_TIME_MS); st->vddio_supply = devm_regulator_get(dev, "vddio"); if (IS_ERR(st->vddio_supply)) return PTR_ERR(st->vddio_supply); - ret = regulator_enable(st->vdd_supply); - if (ret) - return ret; - msleep(INV_ICM42600_POWER_UP_TIME_MS); - - ret = devm_add_action_or_reset(dev, inv_icm42600_disable_vdd_reg, st); - if (ret) - return ret; - ret = inv_icm42600_enable_regulator_vddio(st); if (ret) return ret; @@ -824,16 +794,14 @@ int inv_icm42600_core_probe(struct regmap *regmap, int chip, return ret; /* setup runtime power management */ - ret = pm_runtime_set_active(dev); + ret = devm_pm_runtime_set_active_enabled(dev); if (ret) return ret; - pm_runtime_get_noresume(dev); - pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, INV_ICM42600_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); - pm_runtime_put(dev); - return devm_add_action_or_reset(dev, inv_icm42600_disable_pm, dev); + return ret; } EXPORT_SYMBOL_NS_GPL(inv_icm42600_core_probe, "IIO_ICM42600"); @@ -849,22 +817,20 @@ static int inv_icm42600_suspend(struct device *dev) int accel_conf; int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); st->suspended.gyro = st->conf.gyro.mode; st->suspended.accel = st->conf.accel.mode; st->suspended.temp = st->conf.temp_en; - if (pm_runtime_suspended(dev)) { - ret = 0; - goto out_unlock; - } + if (pm_runtime_suspended(dev)) + return 0; /* disable FIFO data streaming */ if (st->fifo.on) { ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, INV_ICM42600_FIFO_CONFIG_BYPASS); if (ret) - goto out_unlock; + return ret; } /* keep chip on and wake-up capable if APEX and wakeup on */ @@ -880,7 +846,7 @@ static int inv_icm42600_suspend(struct device *dev) if (st->apex.wom.enable) { ret = inv_icm42600_disable_wom(st); if (ret) - goto out_unlock; + return ret; } accel_conf = INV_ICM42600_SENSOR_MODE_OFF; } @@ -888,15 +854,13 @@ static int inv_icm42600_suspend(struct device *dev) ret = inv_icm42600_set_pwr_mgmt0(st, INV_ICM42600_SENSOR_MODE_OFF, accel_conf, false, NULL); if (ret) - goto out_unlock; + return ret; /* disable vddio regulator if chip is sleeping */ if (!wakeup) regulator_disable(st->vddio_supply); -out_unlock: - mutex_unlock(&st->lock); - return ret; + return 0; } /* @@ -912,7 +876,10 @@ static int inv_icm42600_resume(struct device *dev) bool wakeup; int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); + + if (pm_runtime_suspended(dev)) + return 0; /* check wakeup capability */ accel_dev = &st->indio_accel->dev; @@ -924,25 +891,21 @@ static int inv_icm42600_resume(struct device *dev) } else { ret = inv_icm42600_enable_regulator_vddio(st); if (ret) - goto out_unlock; + return ret; } - pm_runtime_disable(dev); - pm_runtime_set_active(dev); - pm_runtime_enable(dev); - /* restore sensors state */ ret = inv_icm42600_set_pwr_mgmt0(st, st->suspended.gyro, st->suspended.accel, st->suspended.temp, NULL); if (ret) - goto out_unlock; + return ret; /* restore APEX features if disabled */ if (!wakeup && st->apex.wom.enable) { ret = inv_icm42600_enable_wom(st); if (ret) - goto out_unlock; + return ret; } /* restore FIFO data streaming */ @@ -953,9 +916,7 @@ static int inv_icm42600_resume(struct device *dev) INV_ICM42600_FIFO_CONFIG_STREAM); } -out_unlock: - mutex_unlock(&st->lock); - return ret; + return 0; } /* Runtime suspend will turn off sensors that are enabled by iio devices. */ @@ -964,34 +925,28 @@ static int inv_icm42600_runtime_suspend(struct device *dev) struct inv_icm42600_state *st = dev_get_drvdata(dev); int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); /* disable all sensors */ ret = inv_icm42600_set_pwr_mgmt0(st, INV_ICM42600_SENSOR_MODE_OFF, INV_ICM42600_SENSOR_MODE_OFF, false, NULL); if (ret) - goto error_unlock; + return ret; regulator_disable(st->vddio_supply); -error_unlock: - mutex_unlock(&st->lock); - return ret; + return 0; } /* Sensors are enabled by iio devices, no need to turn them back on here. */ static int inv_icm42600_runtime_resume(struct device *dev) { struct inv_icm42600_state *st = dev_get_drvdata(dev); - int ret; - - mutex_lock(&st->lock); - ret = inv_icm42600_enable_regulator_vddio(st); + guard(mutex)(&st->lock); - mutex_unlock(&st->lock); - return ret; + return inv_icm42600_enable_regulator_vddio(st); } EXPORT_NS_GPL_DEV_PM_OPS(inv_icm42600_pm_ops, IIO_ICM42600) = { diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c index 9ba6f13628e6..7ef0a25ec74f 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c @@ -184,7 +184,6 @@ static int inv_icm42600_gyro_read_sensor(struct inv_icm42600_state *st, ret = -EINVAL; exit: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } @@ -278,12 +277,10 @@ static int inv_icm42600_gyro_write_scale(struct iio_dev *indio_dev, conf.fs = idx / 2; pm_runtime_get_sync(dev); - mutex_lock(&st->lock); - ret = inv_icm42600_set_gyro_conf(st, &conf, NULL); + scoped_guard(mutex, &st->lock) + ret = inv_icm42600_set_gyro_conf(st, &conf, NULL); - mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -378,7 +375,6 @@ static int inv_icm42600_gyro_write_odr(struct iio_dev *indio_dev, out_unlock: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -430,7 +426,6 @@ static int inv_icm42600_gyro_read_offset(struct inv_icm42600_state *st, memcpy(data, st->buffer, sizeof(data)); mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); if (ret) return ret; @@ -567,7 +562,6 @@ static int inv_icm42600_gyro_write_offset(struct inv_icm42600_state *st, out_unlock: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } @@ -693,16 +687,11 @@ static int inv_icm42600_gyro_hwfifo_set_watermark(struct iio_dev *indio_dev, unsigned int val) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); - int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); st->fifo.watermark.gyro = val; - ret = inv_icm42600_buffer_update_watermark(st); - - mutex_unlock(&st->lock); - - return ret; + return inv_icm42600_buffer_update_watermark(st); } static int inv_icm42600_gyro_hwfifo_flush(struct iio_dev *indio_dev, @@ -714,15 +703,13 @@ static int inv_icm42600_gyro_hwfifo_flush(struct iio_dev *indio_dev, if (count == 0) return 0; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); ret = inv_icm42600_buffer_hwfifo_flush(st, count); - if (!ret) - ret = st->fifo.nb.gyro; - - mutex_unlock(&st->lock); + if (ret) + return ret; - return ret; + return st->fifo.nb.gyro; } static const struct iio_info inv_icm42600_gyro_info = { diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c index 271a4788604a..30f6a9595eea 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c @@ -41,7 +41,6 @@ static int inv_icm42600_temp_read(struct inv_icm42600_state *st, s16 *temp) exit: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index 39eb516acc73..b2fa1f4957a5 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -735,7 +735,6 @@ static int inv_mpu6050_read_channel_data(struct iio_dev *indio_dev, break; } - pm_runtime_mark_last_busy(pdev); pm_runtime_put_autosuspend(pdev); return ret; @@ -938,7 +937,6 @@ static int inv_mpu6050_write_raw(struct iio_dev *indio_dev, break; } - pm_runtime_mark_last_busy(pdev); pm_runtime_put_autosuspend(pdev); error_write_raw_unlock: mutex_unlock(&st->lock); @@ -1146,14 +1144,12 @@ static int inv_mpu6050_enable_wom(struct inv_mpu6050_state *st, bool en) st->chip_config.wom_en = false; } - pm_runtime_mark_last_busy(pdev); pm_runtime_put_autosuspend(pdev); } return result; error_suspend: - pm_runtime_mark_last_busy(pdev); pm_runtime_put_autosuspend(pdev); return result; } @@ -1249,7 +1245,6 @@ static int inv_mpu6050_write_event_value(struct iio_dev *indio_dev, value = (u64)val * 1000000ULL + (u64)val2; result = inv_mpu6050_set_wom_threshold(st, value, INV_MPU6050_FREQ_DIVIDER(st)); - pm_runtime_mark_last_busy(pdev); pm_runtime_put_autosuspend(pdev); return result; @@ -1357,7 +1352,6 @@ inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr, if (result) goto fifo_rate_fail_power_off; - pm_runtime_mark_last_busy(pdev); fifo_rate_fail_power_off: pm_runtime_put_autosuspend(pdev); fifo_rate_fail_unlock: diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c index 5b1088cc3704..10a473342075 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c @@ -194,7 +194,6 @@ static int inv_mpu6050_set_enable(struct iio_dev *indio_dev, bool enable) result = inv_mpu6050_prepare_fifo(st, false); if (result) goto error_power_off; - pm_runtime_mark_last_busy(pdev); pm_runtime_put_autosuspend(pdev); } diff --git a/drivers/iio/imu/kmx61.c b/drivers/iio/imu/kmx61.c index 55c82891e08c..3cd91d8a89ee 100644 --- a/drivers/iio/imu/kmx61.c +++ b/drivers/iio/imu/kmx61.c @@ -747,12 +747,10 @@ static int kmx61_set_power_state(struct kmx61_data *data, bool on, u8 device) data->mag_ps = on; } - if (on) { + if (on) ret = pm_runtime_resume_and_get(&data->client->dev); - } else { - pm_runtime_mark_last_busy(&data->client->dev); + else ret = pm_runtime_put_autosuspend(&data->client->dev); - } if (ret < 0) { dev_err(&data->client->dev, "Failed: kmx61_set_power_state for %d, ret %d\n", diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index c65ad49829e7..d8cb4b0218d5 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -2035,10 +2035,10 @@ st_lsm6dsx_sysfs_sampling_frequency_avail(struct device *dev, odr_table = &sensor->hw->settings->odr_table[sensor->id]; for (i = 0; i < odr_table->odr_len; i++) - len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%03d ", - odr_table->odr_avl[i].milli_hz / 1000, - odr_table->odr_avl[i].milli_hz % 1000); - buf[len - 1] = '\n'; + len += sysfs_emit_at(buf, len, "%d.%03d%c", + odr_table->odr_avl[i].milli_hz / 1000, + odr_table->odr_avl[i].milli_hz % 1000, + (i == odr_table->odr_len - 1) ? '\n' : ' '); return len; } @@ -2054,9 +2054,9 @@ static ssize_t st_lsm6dsx_sysfs_scale_avail(struct device *dev, fs_table = &hw->settings->fs_table[sensor->id]; for (i = 0; i < fs_table->fs_len; i++) - len += scnprintf(buf + len, PAGE_SIZE - len, "0.%09u ", - fs_table->fs_avl[i].gain); - buf[len - 1] = '\n'; + len += sysfs_emit_at(buf, len, "0.%09u%c", + fs_table->fs_avl[i].gain, + (i == fs_table->fs_len - 1) ? '\n' : ' '); return len; } diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 159d6c5ca3ce..88c3d585a1bd 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -97,6 +97,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_COLORTEMP] = "colortemp", [IIO_CHROMATICITY] = "chromaticity", [IIO_ATTENTION] = "attention", + [IIO_ALTCURRENT] = "altcurrent", }; static const char * const iio_modifier_names[] = { @@ -152,6 +153,10 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_PITCH] = "pitch", [IIO_MOD_YAW] = "yaw", [IIO_MOD_ROLL] = "roll", + [IIO_MOD_RMS] = "rms", + [IIO_MOD_ACTIVE] = "active", + [IIO_MOD_REACTIVE] = "reactive", + [IIO_MOD_APPARENT] = "apparent", }; /* relies on pairs of these shared then separate */ @@ -189,6 +194,7 @@ static const char * const iio_chan_info_postfix[] = { [IIO_CHAN_INFO_ZEROPOINT] = "zeropoint", [IIO_CHAN_INFO_TROUGH] = "trough_raw", [IIO_CHAN_INFO_CONVDELAY] = "convdelay", + [IIO_CHAN_INFO_POWERFACTOR] = "powerfactor", }; /** * iio_device_id() - query the unique ID for the device @@ -790,6 +796,7 @@ static ssize_t iio_format_list(char *buf, const int *vals, int type, int length, switch (type) { case IIO_VAL_INT: + case IIO_VAL_CHAR: stride = 1; break; default: @@ -1243,7 +1250,7 @@ static int iio_device_add_channel_label(struct iio_dev *indio_dev, 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, - const long *infomask) + const unsigned long *infomask) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); int i, ret, attrcount = 0; @@ -1273,7 +1280,7 @@ static int iio_device_add_info_mask_type(struct iio_dev *indio_dev, static int iio_device_add_info_mask_type_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, enum iio_shared_by shared_by, - const long *infomask) + const unsigned long *infomask) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); int i, ret, attrcount = 0; diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c index c174ebb7d5e6..1e5eb5a41271 100644 --- a/drivers/iio/inkern.c +++ b/drivers/iio/inkern.c @@ -11,6 +11,7 @@ #include <linux/mutex.h> #include <linux/property.h> #include <linux/slab.h> +#include <linux/units.h> #include <linux/iio/iio.h> #include <linux/iio/iio-opaque.h> @@ -598,6 +599,42 @@ int iio_read_channel_average_raw(struct iio_channel *chan, int *val) } EXPORT_SYMBOL_GPL(iio_read_channel_average_raw); +int iio_multiply_value(int *result, s64 multiplier, + unsigned int type, int val, int val2) +{ + s64 denominator; + + switch (type) { + case IIO_VAL_INT: + *result = multiplier * val; + return IIO_VAL_INT; + case IIO_VAL_INT_PLUS_MICRO: + case IIO_VAL_INT_PLUS_NANO: + switch (type) { + case IIO_VAL_INT_PLUS_MICRO: + denominator = MICRO; + break; + case IIO_VAL_INT_PLUS_NANO: + denominator = NANO; + break; + } + *result = multiplier * abs(val); + *result += div_s64(multiplier * abs(val2), denominator); + if (val < 0 || val2 < 0) + *result *= -1; + return IIO_VAL_INT; + case IIO_VAL_FRACTIONAL: + *result = div_s64(multiplier * val, val2); + return IIO_VAL_INT; + case IIO_VAL_FRACTIONAL_LOG2: + *result = (multiplier * val) >> val2; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} +EXPORT_SYMBOL_NS_GPL(iio_multiply_value, "IIO_UNIT_TEST"); + static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan, int raw, int *processed, unsigned int scale) @@ -605,6 +642,7 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan, int scale_type, scale_val, scale_val2; int offset_type, offset_val, offset_val2; s64 raw64 = raw; + int ret; offset_type = iio_channel_read(chan, &offset_val, &offset_val2, IIO_CHAN_INFO_OFFSET); @@ -639,40 +677,14 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan, * If no channel scaling is available apply consumer scale to * raw value and return. */ - *processed = raw * scale; + *processed = raw64 * scale; return 0; } - switch (scale_type) { - case IIO_VAL_INT: - *processed = raw64 * scale_val * scale; - break; - case IIO_VAL_INT_PLUS_MICRO: - if (scale_val2 < 0) - *processed = -raw64 * scale_val * scale; - else - *processed = raw64 * scale_val * scale; - *processed += div_s64(raw64 * (s64)scale_val2 * scale, - 1000000LL); - break; - case IIO_VAL_INT_PLUS_NANO: - if (scale_val2 < 0) - *processed = -raw64 * scale_val * scale; - else - *processed = raw64 * scale_val * scale; - *processed += div_s64(raw64 * (s64)scale_val2 * scale, - 1000000000LL); - break; - case IIO_VAL_FRACTIONAL: - *processed = div_s64(raw64 * (s64)scale_val * scale, - scale_val2); - break; - case IIO_VAL_FRACTIONAL_LOG2: - *processed = (raw64 * (s64)scale_val * scale) >> scale_val2; - break; - default: - return -EINVAL; - } + ret = iio_multiply_value(processed, raw64 * scale, + scale_type, scale_val, scale_val2); + if (ret < 0) + return ret; return 0; } @@ -714,20 +726,19 @@ int iio_read_channel_processed_scale(struct iio_channel *chan, int *val, unsigned int scale) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); - int ret; + int ret, pval, pval2; guard(mutex)(&iio_dev_opaque->info_exist_lock); if (!chan->indio_dev->info) return -ENODEV; if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_PROCESSED)) { - ret = iio_channel_read(chan, val, NULL, + ret = iio_channel_read(chan, &pval, &pval2, IIO_CHAN_INFO_PROCESSED); if (ret < 0) return ret; - *val *= scale; - return ret; + return iio_multiply_value(val, scale, ret, pval, pval2); } else { ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW); if (ret < 0) diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 4a7d983c9cd4..ac1408d374c9 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -724,6 +724,19 @@ config VEML6040 To compile this driver as a module, choose M here: the module will be called veml6040. +config VEML6046X00 + tristate "VEML6046X00 RGBIR color sensor" + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6046X00 + high accuracy RGBIR color sensor. + + To compile this driver as a module, choose M here: the + module will be called veml6046x00. + config VEML6070 tristate "VEML6070 UV A light sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 8229ebe6edc4..c0048e0d5ca8 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_VCNL4035) += vcnl4035.o obj-$(CONFIG_VEML3235) += veml3235.o obj-$(CONFIG_VEML6030) += veml6030.o obj-$(CONFIG_VEML6040) += veml6040.o +obj-$(CONFIG_VEML6046X00) += veml6046x00.o obj-$(CONFIG_VEML6070) += veml6070.o obj-$(CONFIG_VEML6075) += veml6075.o obj-$(CONFIG_VL6180) += vl6180.o diff --git a/drivers/iio/light/acpi-als.c b/drivers/iio/light/acpi-als.c index 032e6cae8b80..d5d1a8b9c035 100644 --- a/drivers/iio/light/acpi-als.c +++ b/drivers/iio/light/acpi-als.c @@ -49,20 +49,10 @@ static const struct iio_chan_spec acpi_als_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(1), }; -/* - * The event buffer contains timestamp and all the data from - * the ACPI0008 block. There are multiple, but so far we only - * support _ALI (illuminance): One channel, padding and timestamp. - */ -#define ACPI_ALS_EVT_BUFFER_SIZE \ - (sizeof(s32) + sizeof(s32) + sizeof(s64)) - struct acpi_als { struct acpi_device *device; struct mutex lock; struct iio_trigger *trig; - - s32 evt_buffer[ACPI_ALS_EVT_BUFFER_SIZE / sizeof(s32)] __aligned(8); }; /* @@ -152,7 +142,10 @@ static irqreturn_t acpi_als_trigger_handler(int irq, void *p) struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct acpi_als *als = iio_priv(indio_dev); - s32 *buffer = als->evt_buffer; + struct { + s32 light; + aligned_s64 ts; + } scan = { }; s32 val; int ret; @@ -161,7 +154,7 @@ static irqreturn_t acpi_als_trigger_handler(int irq, void *p) ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &val); if (ret < 0) goto out; - *buffer = val; + scan.light = val; /* * When coming from own trigger via polls, set polling function @@ -174,7 +167,7 @@ static irqreturn_t acpi_als_trigger_handler(int irq, void *p) if (!pf->timestamp) pf->timestamp = iio_get_time_ns(indio_dev); - iio_push_to_buffers_with_timestamp(indio_dev, buffer, pf->timestamp); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); out: mutex_unlock(&als->lock); iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/light/adjd_s311.c b/drivers/iio/light/adjd_s311.c index cf96e3dd8bc6..edb3d9dc8bed 100644 --- a/drivers/iio/light/adjd_s311.c +++ b/drivers/iio/light/adjd_s311.c @@ -54,10 +54,6 @@ struct adjd_s311_data { struct i2c_client *client; - struct { - s16 chans[4]; - aligned_s64 ts; - } scan; }; enum adjd_s311_channel_idx { @@ -120,6 +116,10 @@ static irqreturn_t adjd_s311_trigger_handler(int irq, void *p) struct adjd_s311_data *data = iio_priv(indio_dev); s64 time_ns = iio_get_time_ns(indio_dev); int i, j = 0; + struct { + s16 chans[4]; + aligned_s64 ts; + } scan = { }; int ret = adjd_s311_req_data(indio_dev); if (ret < 0) @@ -131,10 +131,10 @@ static irqreturn_t adjd_s311_trigger_handler(int irq, void *p) if (ret < 0) goto done; - data->scan.chans[j++] = ret & ADJD_S311_DATA_MASK; + scan.chans[j++] = ret & ADJD_S311_DATA_MASK; } - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, time_ns); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns); done: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/light/al3000a.c b/drivers/iio/light/al3000a.c index 6f301c067045..9871096cbab3 100644 --- a/drivers/iio/light/al3000a.c +++ b/drivers/iio/light/al3000a.c @@ -94,7 +94,7 @@ static int al3000a_init(struct al3000a_data *data) ret = devm_add_action_or_reset(dev, al3000a_set_pwr_off, data); if (ret) - return dev_err_probe(dev, ret, "failed to add action\n"); + return ret; ret = regmap_write(data->regmap, AL3000A_REG_SYSTEM, AL3000A_CONFIG_RESET); if (ret) diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c index f676da245aa7..389125675caa 100644 --- a/drivers/iio/light/apds9306.c +++ b/drivers/iio/light/apds9306.c @@ -537,7 +537,6 @@ static int apds9306_read_data(struct apds9306_data *data, int *val, int reg) *val = get_unaligned_le24(&buff); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return 0; @@ -1121,7 +1120,6 @@ static int apds9306_write_event_config(struct iio_dev *indio_dev, if (ret) return ret; - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return 0; @@ -1309,7 +1307,7 @@ static int apds9306_probe(struct i2c_client *client) ret = devm_add_action_or_reset(dev, apds9306_powerdown, data); if (ret) - return dev_err_probe(dev, ret, "failed to add action or reset\n"); + return ret; ret = devm_iio_device_register(dev, indio_dev); if (ret) diff --git a/drivers/iio/light/apds9960.c b/drivers/iio/light/apds9960.c index b92d0fce5aec..79b202c59a0f 100644 --- a/drivers/iio/light/apds9960.c +++ b/drivers/iio/light/apds9960.c @@ -495,7 +495,6 @@ static int apds9960_set_power_state(struct apds9960_data *data, bool on) usleep_range(data->als_adc_int_us, APDS9960_MAX_INT_TIME_IN_US); } else { - pm_runtime_mark_last_busy(dev); ret = pm_runtime_put_autosuspend(dev); } diff --git a/drivers/iio/light/bh1745.c b/drivers/iio/light/bh1745.c index 4e9bd8f831f7..10b00344bbed 100644 --- a/drivers/iio/light/bh1745.c +++ b/drivers/iio/light/bh1745.c @@ -755,8 +755,8 @@ static irqreturn_t bh1745_trigger_handler(int interrupt, void *p) scan.chans[j++] = value; } - iio_push_to_buffers_with_timestamp(indio_dev, &scan, - iio_get_time_ns(indio_dev)); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); err: iio_trigger_notify_done(indio_dev->trig); @@ -814,8 +814,7 @@ static int bh1745_init(struct bh1745_data *data) ret = devm_add_action_or_reset(dev, bh1745_power_off, data); if (ret) - return dev_err_probe(dev, ret, - "Failed to add action or reset\n"); + return ret; return 0; } diff --git a/drivers/iio/light/bh1780.c b/drivers/iio/light/bh1780.c index c7c877d2fe67..5d3c6d5276ba 100644 --- a/drivers/iio/light/bh1780.c +++ b/drivers/iio/light/bh1780.c @@ -111,7 +111,6 @@ static int bh1780_read_raw(struct iio_dev *indio_dev, value = bh1780_read_word(bh1780, BH1780_REG_DLOW); if (value < 0) return value; - pm_runtime_mark_last_busy(&bh1780->client->dev); pm_runtime_put_autosuspend(&bh1780->client->dev); *val = value; diff --git a/drivers/iio/light/gp2ap002.c b/drivers/iio/light/gp2ap002.c index 42859e5b1089..a0d8a58f2704 100644 --- a/drivers/iio/light/gp2ap002.c +++ b/drivers/iio/light/gp2ap002.c @@ -271,7 +271,6 @@ static int gp2ap002_read_raw(struct iio_dev *indio_dev, } out: - pm_runtime_mark_last_busy(gp2ap002->dev); pm_runtime_put_autosuspend(gp2ap002->dev); return ret; @@ -353,7 +352,6 @@ static int gp2ap002_write_event_config(struct iio_dev *indio_dev, pm_runtime_get_sync(gp2ap002->dev); gp2ap002->enabled = true; } else { - pm_runtime_mark_last_busy(gp2ap002->dev); pm_runtime_put_autosuspend(gp2ap002->dev); gp2ap002->enabled = false; } diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c index 830e5ae7f34a..384572844162 100644 --- a/drivers/iio/light/hid-sensor-als.c +++ b/drivers/iio/light/hid-sensor-als.c @@ -262,8 +262,9 @@ static int als_proc_event(struct hid_sensor_hub_device *hsdev, if (!als_state->timestamp) als_state->timestamp = iio_get_time_ns(indio_dev); - iio_push_to_buffers_with_timestamp(indio_dev, &als_state->scan, - als_state->timestamp); + iio_push_to_buffers_with_ts(indio_dev, &als_state->scan, + sizeof(als_state->scan), + als_state->timestamp); als_state->timestamp = 0; } diff --git a/drivers/iio/light/isl29028.c b/drivers/iio/light/isl29028.c index 0e4284823d44..374bccad9119 100644 --- a/drivers/iio/light/isl29028.c +++ b/drivers/iio/light/isl29028.c @@ -336,16 +336,11 @@ static int isl29028_ir_get(struct isl29028_chip *chip, int *ir_data) static int isl29028_set_pm_runtime_busy(struct isl29028_chip *chip, bool on) { struct device *dev = regmap_get_device(chip->regmap); - int ret; - if (on) { - ret = pm_runtime_resume_and_get(dev); - } else { - pm_runtime_mark_last_busy(dev); - ret = pm_runtime_put_autosuspend(dev); - } + if (on) + return pm_runtime_resume_and_get(dev); - return ret; + return pm_runtime_put_autosuspend(dev); } /* Channel IO */ diff --git a/drivers/iio/light/isl29125.c b/drivers/iio/light/isl29125.c index 6bc23b164cc5..3acb8a4f1d12 100644 --- a/drivers/iio/light/isl29125.c +++ b/drivers/iio/light/isl29125.c @@ -51,11 +51,6 @@ struct isl29125_data { struct i2c_client *client; u8 conf1; - /* Ensure timestamp is naturally aligned */ - struct { - u16 chans[3]; - aligned_s64 timestamp; - } scan; }; #define ISL29125_CHANNEL(_color, _si) { \ @@ -179,6 +174,11 @@ static irqreturn_t isl29125_trigger_handler(int irq, void *p) struct iio_dev *indio_dev = pf->indio_dev; struct isl29125_data *data = iio_priv(indio_dev); int i, j = 0; + /* Ensure timestamp is naturally aligned */ + struct { + u16 chans[3]; + aligned_s64 timestamp; + } scan = { }; iio_for_each_active_channel(indio_dev, i) { int ret = i2c_smbus_read_word_data(data->client, @@ -186,10 +186,10 @@ static irqreturn_t isl29125_trigger_handler(int irq, void *p) if (ret < 0) goto done; - data->scan.chans[j++] = ret; + scan.chans[j++] = ret; } - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), iio_get_time_ns(indio_dev)); done: diff --git a/drivers/iio/light/ltr390.c b/drivers/iio/light/ltr390.c index ee59bbb8aa09..a2b804e9089a 100644 --- a/drivers/iio/light/ltr390.c +++ b/drivers/iio/light/ltr390.c @@ -26,6 +26,7 @@ #include <linux/math.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/iio/iio.h> @@ -38,12 +39,21 @@ #define LTR390_ALS_UVS_GAIN 0x05 #define LTR390_PART_ID 0x06 #define LTR390_MAIN_STATUS 0x07 + #define LTR390_ALS_DATA 0x0D +#define LTR390_ALS_DATA_BYTE(n) (LTR390_ALS_DATA + (n)) + #define LTR390_UVS_DATA 0x10 +#define LTR390_UVS_DATA_BYTE(n) (LTR390_UVS_DATA + (n)) + #define LTR390_INT_CFG 0x19 #define LTR390_INT_PST 0x1A + #define LTR390_THRESH_UP 0x21 +#define LTR390_THRESH_UP_BYTE(n) (LTR390_THRESH_UP + (n)) + #define LTR390_THRESH_LOW 0x24 +#define LTR390_THRESH_LOW_BYTE(n) (LTR390_THRESH_LOW + (n)) #define LTR390_PART_NUMBER_ID 0xb #define LTR390_ALS_UVS_GAIN_MASK GENMASK(2, 0) @@ -96,6 +106,32 @@ struct ltr390_data { enum ltr390_mode mode; int gain; int int_time_us; + bool irq_enabled; +}; + +static const struct regmap_range ltr390_readable_reg_ranges[] = { + regmap_reg_range(LTR390_MAIN_CTRL, LTR390_MAIN_CTRL), + regmap_reg_range(LTR390_ALS_UVS_MEAS_RATE, LTR390_MAIN_STATUS), + regmap_reg_range(LTR390_ALS_DATA_BYTE(0), LTR390_UVS_DATA_BYTE(2)), + regmap_reg_range(LTR390_INT_CFG, LTR390_INT_PST), + regmap_reg_range(LTR390_THRESH_UP_BYTE(0), LTR390_THRESH_LOW_BYTE(2)), +}; + +static const struct regmap_access_table ltr390_readable_reg_table = { + .yes_ranges = ltr390_readable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(ltr390_readable_reg_ranges), +}; + +static const struct regmap_range ltr390_writeable_reg_ranges[] = { + regmap_reg_range(LTR390_MAIN_CTRL, LTR390_MAIN_CTRL), + regmap_reg_range(LTR390_ALS_UVS_MEAS_RATE, LTR390_ALS_UVS_GAIN), + regmap_reg_range(LTR390_INT_CFG, LTR390_INT_PST), + regmap_reg_range(LTR390_THRESH_UP_BYTE(0), LTR390_THRESH_LOW_BYTE(2)), +}; + +static const struct regmap_access_table ltr390_writeable_reg_table = { + .yes_ranges = ltr390_writeable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(ltr390_writeable_reg_ranges), }; static const struct regmap_config ltr390_regmap_config = { @@ -103,6 +139,9 @@ static const struct regmap_config ltr390_regmap_config = { .reg_bits = 8, .reg_stride = 1, .val_bits = 8, + .max_register = LTR390_THRESH_LOW_BYTE(2), + .rd_table = <r390_readable_reg_table, + .wr_table = <r390_writeable_reg_table, }; /* Sampling frequency is in mili Hz and mili Seconds */ @@ -178,9 +217,10 @@ static int ltr390_get_samp_freq_or_period(struct ltr390_data *data, return ltr390_samp_freq_table[value][option]; } -static int ltr390_read_raw(struct iio_dev *iio_device, - struct iio_chan_spec const *chan, int *val, - int *val2, long mask) + +static int ltr390_do_read_raw(struct iio_dev *iio_device, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) { int ret; struct ltr390_data *data = iio_priv(iio_device); @@ -243,6 +283,27 @@ static int ltr390_read_raw(struct iio_dev *iio_device, } } +static int ltr390_read_raw(struct iio_dev *iio_device, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct ltr390_data *data = iio_priv(iio_device); + struct device *dev = &data->client->dev; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "runtime PM failed to resume: %d\n", ret); + return ret; + } + + ret = ltr390_do_read_raw(iio_device, chan, val, val2, mask); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + /* integration time in us */ static const int ltr390_int_time_map_us[] = { 400000, 200000, 100000, 50000, 25000, 12500 }; static const int ltr390_gain_map[] = { 1, 3, 6, 9, 18 }; @@ -549,11 +610,11 @@ static int ltr390_read_event_config(struct iio_dev *indio_dev, return FIELD_GET(LTR390_LS_INT_EN, status); } -static int ltr390_write_event_config(struct iio_dev *indio_dev, - const struct iio_chan_spec *chan, - enum iio_event_type type, - enum iio_event_direction dir, - bool state) +static int ltr390_do_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) { struct ltr390_data *data = iio_priv(indio_dev); int ret; @@ -561,7 +622,6 @@ static int ltr390_write_event_config(struct iio_dev *indio_dev, if (!state) return regmap_clear_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN); - guard(mutex)(&data->lock); ret = regmap_set_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN); if (ret < 0) return ret; @@ -586,6 +646,51 @@ static int ltr390_write_event_config(struct iio_dev *indio_dev, } } +static int ltr390_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + int ret; + struct ltr390_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + + guard(mutex)(&data->lock); + + if (state && !data->irq_enabled) { + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "runtime PM failed to resume: %d\n", ret); + return ret; + } + data->irq_enabled = true; + } + + ret = ltr390_do_event_config(indio_dev, chan, type, dir, state); + + if (!state && data->irq_enabled) { + data->irq_enabled = false; + pm_runtime_put_autosuspend(dev); + } + + return ret; +} + +static int ltr390_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ltr390_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + if (readval) + return regmap_read(data->regmap, reg, readval); + + return regmap_write(data->regmap, reg, writeval); +} + static const struct iio_info ltr390_info = { .read_raw = ltr390_read_raw, .write_raw = ltr390_write_raw, @@ -594,6 +699,7 @@ static const struct iio_info ltr390_info = { .read_event_config = ltr390_read_event_config, .write_event_value = ltr390_write_event_value, .write_event_config = ltr390_write_event_config, + .debugfs_reg_access = ltr390_debugfs_reg_access, }; static irqreturn_t ltr390_interrupt_handler(int irq, void *private) @@ -628,6 +734,43 @@ static irqreturn_t ltr390_interrupt_handler(int irq, void *private) return IRQ_HANDLED; } +static void ltr390_powerdown(void *priv) +{ + struct ltr390_data *data = priv; + struct device *dev = &data->client->dev; + int ret; + + guard(mutex)(&data->lock); + + /* Ensure that power off and interrupts are disabled */ + if (data->irq_enabled) { + ret = regmap_clear_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN); + if (ret < 0) + dev_err(dev, "failed to disable interrupts\n"); + + data->irq_enabled = false; + pm_runtime_put_autosuspend(dev); + } + + ret = regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); + if (ret < 0) + dev_err(dev, "failed to disable sensor\n"); +} + +static int ltr390_pm_init(struct ltr390_data *data) +{ + int ret; + struct device *dev = &data->client->dev; + + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return dev_err_probe(dev, ret, "failed to enable runtime PM\n"); + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + return 0; +} + static int ltr390_probe(struct i2c_client *client) { struct ltr390_data *data; @@ -640,8 +783,9 @@ static int ltr390_probe(struct i2c_client *client) if (!indio_dev) return -ENOMEM; - data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data = iio_priv(indio_dev); data->regmap = devm_regmap_init_i2c(client, <r390_regmap_config); if (IS_ERR(data->regmap)) return dev_err_probe(dev, PTR_ERR(data->regmap), @@ -654,6 +798,8 @@ static int ltr390_probe(struct i2c_client *client) data->gain = 3; /* default mode for ltr390 is ALS mode */ data->mode = LTR390_SET_ALS_MODE; + /* default value of irq_enabled is false */ + data->irq_enabled = false; mutex_init(&data->lock); @@ -681,6 +827,10 @@ static int ltr390_probe(struct i2c_client *client) if (ret) return dev_err_probe(dev, ret, "failed to enable the sensor\n"); + ret = devm_add_action_or_reset(dev, ltr390_powerdown, data); + if (ret) + return dev_err_probe(dev, ret, "failed to add action or reset\n"); + if (client->irq) { ret = devm_request_threaded_irq(dev, client->irq, NULL, ltr390_interrupt_handler, @@ -692,6 +842,10 @@ static int ltr390_probe(struct i2c_client *client) "request irq (%d) failed\n", client->irq); } + ret = ltr390_pm_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize runtime PM\n"); + return devm_iio_device_register(dev, indio_dev); } @@ -713,7 +867,26 @@ static int ltr390_resume(struct device *dev) LTR390_SENSOR_ENABLE); } -static DEFINE_SIMPLE_DEV_PM_OPS(ltr390_pm_ops, ltr390_suspend, ltr390_resume); +static int ltr390_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ltr390_data *data = iio_priv(indio_dev); + + return regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); +} + +static int ltr390_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ltr390_data *data = iio_priv(indio_dev); + + return regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); +} + +static const struct dev_pm_ops ltr390_pm_ops = { + SYSTEM_SLEEP_PM_OPS(ltr390_suspend, ltr390_resume) + RUNTIME_PM_OPS(ltr390_runtime_suspend, ltr390_runtime_resume, NULL) +}; static const struct i2c_device_id ltr390_id[] = { { "ltr390" }, @@ -731,7 +904,7 @@ static struct i2c_driver ltr390_driver = { .driver = { .name = "ltr390", .of_match_table = ltr390_of_table, - .pm = pm_sleep_ptr(<r390_pm_ops), + .pm = pm_ptr(<r390_pm_ops), }, .probe = ltr390_probe, .id_table = ltr390_id, diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c index debf57a52d1c..022e0693983b 100644 --- a/drivers/iio/light/ltr501.c +++ b/drivers/iio/light/ltr501.c @@ -1315,8 +1315,8 @@ static irqreturn_t ltr501_trigger_handler(int irq, void *p) scan.channels[j++] = psdata & LTR501_PS_DATA_MASK; } - iio_push_to_buffers_with_timestamp(indio_dev, &scan, - iio_get_time_ns(indio_dev)); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); done: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/light/ltrf216a.c b/drivers/iio/light/ltrf216a.c index 61f57a82b872..5f27f754fe1c 100644 --- a/drivers/iio/light/ltrf216a.c +++ b/drivers/iio/light/ltrf216a.c @@ -208,7 +208,6 @@ static int ltrf216a_set_power_state(struct ltrf216a_data *data, bool on) return ret; } } else { - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); } diff --git a/drivers/iio/light/max44000.c b/drivers/iio/light/max44000.c index e8b767680133..039d45af3a7f 100644 --- a/drivers/iio/light/max44000.c +++ b/drivers/iio/light/max44000.c @@ -75,11 +75,6 @@ struct max44000_data { struct mutex lock; struct regmap *regmap; - /* Ensure naturally aligned timestamp */ - struct { - u16 channels[2]; - aligned_s64 ts; - } scan; }; /* Default scale is set to the minimum of 0.03125 or 1 / (1 << 5) lux */ @@ -496,24 +491,29 @@ static irqreturn_t max44000_trigger_handler(int irq, void *p) int index = 0; unsigned int regval; int ret; + struct { + u16 channels[2]; + aligned_s64 ts; + } scan = { }; + mutex_lock(&data->lock); if (test_bit(MAX44000_SCAN_INDEX_ALS, indio_dev->active_scan_mask)) { ret = max44000_read_alsval(data); if (ret < 0) goto out_unlock; - data->scan.channels[index++] = ret; + scan.channels[index++] = ret; } if (test_bit(MAX44000_SCAN_INDEX_PRX, indio_dev->active_scan_mask)) { ret = regmap_read(data->regmap, MAX44000_REG_PRX_DATA, ®val); if (ret < 0) goto out_unlock; - data->scan.channels[index] = regval; + scan.channels[index] = regval; } mutex_unlock(&data->lock); - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, - iio_get_time_ns(indio_dev)); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); iio_trigger_notify_done(indio_dev->trig); return IRQ_HANDLED; diff --git a/drivers/iio/light/opt4001.c b/drivers/iio/light/opt4001.c index ba4eb82d9bc2..95167273bb90 100644 --- a/drivers/iio/light/opt4001.c +++ b/drivers/iio/light/opt4001.c @@ -428,8 +428,7 @@ static int opt4001_probe(struct i2c_client *client) opt4001_chip_off_action, chip); if (ret < 0) - return dev_err_probe(&client->dev, ret, - "Failed to setup power off action\n"); + return ret; return devm_iio_device_register(&client->dev, indio_dev); } diff --git a/drivers/iio/light/opt4060.c b/drivers/iio/light/opt4060.c index 566f1bb8fe2a..981c704e7df5 100644 --- a/drivers/iio/light/opt4060.c +++ b/drivers/iio/light/opt4060.c @@ -1104,7 +1104,7 @@ static irqreturn_t opt4060_trigger_handler(int irq, void *p) } } - iio_push_to_buffers_with_timestamp(idev, &raw, pf->timestamp); + iio_push_to_buffers_with_ts(idev, &raw, sizeof(raw), pf->timestamp); err_read: iio_trigger_notify_done(idev->trig); return IRQ_HANDLED; @@ -1212,7 +1212,7 @@ static int opt4060_setup_trigger(struct opt4060_chip *chip, struct iio_dev *idev name = devm_kasprintf(chip->dev, GFP_KERNEL, "%s-opt4060", dev_name(chip->dev)); if (!name) - return dev_err_probe(chip->dev, -ENOMEM, "Failed to alloc chip name\n"); + return -ENOMEM; ret = devm_request_threaded_irq(chip->dev, chip->irq, NULL, opt4060_irq_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, @@ -1299,8 +1299,7 @@ static int opt4060_probe(struct i2c_client *client) ret = devm_add_action_or_reset(dev, opt4060_chip_off_action, chip); if (ret < 0) - return dev_err_probe(dev, ret, - "Failed to setup power off action\n"); + return ret; ret = opt4060_setup_buffer(chip, indio_dev); if (ret) diff --git a/drivers/iio/light/pa12203001.c b/drivers/iio/light/pa12203001.c index 8885852bef22..98a1f1624c75 100644 --- a/drivers/iio/light/pa12203001.c +++ b/drivers/iio/light/pa12203001.c @@ -185,15 +185,10 @@ static int pa12203001_set_power_state(struct pa12203001_data *data, bool on, mutex_unlock(&data->lock); } - if (on) { - ret = pm_runtime_resume_and_get(&data->client->dev); + if (on) + return pm_runtime_resume_and_get(&data->client->dev); - } else { - pm_runtime_mark_last_busy(&data->client->dev); - ret = pm_runtime_put_autosuspend(&data->client->dev); - } - - return ret; + return pm_runtime_put_autosuspend(&data->client->dev); err: mutex_unlock(&data->lock); diff --git a/drivers/iio/light/rohm-bu27034.c b/drivers/iio/light/rohm-bu27034.c index 7cec5e943373..28d111ac8c0a 100644 --- a/drivers/iio/light/rohm-bu27034.c +++ b/drivers/iio/light/rohm-bu27034.c @@ -1193,7 +1193,8 @@ static int bu27034_buffer_thread(void *arg) */ data->scan.mlux = (u32)mlux; } - iio_push_to_buffers_with_timestamp(idev, &data->scan, tstamp); + iio_push_to_buffers_with_ts(idev, &data->scan, + sizeof(data->scan), tstamp); } return 0; diff --git a/drivers/iio/light/rpr0521.c b/drivers/iio/light/rpr0521.c index c50183f07240..9341c1d58cbe 100644 --- a/drivers/iio/light/rpr0521.c +++ b/drivers/iio/light/rpr0521.c @@ -358,12 +358,10 @@ static int rpr0521_set_power_state(struct rpr0521_data *data, bool on, * Note: If either measurement is re-enabled before _suspend(), * both stay enabled until _suspend(). */ - if (on) { + if (on) ret = pm_runtime_resume_and_get(&data->client->dev); - } else { - pm_runtime_mark_last_busy(&data->client->dev); + else ret = pm_runtime_put_autosuspend(&data->client->dev); - } if (ret < 0) { dev_err(&data->client->dev, "Failed: rpr0521_set_power_state for %d, ret %d\n", @@ -457,8 +455,8 @@ static irqreturn_t rpr0521_trigger_consumer_handler(int irq, void *p) data->scan.channels, (3 * 2) + 1); /* 3 * 16-bit + (discarded) int clear reg. */ if (!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); else dev_err(&data->client->dev, "Trigger consumer can't read from sensor.\n"); diff --git a/drivers/iio/light/si1145.c b/drivers/iio/light/si1145.c index 4aa02afd853e..f8eb251eca8d 100644 --- a/drivers/iio/light/si1145.c +++ b/drivers/iio/light/si1145.c @@ -494,8 +494,9 @@ static irqreturn_t si1145_trigger_handler(int irq, void *private) goto done; } - iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, - iio_get_time_ns(indio_dev)); + iio_push_to_buffers_with_ts(indio_dev, data->buffer, + sizeof(data->buffer), + iio_get_time_ns(indio_dev)); done: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/light/st_uvis25.h b/drivers/iio/light/st_uvis25.h index 1f93e3dc45c2..78bc56aad129 100644 --- a/drivers/iio/light/st_uvis25.h +++ b/drivers/iio/light/st_uvis25.h @@ -27,11 +27,6 @@ struct st_uvis25_hw { struct iio_trigger *trig; bool enabled; int irq; - /* Ensure timestamp is naturally aligned */ - struct { - u8 chan; - aligned_s64 ts; - } scan; }; extern const struct dev_pm_ops st_uvis25_pm_ops; diff --git a/drivers/iio/light/st_uvis25_core.c b/drivers/iio/light/st_uvis25_core.c index 124a8f9204a9..bcd729a9924e 100644 --- a/drivers/iio/light/st_uvis25_core.c +++ b/drivers/iio/light/st_uvis25_core.c @@ -234,15 +234,21 @@ static irqreturn_t st_uvis25_buffer_handler_thread(int irq, void *p) struct st_uvis25_hw *hw = iio_priv(iio_dev); unsigned int val; int err; + /* Ensure timestamp is naturally aligned */ + struct { + u8 chan; + aligned_s64 ts; + } scan = { }; + err = regmap_read(hw->regmap, ST_UVIS25_REG_OUT_ADDR, &val); if (err < 0) goto out; - hw->scan.chan = val; + scan.chan = val; - iio_push_to_buffers_with_timestamp(iio_dev, &hw->scan, - iio_get_time_ns(iio_dev)); + iio_push_to_buffers_with_ts(iio_dev, &scan, sizeof(scan), + iio_get_time_ns(iio_dev)); out: iio_trigger_notify_done(hw->trig); diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c index 81dd2bfc22c0..a75a83594a7e 100644 --- a/drivers/iio/light/stk3310.c +++ b/drivers/iio/light/stk3310.c @@ -607,10 +607,8 @@ static int stk3310_probe(struct i2c_client *client) struct stk3310_data *data; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/light/tcs3414.c b/drivers/iio/light/tcs3414.c index 39268f855c77..5be461e6dbdb 100644 --- a/drivers/iio/light/tcs3414.c +++ b/drivers/iio/light/tcs3414.c @@ -53,11 +53,6 @@ struct tcs3414_data { u8 control; u8 gain; u8 timing; - /* Ensure timestamp is naturally aligned */ - struct { - u16 chans[4]; - aligned_s64 timestamp; - } scan; }; #define TCS3414_CHANNEL(_color, _si, _addr) { \ @@ -204,6 +199,12 @@ static irqreturn_t tcs3414_trigger_handler(int irq, void *p) struct iio_dev *indio_dev = pf->indio_dev; struct tcs3414_data *data = iio_priv(indio_dev); int i, j = 0; + /* Ensure timestamp is naturally aligned */ + struct { + u16 chans[4]; + aligned_s64 timestamp; + } scan = { }; + iio_for_each_active_channel(indio_dev, i) { int ret = i2c_smbus_read_word_data(data->client, @@ -211,10 +212,10 @@ static irqreturn_t tcs3414_trigger_handler(int irq, void *p) if (ret < 0) goto done; - data->scan.chans[j++] = ret; + scan.chans[j++] = ret; } - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), iio_get_time_ns(indio_dev)); done: diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index 0f8bf8503edd..12429a3261b3 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -64,11 +64,6 @@ struct tcs3472_data { u8 control; u8 atime; u8 apers; - /* Ensure timestamp is naturally aligned */ - struct { - u16 chans[4]; - aligned_s64 timestamp; - } scan; }; static const struct iio_event_spec tcs3472_events[] = { @@ -377,6 +372,11 @@ static irqreturn_t tcs3472_trigger_handler(int irq, void *p) struct iio_dev *indio_dev = pf->indio_dev; struct tcs3472_data *data = iio_priv(indio_dev); int i, j = 0; + /* Ensure timestamp is naturally aligned */ + struct { + u16 chans[4]; + aligned_s64 timestamp; + } scan = { }; int ret = tcs3472_req_data(data); if (ret < 0) @@ -388,10 +388,10 @@ static irqreturn_t tcs3472_trigger_handler(int irq, void *p) if (ret < 0) goto done; - data->scan.chans[j++] = ret; + scan.chans[j++] = ret; } - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), iio_get_time_ns(indio_dev)); done: diff --git a/drivers/iio/light/tsl2583.c b/drivers/iio/light/tsl2583.c index fc3b0c4226be..8801a491de77 100644 --- a/drivers/iio/light/tsl2583.c +++ b/drivers/iio/light/tsl2583.c @@ -641,16 +641,10 @@ static const struct iio_chan_spec tsl2583_channels[] = { static int tsl2583_set_pm_runtime_busy(struct tsl2583_chip *chip, bool on) { - int ret; + if (on) + return pm_runtime_resume_and_get(&chip->client->dev); - if (on) { - ret = pm_runtime_resume_and_get(&chip->client->dev); - } else { - pm_runtime_mark_last_busy(&chip->client->dev); - ret = pm_runtime_put_autosuspend(&chip->client->dev); - } - - return ret; + return pm_runtime_put_autosuspend(&chip->client->dev); } static int tsl2583_read_raw(struct iio_dev *indio_dev, diff --git a/drivers/iio/light/tsl2591.c b/drivers/iio/light/tsl2591.c index 08476f193a44..c5557867ea43 100644 --- a/drivers/iio/light/tsl2591.c +++ b/drivers/iio/light/tsl2591.c @@ -772,7 +772,6 @@ static int tsl2591_read_raw(struct iio_dev *indio_dev, err_unlock: mutex_unlock(&chip->als_mutex); - pm_runtime_mark_last_busy(&client->dev); pm_runtime_put_autosuspend(&client->dev); return ret; @@ -995,7 +994,6 @@ static int tsl2591_write_event_config(struct iio_dev *indio_dev, pm_runtime_get_sync(&client->dev); } else if (!state && chip->events_enabled) { chip->events_enabled = false; - pm_runtime_mark_last_busy(&client->dev); pm_runtime_put_autosuspend(&client->dev); } diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c index 61a0957317a1..d2f5a44892a8 100644 --- a/drivers/iio/light/us5182d.c +++ b/drivers/iio/light/us5182d.c @@ -361,19 +361,13 @@ static int us5182d_shutdown_en(struct us5182d_data *data, u8 state) static int us5182d_set_power_state(struct us5182d_data *data, bool on) { - int ret; - if (data->power_mode == US5182D_ONESHOT) return 0; - if (on) { - ret = pm_runtime_resume_and_get(&data->client->dev); - } else { - pm_runtime_mark_last_busy(&data->client->dev); - ret = pm_runtime_put_autosuspend(&data->client->dev); - } + if (on) + return pm_runtime_resume_and_get(&data->client->dev); - return ret; + return pm_runtime_put_autosuspend(&data->client->dev); } static int us5182d_read_value(struct us5182d_data *data, diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c index 90e7d4421abf..4dbb2294a843 100644 --- a/drivers/iio/light/vcnl4000.c +++ b/drivers/iio/light/vcnl4000.c @@ -576,16 +576,11 @@ static bool vcnl4010_is_in_periodic_mode(struct vcnl4000_data *data) static int vcnl4000_set_pm_runtime_state(struct vcnl4000_data *data, bool on) { struct device *dev = &data->client->dev; - int ret; - if (on) { - ret = pm_runtime_resume_and_get(dev); - } else { - pm_runtime_mark_last_busy(dev); - ret = pm_runtime_put_autosuspend(dev); - } + if (on) + return pm_runtime_resume_and_get(dev); - return ret; + return pm_runtime_put_autosuspend(dev); } static int vcnl4040_read_als_it(struct vcnl4000_data *data, int *val, int *val2) @@ -1662,7 +1657,10 @@ static irqreturn_t vcnl4010_trigger_handler(int irq, void *p) struct iio_dev *indio_dev = pf->indio_dev; struct vcnl4000_data *data = iio_priv(indio_dev); const unsigned long *active_scan_mask = indio_dev->active_scan_mask; - u16 buffer[8] __aligned(8) = {0}; /* 1x16-bit + naturally aligned ts */ + struct { + u16 chan; + aligned_s64 ts; + } scan = { }; bool data_read = false; unsigned long isr; int val = 0; @@ -1682,7 +1680,7 @@ static irqreturn_t vcnl4010_trigger_handler(int irq, void *p) if (ret < 0) goto end; - buffer[0] = val; + scan.chan = val; data_read = true; } } @@ -1695,8 +1693,8 @@ static irqreturn_t vcnl4010_trigger_handler(int irq, void *p) if (!data_read) goto end; - iio_push_to_buffers_with_timestamp(indio_dev, buffer, - iio_get_time_ns(indio_dev)); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); end: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/light/vcnl4035.c b/drivers/iio/light/vcnl4035.c index 01bc99564f98..963747927425 100644 --- a/drivers/iio/light/vcnl4035.c +++ b/drivers/iio/light/vcnl4035.c @@ -141,17 +141,12 @@ static const struct iio_trigger_ops vcnl4035_trigger_ops = { static int vcnl4035_set_pm_runtime_state(struct vcnl4035_data *data, bool on) { - int ret; struct device *dev = &data->client->dev; - if (on) { - ret = pm_runtime_resume_and_get(dev); - } else { - pm_runtime_mark_last_busy(dev); - ret = pm_runtime_put_autosuspend(dev); - } + if (on) + return pm_runtime_resume_and_get(dev); - return ret; + return pm_runtime_put_autosuspend(dev); } static int vcnl4035_read_info_raw(struct iio_dev *indio_dev, diff --git a/drivers/iio/light/veml6030.c b/drivers/iio/light/veml6030.c index 0945f146bedb..6bcacae3863c 100644 --- a/drivers/iio/light/veml6030.c +++ b/drivers/iio/light/veml6030.c @@ -903,7 +903,7 @@ static irqreturn_t veml6030_trigger_handler(int irq, void *p) scan.chans[i++] = reg; } - iio_push_to_buffers_with_timestamp(iio, &scan, pf->timestamp); + iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan), pf->timestamp); done: iio_trigger_notify_done(iio->trig); diff --git a/drivers/iio/light/veml6040.c b/drivers/iio/light/veml6040.c index 71a594b2ec85..f563f9f0ee67 100644 --- a/drivers/iio/light/veml6040.c +++ b/drivers/iio/light/veml6040.c @@ -219,8 +219,7 @@ static int veml6040_probe(struct i2c_client *client) indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "IIO device allocation failed\n"); + return -ENOMEM; regmap = devm_regmap_init_i2c(client, &veml6040_regmap_config); if (IS_ERR(regmap)) diff --git a/drivers/iio/light/veml6046x00.c b/drivers/iio/light/veml6046x00.c new file mode 100644 index 000000000000..e60f24d46e7b --- /dev/null +++ b/drivers/iio/light/veml6046x00.c @@ -0,0 +1,1030 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VEML6046X00 High Accuracy RGBIR Color Sensor + * + * Copyright (c) 2025 Andreas Klinger <ak@it-klinger.de> + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <asm/byteorder.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +/* + * Device registers + * Those which are accessed as bulk io are omitted + */ +#define VEML6046X00_REG_CONF0 0x00 +#define VEML6046X00_REG_CONF1 0x01 +#define VEML6046X00_REG_THDH 0x04 +#define VEML6046X00_REG_THDL 0x06 +#define VEML6046X00_REG_R 0x10 +#define VEML6046X00_REG_G 0x12 +#define VEML6046X00_REG_B 0x14 +#define VEML6046X00_REG_IR 0x16 +#define VEML6046X00_REG_ID 0x18 +#define VEML6046X00_REG_INT 0x1A +#define VEML6046X00_REG_INT_H 0x1B + +/* Bit masks for specific functionality */ +#define VEML6046X00_CONF0_ON_0 BIT(0) +#define VEML6046X00_CONF0_INT BIT(1) +#define VEML6046X00_CONF0_AF_TRIG BIT(2) +#define VEML6046X00_CONF0_AF BIT(3) +#define VEML6046X00_CONF0_IT GENMASK(6, 4) +#define VEML6046X00_CONF1_CAL BIT(0) +#define VEML6046X00_CONF1_PERS GENMASK(2, 1) +#define VEML6046X00_CONF1_GAIN GENMASK(4, 3) +#define VEML6046X00_CONF1_PD_D2 BIT(6) +#define VEML6046X00_CONF1_ON_1 BIT(7) +#define VEML6046X00_INT_TH_H BIT(1) +#define VEML6046X00_INT_TH_L BIT(2) +#define VEML6046X00_INT_DRDY BIT(3) +#define VEML6046X00_INT_MASK \ + (VEML6046X00_INT_TH_H | VEML6046X00_INT_TH_L | VEML6046X00_INT_DRDY) + +#define VEML6046X00_GAIN_1 0x0 +#define VEML6046X00_GAIN_2 0x1 +#define VEML6046X00_GAIN_0_66 0x2 +#define VEML6046X00_GAIN_0_5 0x3 + +#define VEML6046X00_PD_2_2 0x0 +#define VEML6046X00_PD_1_2 BIT(6) + +/* Autosuspend delay */ +#define VEML6046X00_AUTOSUSPEND_MS (3 * MSEC_PER_SEC) + +enum veml6046x00_scan { + VEML6046X00_SCAN_R, + VEML6046X00_SCAN_G, + VEML6046X00_SCAN_B, + VEML6046X00_SCAN_IR, + VEML6046X00_SCAN_TIMESTAMP, +}; + +/** + * struct veml6046x00_rf - Regmap field of configuration registers. + * @int_en: Interrupt enable of green channel. + * @mode: Mode of operation. + * Driver uses always Active force mode. + * @trig: Trigger to be set in active force mode for starting + * measurement. + * @it: Integration time. + * @pers: Persistense - Number of threshold crossing for triggering + * interrupt. + */ +struct veml6046x00_rf { + struct regmap_field *int_en; + struct regmap_field *mode; + struct regmap_field *trig; + struct regmap_field *it; + struct regmap_field *pers; +}; + +/** + * struct veml6046x00_data - Private data of driver. + * @regmap: Regmap definition of sensor. + * @trig: Industrial-IO trigger. + * @rf: Regmap field of configuration. + */ +struct veml6046x00_data { + struct regmap *regmap; + struct iio_trigger *trig; + struct veml6046x00_rf rf; +}; + +/** + * DOC: Valid integration times (IT) + * + * static const int veml6046x00_it contains the array with valid IT. + * + * Register value to be read or written in regmap_field it on veml6046x00 is + * identical with array index. + * This means there is no separate translation table between valid integration + * times and register values needed. The index of the array is identical with + * the register value. + * + * The array is in the form as expected by the callback of the sysfs attribute + * integration_time_available (IIO_CHAN_INFO_INT_TIME). So there is no + * additional conversion needed. + */ +static const int veml6046x00_it[][2] = { + { 0, 3125 }, + { 0, 6250 }, + { 0, 12500 }, + { 0, 25000 }, + { 0, 50000 }, + { 0, 100000 }, + { 0, 200000 }, + { 0, 400000 }, +}; + +/** + * DOC: Handling of gain and photodiode size (PD) + * + * Gains here in the driver are not exactly the same as in the datasheet of the + * sensor. The gain in the driver is a combination of the gain of the sensor + * with the photodiode size (PD). + * The following combinations are possible: + * gain(driver) = gain(sensor) * PD + * 0.25 = x0.5 * 1/2 + * 0.33 = x0.66 * 1/2 + * 0.5 = x0.5 * 2/2 + * 0.66 = x0.66 * 2/2 + * 1 = x1 * 2/2 + * 2 = x2 * 2/2 + */ + +/** + * struct veml6046x00_gain_pd - Translation of gain and photodiode size (PD). + * @gain_sen: Gain used in the sensor as described in the datasheet of the + * sensor + * @pd: Photodiode size in the sensor + * + * This is the translation table from the gain used in the driver (and also used + * by the userspace interface in sysfs) to the gain and PD used in the sensor + * hardware. + * + * There are six gain values visible to the user (0.25 .. 2) which translate to + * two different gains in the sensor hardware (x0.5 .. x2) and two PD (1/2 and + * 2/2). Theoretical are there eight combinations, but gain values 0.5 and 1 are + * doubled and therefore the combination with the larger PD (2/2) is taken as + * more photodiode cells are supposed to deliver a more precise result. + */ +struct veml6046x00_gain_pd { + unsigned int gain_sen; + unsigned int pd; +}; + +static const struct veml6046x00_gain_pd veml6046x00_gain_pd[] = { + { .gain_sen = VEML6046X00_GAIN_0_5, .pd = VEML6046X00_PD_1_2 }, + { .gain_sen = VEML6046X00_GAIN_0_66, .pd = VEML6046X00_PD_1_2 }, + { .gain_sen = VEML6046X00_GAIN_0_5, .pd = VEML6046X00_PD_2_2 }, + { .gain_sen = VEML6046X00_GAIN_0_66, .pd = VEML6046X00_PD_2_2 }, + { .gain_sen = VEML6046X00_GAIN_1, .pd = VEML6046X00_PD_2_2 }, + { .gain_sen = VEML6046X00_GAIN_2, .pd = VEML6046X00_PD_2_2 }, +}; + +/** + * DOC: Factors for calculation of lux + * + * static const int veml6046x00_it_gains contains the factors for calculation of + * lux. + * + * Depending on the set up integration time (IT), gain and photodiode size (PD) + * the measured raw values are different if the light is constant. As the gain + * and PD are already coupled in the driver (see &struct veml6046x00_gain_pd) + * there are two dimensions remaining: IT and gain(driver). + * + * The array of available factors for a certain IT are grouped together in the + * same form as expected by the callback of scale_available + * (IIO_CHAN_INFO_SCALE). + * + * Factors for lux / raw count are taken directly from the datasheet. + */ +static const int veml6046x00_it_gains[][6][2] = { + /* integration time: 3.125 ms */ + { + { 5, 376000 }, /* gain: x0.25 */ + { 4, 72700 }, /* gain: x0.33 */ + { 2, 688000 }, /* gain: x0.5 */ + { 2, 36400 }, /* gain: x0.66 */ + { 1, 344000 }, /* gain: x1 */ + { 0, 672000 }, /* gain: x2 */ + }, + /* integration time: 6.25 ms */ + { + { 2, 688000 }, /* gain: x0.25 */ + { 2, 36350 }, /* gain: x0.33 */ + { 1, 344000 }, /* gain: x0.5 */ + { 1, 18200 }, /* gain: x0.66 */ + { 0, 672000 }, /* gain: x1 */ + { 0, 336000 }, /* gain: x2 */ + }, + /* integration time: 12.5 ms */ + { + { 1, 344000 }, /* gain: x0.25 */ + { 1, 18175 }, /* gain: x0.33 */ + { 0, 672000 }, /* gain: x0.5 */ + { 0, 509100 }, /* gain: x0.66 */ + { 0, 336000 }, /* gain: x1 */ + { 0, 168000 }, /* gain: x2 */ + }, + /* integration time: 25 ms */ + { + { 0, 672000 }, /* gain: x0.25 */ + { 0, 509087 }, /* gain: x0.33 */ + { 0, 336000 }, /* gain: x0.5 */ + { 0, 254550 }, /* gain: x0.66 */ + { 0, 168000 }, /* gain: x1 */ + { 0, 84000 }, /* gain: x2 */ + }, + /* integration time: 50 ms */ + { + { 0, 336000 }, /* gain: x0.25 */ + { 0, 254543 }, /* gain: x0.33 */ + { 0, 168000 }, /* gain: x0.5 */ + { 0, 127275 }, /* gain: x0.66 */ + { 0, 84000 }, /* gain: x1 */ + { 0, 42000 }, /* gain: x2 */ + }, + /* integration time: 100 ms */ + { + { 0, 168000 }, /* gain: x0.25 */ + { 0, 127271 }, /* gain: x0.33 */ + { 0, 84000 }, /* gain: x0.5 */ + { 0, 63637 }, /* gain: x0.66 */ + { 0, 42000 }, /* gain: x1 */ + { 0, 21000 }, /* gain: x2 */ + }, + /* integration time: 200 ms */ + { + { 0, 84000 }, /* gain: x0.25 */ + { 0, 63635 }, /* gain: x0.33 */ + { 0, 42000 }, /* gain: x0.5 */ + { 0, 31818 }, /* gain: x0.66 */ + { 0, 21000 }, /* gain: x1 */ + { 0, 10500 }, /* gain: x2 */ + }, + /* integration time: 400 ms */ + { + { 0, 42000 }, /* gain: x0.25 */ + { 0, 31817 }, /* gain: x0.33 */ + { 0, 21000 }, /* gain: x0.5 */ + { 0, 15909 }, /* gain: x0.66 */ + { 0, 10500 }, /* gain: x1 */ + { 0, 5250 }, /* gain: x2 */ + }, +}; + +/* + * Two bits (RGB_ON_0 and RGB_ON_1) must be cleared to power on the device. + */ +static int veml6046x00_power_on(struct veml6046x00_data *data) +{ + int ret; + struct device *dev = regmap_get_device(data->regmap); + + ret = regmap_clear_bits(data->regmap, VEML6046X00_REG_CONF0, + VEML6046X00_CONF0_ON_0); + if (ret) { + dev_err(dev, "Failed to set bit for power on %d\n", ret); + return ret; + } + + return regmap_clear_bits(data->regmap, VEML6046X00_REG_CONF1, + VEML6046X00_CONF1_ON_1); +} + +/* + * Two bits (RGB_ON_0 and RGB_ON_1) must be set to power off the device. + */ +static int veml6046x00_shutdown(struct veml6046x00_data *data) +{ + int ret; + struct device *dev = regmap_get_device(data->regmap); + + ret = regmap_set_bits(data->regmap, VEML6046X00_REG_CONF0, + VEML6046X00_CONF0_ON_0); + if (ret) { + dev_err(dev, "Failed to set bit for shutdown %d\n", ret); + return ret; + } + + return regmap_set_bits(data->regmap, VEML6046X00_REG_CONF1, + VEML6046X00_CONF1_ON_1); +} + +static void veml6046x00_shutdown_action(void *data) +{ + veml6046x00_shutdown(data); +} + +static const struct iio_chan_spec veml6046x00_channels[] = { + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_R, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_RED, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_R, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_G, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_GREEN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_G, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_B, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BLUE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_B, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_IR, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_IR, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(VEML6046X00_SCAN_TIMESTAMP), +}; + +static const struct regmap_config veml6046x00_regmap_config = { + .name = "veml6046x00_regm", + .reg_bits = 8, + .val_bits = 8, + .max_register = VEML6046X00_REG_INT_H, +}; + +static const struct reg_field veml6046x00_rf_int_en = + REG_FIELD(VEML6046X00_REG_CONF0, 1, 1); + +static const struct reg_field veml6046x00_rf_trig = + REG_FIELD(VEML6046X00_REG_CONF0, 2, 2); + +static const struct reg_field veml6046x00_rf_mode = + REG_FIELD(VEML6046X00_REG_CONF0, 3, 3); + +static const struct reg_field veml6046x00_rf_it = + REG_FIELD(VEML6046X00_REG_CONF0, 4, 6); + +static const struct reg_field veml6046x00_rf_pers = + REG_FIELD(VEML6046X00_REG_CONF1, 1, 2); + +static int veml6046x00_regfield_init(struct veml6046x00_data *data) +{ + struct regmap *regmap = data->regmap; + struct device *dev = regmap_get_device(data->regmap); + struct regmap_field *rm_field; + struct veml6046x00_rf *rf = &data->rf; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_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, regmap, veml6046x00_rf_mode); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->mode = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_trig); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->trig = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_it); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->it = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_pers); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->pers = rm_field; + + return 0; +} + +static int veml6046x00_get_it_index(struct veml6046x00_data *data) +{ + int ret; + unsigned int reg; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) + return ret; + + /* register value is identical with index of array */ + if (reg >= ARRAY_SIZE(veml6046x00_it)) + return -EINVAL; + + return reg; +} + +static int veml6046x00_get_it_usec(struct veml6046x00_data *data, unsigned int *it_usec) +{ + int ret; + unsigned int reg; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) + return ret; + + if (reg >= ARRAY_SIZE(veml6046x00_it)) + return -EINVAL; + + *it_usec = veml6046x00_it[reg][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6046x00_set_it(struct iio_dev *iio, int val, int val2) +{ + struct veml6046x00_data *data = iio_priv(iio); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(veml6046x00_it); i++) { + if ((veml6046x00_it[i][0] == val) && + (veml6046x00_it[i][1] == val2)) + return regmap_field_write(data->rf.it, i); + } + + return -EINVAL; +} + +static int veml6046x00_get_val_gain_idx(struct veml6046x00_data *data, int val, + int val2) +{ + unsigned int i; + int it_idx; + + it_idx = veml6046x00_get_it_index(data); + if (it_idx < 0) + return it_idx; + + for (i = 0; i < ARRAY_SIZE(veml6046x00_it_gains[it_idx]); i++) { + if ((veml6046x00_it_gains[it_idx][i][0] == val) && + (veml6046x00_it_gains[it_idx][i][1] == val2)) + return i; + } + + return -EINVAL; +} + +static int veml6046x00_get_gain_idx(struct veml6046x00_data *data) +{ + int ret; + unsigned int i, reg, reg_gain, reg_pd; + + ret = regmap_read(data->regmap, VEML6046X00_REG_CONF1, ®); + if (ret) + return ret; + + reg_gain = FIELD_GET(VEML6046X00_CONF1_GAIN, reg); + reg_pd = reg & VEML6046X00_CONF1_PD_D2; + + for (i = 0; i < ARRAY_SIZE(veml6046x00_gain_pd); i++) { + if ((veml6046x00_gain_pd[i].gain_sen == reg_gain) && + (veml6046x00_gain_pd[i].pd == reg_pd)) + return i; + } + + return -EINVAL; +} + +static int veml6046x00_set_scale(struct iio_dev *iio, int val, int val2) +{ + struct veml6046x00_data *data = iio_priv(iio); + unsigned int new_scale; + int gain_idx; + + gain_idx = veml6046x00_get_val_gain_idx(data, val, val2); + if (gain_idx < 0) + return gain_idx; + + new_scale = FIELD_PREP(VEML6046X00_CONF1_GAIN, + veml6046x00_gain_pd[gain_idx].gain_sen) | + veml6046x00_gain_pd[gain_idx].pd; + + return regmap_update_bits(data->regmap, VEML6046X00_REG_CONF1, + VEML6046X00_CONF1_GAIN | + VEML6046X00_CONF1_PD_D2, + new_scale); +} + +static int veml6046x00_get_scale(struct veml6046x00_data *data, + int *val, int *val2) +{ + int gain_idx, it_idx; + + gain_idx = veml6046x00_get_gain_idx(data); + if (gain_idx < 0) + return gain_idx; + + it_idx = veml6046x00_get_it_index(data); + if (it_idx < 0) + return it_idx; + + *val = veml6046x00_it_gains[it_idx][gain_idx][0]; + *val2 = veml6046x00_it_gains[it_idx][gain_idx][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +/** + * veml6046x00_read_data_ready() - Read data ready bit + * @data: Private data. + * + * Helper function for reading data ready bit from interrupt register. + * + * Return: + * * %1 - Data is available (AF_DATA_READY is set) + * * %0 - No data available + * * %-EIO - Error during bulk read + */ +static int veml6046x00_read_data_ready(struct veml6046x00_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + u8 reg[2]; + + /* + * Note from the vendor, but not explicitly in the datasheet: we + * should always read both registers together. + */ + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_INT, + ®, sizeof(reg)); + if (ret) { + dev_err(dev, "Failed to read interrupt register %d\n", ret); + return -EIO; + } + + if (reg[1] & VEML6046X00_INT_DRDY) + return 1; + + return 0; +} + +/** + * veml6046x00_wait_data_available() - Wait until data is available + * @iio: Industrial IO. + * @usecs: Microseconds to wait for data. + * + * This function waits for a certain bit in the interrupt register which signals + * that there is data to be read available. + * + * It tries it two times with a waiting time of usecs in between. + * + * Return: + * * %1 - Data is available (AF_DATA_READY is set) + * * %0 - Timeout, no data available after usecs timeout + * * %-EIO - Error during bulk read + */ +static int veml6046x00_wait_data_available(struct iio_dev *iio, unsigned int usecs) +{ + struct veml6046x00_data *data = iio_priv(iio); + int ret; + + ret = veml6046x00_read_data_ready(data); + if (ret) + return ret; + + fsleep(usecs); + return veml6046x00_read_data_ready(data); +} + +static int veml6046x00_single_read(struct iio_dev *iio, + enum iio_modifier modifier, int *val) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + unsigned int addr, it_usec; + int ret; + __le16 reg; + + switch (modifier) { + case IIO_MOD_LIGHT_RED: + addr = VEML6046X00_REG_R; + break; + case IIO_MOD_LIGHT_GREEN: + addr = VEML6046X00_REG_G; + break; + case IIO_MOD_LIGHT_BLUE: + addr = VEML6046X00_REG_B; + break; + case IIO_MOD_LIGHT_IR: + addr = VEML6046X00_REG_IR; + break; + default: + return -EINVAL; + } + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = veml6046x00_get_it_usec(data, &it_usec); + if (ret < 0) { + dev_err(dev, "Failed to get integration time ret: %d", ret); + goto out; + } + + ret = regmap_field_write(data->rf.mode, 1); + if (ret) { + dev_err(dev, "Failed to write mode ret: %d", ret); + goto out; + } + + ret = regmap_field_write(data->rf.trig, 1); + if (ret) { + dev_err(dev, "Failed to write trigger ret: %d", ret); + goto out; + } + + /* integration time + 12.5 % to ensure completion */ + fsleep(it_usec + it_usec / 8); + + ret = veml6046x00_wait_data_available(iio, it_usec * 4); + if (ret < 0) + goto out; + if (ret == 0) { + ret = -EAGAIN; + goto out; + } + + if (!iio_device_claim_direct(iio)) { + ret = -EBUSY; + goto out; + } + + ret = regmap_bulk_read(data->regmap, addr, ®, sizeof(reg)); + iio_device_release_direct(iio); + if (ret) + goto out; + + *val = le16_to_cpu(reg); + + ret = IIO_VAL_INT; + +out: + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int veml6046x00_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct veml6046x00_data *data = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_INTENSITY) + return -EINVAL; + return veml6046x00_single_read(iio, chan->channel2, val); + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + return veml6046x00_get_it_usec(data, val2); + case IIO_CHAN_INFO_SCALE: + return veml6046x00_get_scale(data, val, val2); + default: + return -EINVAL; + } +} + +static int veml6046x00_read_avail(struct iio_dev *iio, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml6046x00_data *data = iio_priv(iio); + int it_idx; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *vals = (int *)&veml6046x00_it; + *length = 2 * ARRAY_SIZE(veml6046x00_it); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + it_idx = veml6046x00_get_it_index(data); + if (it_idx < 0) + return it_idx; + *vals = (int *)&veml6046x00_it_gains[it_idx]; + *length = 2 * ARRAY_SIZE(veml6046x00_it_gains[it_idx]); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int veml6046x00_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml6046x00_set_it(iio, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml6046x00_set_scale(iio, val, val2); + default: + return -EINVAL; + } +} + +static const struct iio_info veml6046x00_info_no_irq = { + .read_raw = veml6046x00_read_raw, + .read_avail = veml6046x00_read_avail, + .write_raw = veml6046x00_write_raw, +}; + +static int veml6046x00_buffer_preenable(struct iio_dev *iio) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_field_write(data->rf.mode, 0); + if (ret) { + dev_err(dev, "Failed to set mode %d\n", ret); + return ret; + } + + ret = regmap_field_write(data->rf.trig, 0); + if (ret) { + /* + * no unrolling of mode as it is set appropriately with next + * single read. + */ + dev_err(dev, "Failed to set trigger %d\n", ret); + return ret; + } + + return pm_runtime_resume_and_get(dev); +} + +static int veml6046x00_buffer_postdisable(struct iio_dev *iio) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_field_write(data->rf.mode, 1); + if (ret) { + dev_err(dev, "Failed to set mode %d\n", ret); + return ret; + } + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static const struct iio_buffer_setup_ops veml6046x00_buffer_setup_ops = { + .preenable = veml6046x00_buffer_preenable, + .postdisable = veml6046x00_buffer_postdisable, +}; + +static irqreturn_t veml6046x00_trig_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct veml6046x00_data *data = iio_priv(iio); + int ret; + struct { + __le16 chans[4]; + aligned_s64 timestamp; + } scan; + + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_R, + &scan.chans, sizeof(scan.chans)); + if (ret) + goto done; + + iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan), + iio_get_time_ns(iio)); + +done: + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + +static int veml6046x00_validate_part_id(struct veml6046x00_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + unsigned int part_id; + int ret; + __le16 reg; + + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_ID, + ®, sizeof(reg)); + if (ret) + return dev_err_probe(dev, ret, "Failed to read ID\n"); + + part_id = le16_to_cpu(reg); + if (part_id != 0x01) + dev_info(dev, "Unknown ID %#04x\n", part_id); + + return 0; +} + +static int veml6046x00_setup_device(struct iio_dev *iio) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + __le16 reg16; + + reg16 = cpu_to_le16(VEML6046X00_CONF0_AF); + ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_CONF0, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set configuration\n"); + + reg16 = cpu_to_le16(0); + ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_THDL, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set low threshold\n"); + + reg16 = cpu_to_le16(U16_MAX); + ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_THDH, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set high threshold\n"); + + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_INT, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to clear interrupts\n"); + + return 0; +} + +static int veml6046x00_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct veml6046x00_data *data; + struct iio_dev *iio; + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &veml6046x00_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to set regmap\n"); + + iio = devm_iio_device_alloc(dev, sizeof(*data)); + if (!iio) + return -ENOMEM; + + data = iio_priv(iio); + /* struct iio_dev is retrieved via dev_get_drvdata(). */ + i2c_set_clientdata(i2c, iio); + data->regmap = regmap; + + ret = veml6046x00_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"); + + /* bring device in a known state and switch device on */ + ret = veml6046x00_setup_device(iio); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(dev, veml6046x00_shutdown_action, data); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add shut down action\n"); + + ret = pm_runtime_set_active(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to activate PM runtime\n"); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable PM runtime\n"); + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, VEML6046X00_AUTOSUSPEND_MS); + pm_runtime_use_autosuspend(dev); + + ret = veml6046x00_validate_part_id(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to validate device ID\n"); + + iio->name = "veml6046x00"; + iio->channels = veml6046x00_channels; + iio->num_channels = ARRAY_SIZE(veml6046x00_channels); + iio->modes = INDIO_DIRECT_MODE; + + iio->info = &veml6046x00_info_no_irq; + + ret = devm_iio_triggered_buffer_setup(dev, iio, NULL, + veml6046x00_trig_handler, + &veml6046x00_buffer_setup_ops); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register triggered buffer"); + + pm_runtime_put_autosuspend(dev); + + ret = devm_iio_device_register(dev, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to register iio device"); + + return 0; +} + +static int veml6046x00_runtime_suspend(struct device *dev) +{ + struct veml6046x00_data *data = iio_priv(dev_get_drvdata(dev)); + + return veml6046x00_shutdown(data); +} + +static int veml6046x00_runtime_resume(struct device *dev) +{ + struct veml6046x00_data *data = iio_priv(dev_get_drvdata(dev)); + + return veml6046x00_power_on(data); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(veml6046x00_pm_ops, + veml6046x00_runtime_suspend, + veml6046x00_runtime_resume, NULL); + +static const struct of_device_id veml6046x00_of_match[] = { + { .compatible = "vishay,veml6046x00" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6046x00_of_match); + +static const struct i2c_device_id veml6046x00_id[] = { + { "veml6046x00" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6046x00_id); + +static struct i2c_driver veml6046x00_driver = { + .driver = { + .name = "veml6046x00", + .of_match_table = veml6046x00_of_match, + .pm = pm_ptr(&veml6046x00_pm_ops), + }, + .probe = veml6046x00_probe, + .id_table = veml6046x00_id, +}; +module_i2c_driver(veml6046x00_driver); + +MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>"); +MODULE_DESCRIPTION("VEML6046X00 RGBIR Color Sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/vl6180.c b/drivers/iio/light/vl6180.c index cc4f2e5404aa..c1314b144367 100644 --- a/drivers/iio/light/vl6180.c +++ b/drivers/iio/light/vl6180.c @@ -96,11 +96,6 @@ struct vl6180_data { unsigned int als_it_ms; unsigned int als_meas_rate; unsigned int range_meas_rate; - - struct { - u16 chan[2]; - aligned_s64 timestamp; - } scan; }; enum { VL6180_ALS, VL6180_RANGE, VL6180_PROX }; @@ -545,6 +540,11 @@ static irqreturn_t vl6180_trigger_handler(int irq, void *priv) struct vl6180_data *data = iio_priv(indio_dev); s64 time_ns = iio_get_time_ns(indio_dev); int ret, bit, i = 0; + struct { + u16 chan[2]; + aligned_s64 timestamp; + } scan = { }; + iio_for_each_active_channel(indio_dev, bit) { if (vl6180_chan_regs_table[bit].word) @@ -560,10 +560,10 @@ static irqreturn_t vl6180_trigger_handler(int irq, void *priv) return IRQ_HANDLED; } - data->scan.chan[i++] = ret; + scan.chan[i++] = ret; } - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, time_ns); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns); iio_trigger_notify_done(indio_dev->trig); /* Clear the interrupt flag after data read */ @@ -722,7 +722,7 @@ static int vl6180_probe(struct i2c_client *client) IRQF_ONESHOT, indio_dev->name, indio_dev); if (ret) - return dev_err_probe(&client->dev, ret, "devm_request_irq error \n"); + return dev_err_probe(&client->dev, ret, "devm_request_irq error\n"); init_completion(&data->completion); diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig index 3debf1320ad1..81b812a29044 100644 --- a/drivers/iio/magnetometer/Kconfig +++ b/drivers/iio/magnetometer/Kconfig @@ -123,7 +123,7 @@ config HID_SENSOR_MAGNETOMETER_3D select IIO_BUFFER select HID_SENSOR_IIO_COMMON select HID_SENSOR_IIO_TRIGGER - tristate "HID Magenetometer 3D" + tristate "HID Magnetometer 3D" help Say yes here to build support for the HID SENSOR Magnetometer 3D. @@ -173,6 +173,19 @@ config IIO_ST_MAGN_SPI_3AXIS To compile this driver as a module, choose M here. The module will be called st_magn_spi. +config INFINEON_TLV493D + tristate "Infineon TLV493D Low-Power 3D Magnetic Sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to add support for the Infineon TLV493D-A1B6 Low- + Power 3D Magnetic Sensor. + + This driver can also be compiled as a module. + To compile this driver as a module, choose M here: the module + will be called tlv493d. + config SENSORS_HMC5843 tristate select IIO_BUFFER diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile index 9297723a97d8..dfe970fcacb8 100644 --- a/drivers/iio/magnetometer/Makefile +++ b/drivers/iio/magnetometer/Makefile @@ -23,6 +23,8 @@ st_magn-$(CONFIG_IIO_BUFFER) += st_magn_buffer.o obj-$(CONFIG_IIO_ST_MAGN_I2C_3AXIS) += st_magn_i2c.o obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o +obj-$(CONFIG_INFINEON_TLV493D) += tlv493d.o + obj-$(CONFIG_SENSORS_HMC5843) += hmc5843_core.o obj-$(CONFIG_SENSORS_HMC5843_I2C) += hmc5843_i2c.o obj-$(CONFIG_SENSORS_HMC5843_SPI) += hmc5843_spi.o diff --git a/drivers/iio/magnetometer/ak8974.c b/drivers/iio/magnetometer/ak8974.c index 947fe8a475f2..68ece700c7ce 100644 --- a/drivers/iio/magnetometer/ak8974.c +++ b/drivers/iio/magnetometer/ak8974.c @@ -583,7 +583,6 @@ static int ak8974_measure_channel(struct ak8974 *ak8974, unsigned long address, *val = (s16)le16_to_cpu(hw_values[address]); out_unlock: mutex_unlock(&ak8974->lock); - pm_runtime_mark_last_busy(&ak8974->i2c->dev); pm_runtime_put_autosuspend(&ak8974->i2c->dev); return ret; @@ -678,7 +677,6 @@ static void ak8974_fill_buffer(struct iio_dev *indio_dev) out_unlock: mutex_unlock(&ak8974->lock); - pm_runtime_mark_last_busy(&ak8974->i2c->dev); pm_runtime_put_autosuspend(&ak8974->i2c->dev); } diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index a1e92b2abffd..3fd0171e5d69 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -775,7 +775,6 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) mutex_unlock(&data->lock); - pm_runtime_mark_last_busy(&data->client->dev); pm_runtime_put_autosuspend(&data->client->dev); /* Swap bytes and convert to valid range. */ diff --git a/drivers/iio/magnetometer/als31300.c b/drivers/iio/magnetometer/als31300.c index f72af829715f..2a2677428ed5 100644 --- a/drivers/iio/magnetometer/als31300.c +++ b/drivers/iio/magnetometer/als31300.c @@ -140,7 +140,6 @@ static int als31300_get_measure(struct als31300_data *data, *z = ALS31300_DATA_Z_GET(buf); out: - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return ret; @@ -156,7 +155,6 @@ static int als31300_read_raw(struct iio_dev *indio_dev, int ret; switch (mask) { - case IIO_CHAN_INFO_PROCESSED: case IIO_CHAN_INFO_RAW: ret = als31300_get_measure(data, &t, &x, &y, &z); if (ret) @@ -373,7 +371,7 @@ static int als31300_probe(struct i2c_client *i2c) ret = devm_add_action_or_reset(dev, als31300_power_down, data); if (ret) - return dev_err_probe(dev, ret, "failed to add powerdown action\n"); + return ret; indio_dev->info = &als31300_info; indio_dev->modes = INDIO_DIRECT_MODE; @@ -401,7 +399,6 @@ static int als31300_probe(struct i2c_client *i2c) pm_runtime_set_autosuspend_delay(dev, 200); pm_runtime_use_autosuspend(dev); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); ret = devm_iio_device_register(dev, indio_dev); diff --git a/drivers/iio/magnetometer/bmc150_magn.c b/drivers/iio/magnetometer/bmc150_magn.c index 761daead5ada..6a73f6e2f1f0 100644 --- a/drivers/iio/magnetometer/bmc150_magn.c +++ b/drivers/iio/magnetometer/bmc150_magn.c @@ -257,22 +257,17 @@ static int bmc150_magn_set_power_mode(struct bmc150_magn_data *data, static int bmc150_magn_set_power_state(struct bmc150_magn_data *data, bool on) { -#ifdef CONFIG_PM - int ret; + int ret = 0; - if (on) { + if (on) ret = pm_runtime_resume_and_get(data->dev); - } else { - pm_runtime_mark_last_busy(data->dev); - ret = pm_runtime_put_autosuspend(data->dev); - } - + else + pm_runtime_put_autosuspend(data->dev); if (ret < 0) { dev_err(data->dev, "failed to change power state to %d\n", on); return ret; } -#endif return 0; } diff --git a/drivers/iio/magnetometer/tlv493d.c b/drivers/iio/magnetometer/tlv493d.c new file mode 100644 index 000000000000..ec53fd40277b --- /dev/null +++ b/drivers/iio/magnetometer/tlv493d.c @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Infineon TLV493D Low-Power 3D Magnetic Sensor + * + * Copyright (C) 2025 Dixit Parmar <dixitparmar19@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/dev_printk.h> +#include <linux/i2c.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +/* + * TLV493D sensor I2C communication note: + * + * The sensor supports only direct byte-stream write starting from the + * register address 0x0. So for any modification to be made to any write + * registers, it must be written starting from the register address 0x0. + * I2C write operation should not contain the register address in the I2C + * frame, it should contain only raw byte stream for the write registers. + * I2C Frame: |S|SlaveAddr Wr|Ack|Byte[0]|Ack|Byte[1]|Ack|.....|Sp| + * + * Same as the write operation, reading from the sensor registers is also + * performed starting from the register address 0x0 for as many bytes as + * need to be read. + * I2C read operation should not contain the register address in the I2C frame. + * I2C Frame: |S|SlaveAddr Rd|Ack|Byte[0]|Ack|Byte[1]|Ack|.....|Sp| + */ + +#define TLV493D_RD_REG_BX 0x00 +#define TLV493D_RD_REG_BY 0x01 +#define TLV493D_RD_REG_BZ 0x02 +#define TLV493D_RD_REG_TEMP 0x03 +#define TLV493D_RD_REG_BX2 0x04 +#define TLV493D_RD_REG_BZ2 0x05 +#define TLV493D_RD_REG_TEMP2 0x06 +#define TLV493D_RD_REG_RES1 0x07 +#define TLV493D_RD_REG_RES2 0x08 +#define TLV493D_RD_REG_RES3 0x09 +#define TLV493D_RD_REG_MAX 0x0a + +#define TLV493D_WR_REG_MODE1 0x01 +#define TLV493D_WR_REG_MODE2 0x03 +#define TLV493D_WR_REG_MAX 0x04 + +#define TLV493D_BX_MAG_X_AXIS_MSB GENMASK(7, 0) +#define TLV493D_BX2_MAG_X_AXIS_LSB GENMASK(7, 4) +#define TLV493D_BY_MAG_Y_AXIS_MSB GENMASK(7, 0) +#define TLV493D_BX2_MAG_Y_AXIS_LSB GENMASK(3, 0) +#define TLV493D_BZ_MAG_Z_AXIS_MSB GENMASK(7, 0) +#define TLV493D_BZ2_MAG_Z_AXIS_LSB GENMASK(3, 0) +#define TLV493D_TEMP_TEMP_MSB GENMASK(7, 4) +#define TLV493D_TEMP2_TEMP_LSB GENMASK(7, 0) +#define TLV493D_TEMP_CHANNEL GENMASK(1, 0) +#define TLV493D_MODE1_MOD_LOWFAST GENMASK(1, 0) +#define TLV493D_MODE2_LP_PERIOD BIT(6) +#define TLV493D_RD_REG_RES1_WR_MASK GENMASK(4, 3) +#define TLV493D_RD_REG_RES2_WR_MASK GENMASK(7, 0) +#define TLV493D_RD_REG_RES3_WR_MASK GENMASK(4, 0) + +enum tlv493d_channels { + TLV493D_AXIS_X, + TLV493D_AXIS_Y, + TLV493D_AXIS_Z, + TLV493D_TEMPERATURE, +}; + +enum tlv493d_op_mode { + TLV493D_OP_MODE_POWERDOWN, + TLV493D_OP_MODE_FAST, + TLV493D_OP_MODE_LOWPOWER, + TLV493D_OP_MODE_ULTRA_LOWPOWER, + TLV493D_OP_MODE_MASTERCONTROLLED, +}; + +struct tlv493d_data { + struct i2c_client *client; + /* protects from simultaneous sensor access and register readings */ + struct mutex lock; + enum tlv493d_op_mode mode; + u8 wr_regs[TLV493D_WR_REG_MAX]; +}; + +/* + * Different mode has different measurement sampling time, this time is + * used in deriving the sleep and timeout while reading the data from + * sensor in polling. + * Power-down mode: No measurement. + * Fast mode: Freq:3.3 KHz. Measurement time:305 usec. + * Low-power mode: Freq:100 Hz. Measurement time:10 msec. + * Ultra low-power mode: Freq:10 Hz. Measurement time:100 msec. + * Master controlled mode: Freq:3.3 Khz. Measurement time:305 usec. + */ +static const u32 tlv493d_sample_rate_us[] = { + [TLV493D_OP_MODE_POWERDOWN] = 0, + [TLV493D_OP_MODE_FAST] = 305, + [TLV493D_OP_MODE_LOWPOWER] = 10 * USEC_PER_MSEC, + [TLV493D_OP_MODE_ULTRA_LOWPOWER] = 100 * USEC_PER_MSEC, + [TLV493D_OP_MODE_MASTERCONTROLLED] = 305, +}; + +static int tlv493d_write_all_regs(struct tlv493d_data *data) +{ + int ret; + struct device *dev = &data->client->dev; + + ret = i2c_master_send(data->client, data->wr_regs, ARRAY_SIZE(data->wr_regs)); + if (ret < 0) { + dev_err(dev, "i2c write registers failed, error: %d\n", ret); + return ret; + } + + return 0; +} + +static int tlv493d_set_operating_mode(struct tlv493d_data *data, enum tlv493d_op_mode mode) +{ + u8 *mode1_cfg = &data->wr_regs[TLV493D_WR_REG_MODE1]; + u8 *mode2_cfg = &data->wr_regs[TLV493D_WR_REG_MODE2]; + + switch (mode) { + case TLV493D_OP_MODE_POWERDOWN: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 0); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + + case TLV493D_OP_MODE_FAST: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 1); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + + case TLV493D_OP_MODE_LOWPOWER: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 2); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 1); + break; + + case TLV493D_OP_MODE_ULTRA_LOWPOWER: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 2); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + + case TLV493D_OP_MODE_MASTERCONTROLLED: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 3); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + } + + return tlv493d_write_all_regs(data); +} + +static s16 tlv493d_get_channel_data(u8 *b, enum tlv493d_channels ch) +{ + u16 val; + + switch (ch) { + case TLV493D_AXIS_X: + val = FIELD_GET(TLV493D_BX_MAG_X_AXIS_MSB, b[TLV493D_RD_REG_BX]) << 4 | + FIELD_GET(TLV493D_BX2_MAG_X_AXIS_LSB, b[TLV493D_RD_REG_BX2]) >> 4; + break; + case TLV493D_AXIS_Y: + val = FIELD_GET(TLV493D_BY_MAG_Y_AXIS_MSB, b[TLV493D_RD_REG_BY]) << 4 | + FIELD_GET(TLV493D_BX2_MAG_Y_AXIS_LSB, b[TLV493D_RD_REG_BX2]); + break; + case TLV493D_AXIS_Z: + val = FIELD_GET(TLV493D_BZ_MAG_Z_AXIS_MSB, b[TLV493D_RD_REG_BZ]) << 4 | + FIELD_GET(TLV493D_BZ2_MAG_Z_AXIS_LSB, b[TLV493D_RD_REG_BZ2]); + break; + case TLV493D_TEMPERATURE: + val = FIELD_GET(TLV493D_TEMP_TEMP_MSB, b[TLV493D_RD_REG_TEMP]) << 8 | + FIELD_GET(TLV493D_TEMP2_TEMP_LSB, b[TLV493D_RD_REG_TEMP2]); + break; + } + + return sign_extend32(val, 11); +} + +static int tlv493d_get_measurements(struct tlv493d_data *data, s16 *x, s16 *y, + s16 *z, s16 *t) +{ + u8 buff[7] = {}; + int err, ret; + struct device *dev = &data->client->dev; + u32 sleep_us = tlv493d_sample_rate_us[data->mode]; + + guard(mutex)(&data->lock); + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* + * Poll until data is valid. + * For a valid data TLV493D_TEMP_CHANNEL bit of TLV493D_RD_REG_TEMP + * should be set to 0. The sampling time depends on the sensor mode. + * Poll 3x the time of the sampling time. + */ + ret = read_poll_timeout(i2c_master_recv, err, + err || !FIELD_GET(TLV493D_TEMP_CHANNEL, buff[TLV493D_RD_REG_TEMP]), + sleep_us, 3 * sleep_us, false, data->client, buff, + ARRAY_SIZE(buff)); + if (ret) { + dev_err(dev, "i2c read poll timeout, error:%d\n", ret); + goto out_put_autosuspend; + } + if (err < 0) { + dev_err(dev, "i2c read data failed, error:%d\n", err); + ret = err; + goto out_put_autosuspend; + } + + *x = tlv493d_get_channel_data(buff, TLV493D_AXIS_X); + *y = tlv493d_get_channel_data(buff, TLV493D_AXIS_Y); + *z = tlv493d_get_channel_data(buff, TLV493D_AXIS_Z); + *t = tlv493d_get_channel_data(buff, TLV493D_TEMPERATURE); + +out_put_autosuspend: + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int tlv493d_init(struct tlv493d_data *data) +{ + int ret; + u8 buff[TLV493D_RD_REG_MAX]; + struct device *dev = &data->client->dev; + + /* + * The sensor initialization requires below steps to be followed, + * 1. Power-up sensor. + * 2. Read and store read-registers map (0x0-0x9). + * 3. Copy values from read reserved registers to write reserved fields + * (0x0-0x3). + * 4. Set operating mode. + * 5. Write to all registers. + */ + ret = i2c_master_recv(data->client, buff, ARRAY_SIZE(buff)); + if (ret < 0) + return dev_err_probe(dev, ret, "i2c read failed\n"); + + /* Write register 0x0 is reserved. Does not require to be updated.*/ + data->wr_regs[0] = 0; + data->wr_regs[1] = buff[TLV493D_RD_REG_RES1] & TLV493D_RD_REG_RES1_WR_MASK; + data->wr_regs[2] = buff[TLV493D_RD_REG_RES2] & TLV493D_RD_REG_RES2_WR_MASK; + data->wr_regs[3] = buff[TLV493D_RD_REG_RES3] & TLV493D_RD_REG_RES3_WR_MASK; + + ret = tlv493d_set_operating_mode(data, data->mode); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to set operating mode\n"); + + return 0; +} + +static int tlv493d_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct tlv493d_data *data = iio_priv(indio_dev); + s16 x, y, z, t; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = tlv493d_get_measurements(data, &x, &y, &z, &t); + if (ret) + return ret; + + switch (chan->address) { + case TLV493D_AXIS_X: + *val = x; + return IIO_VAL_INT; + case TLV493D_AXIS_Y: + *val = y; + return IIO_VAL_INT; + case TLV493D_AXIS_Z: + *val = z; + return IIO_VAL_INT; + case TLV493D_TEMPERATURE: + *val = t; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MAGN: + /* + * Magnetic field scale: 0.0098 mTesla (i.e. 9.8 µT) + * Magnetic field in Gauss: mT * 10 = 0.098. + */ + *val = 98; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_TEMP: + /* + * Temperature scale: 1.1 °C per LSB, expressed as 1100 m°C + * Returned as integer for IIO core to apply: + * temp = (raw + offset) * scale + */ + *val = 1100; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + /* + * Temperature offset includes sensor-specific raw offset + * plus compensation for +25°C bias in formula. + * offset = -raw_offset + (25000 / 1100) + * -340 + 22.72 = -317.28 + */ + *val = -31728; + *val2 = 100; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static irqreturn_t tlv493d_trigger_handler(int irq, void *ptr) +{ + int ret; + s16 x, y, z, t; + struct iio_poll_func *pf = ptr; + struct iio_dev *indio_dev = pf->indio_dev; + struct tlv493d_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + struct { + s16 channels[3]; + s16 temperature; + aligned_s64 timestamp; + } scan; + + ret = tlv493d_get_measurements(data, &x, &y, &z, &t); + if (ret) { + dev_err(dev, "failed to read sensor data\n"); + goto out_trigger_notify; + } + + scan.channels[0] = x; + scan.channels[1] = y; + scan.channels[2] = z; + scan.temperature = t; + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); + +out_trigger_notify: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define TLV493D_AXIS_CHANNEL(axis, index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec tlv493d_channels[] = { + TLV493D_AXIS_CHANNEL(X, TLV493D_AXIS_X), + TLV493D_AXIS_CHANNEL(Y, TLV493D_AXIS_Y), + TLV493D_AXIS_CHANNEL(Z, TLV493D_AXIS_Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = TLV493D_TEMPERATURE, + .scan_index = TLV493D_TEMPERATURE, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_info tlv493d_info = { + .read_raw = tlv493d_read_raw, +}; + +static const unsigned long tlv493d_scan_masks[] = { GENMASK(3, 0), 0 }; + +static int tlv493d_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct tlv493d_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulator\n"); + + /* + * Setting Sensor default operating mode to Master-Controlled mode since + * it performs measurement cycle only on-request and stays in Power-Down + * state until next cycle is initiated. + */ + data->mode = TLV493D_OP_MODE_MASTERCONTROLLED; + ret = tlv493d_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize\n"); + + indio_dev->info = &tlv493d_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = client->name; + indio_dev->channels = tlv493d_channels; + indio_dev->num_channels = ARRAY_SIZE(tlv493d_channels); + indio_dev->available_scan_masks = tlv493d_scan_masks; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + tlv493d_trigger_handler, + NULL); + if (ret) + return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, 500); + pm_runtime_use_autosuspend(dev); + + pm_runtime_put_autosuspend(dev); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "iio device register failed\n"); + + return 0; +} + +static int tlv493d_runtime_suspend(struct device *dev) +{ + struct tlv493d_data *data = iio_priv(dev_get_drvdata(dev)); + + return tlv493d_set_operating_mode(data, TLV493D_OP_MODE_POWERDOWN); +} + +static int tlv493d_runtime_resume(struct device *dev) +{ + struct tlv493d_data *data = iio_priv(dev_get_drvdata(dev)); + + return tlv493d_set_operating_mode(data, data->mode); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(tlv493d_pm_ops, tlv493d_runtime_suspend, + tlv493d_runtime_resume, NULL); + +static const struct i2c_device_id tlv493d_id[] = { + { "tlv493d" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tlv493d_id); + +static const struct of_device_id tlv493d_of_match[] = { + { .compatible = "infineon,tlv493d-a1b6" }, + { } +}; +MODULE_DEVICE_TABLE(of, tlv493d_of_match); + +static struct i2c_driver tlv493d_driver = { + .driver = { + .name = "tlv493d", + .of_match_table = tlv493d_of_match, + .pm = pm_ptr(&tlv493d_pm_ops), + }, + .probe = tlv493d_probe, + .id_table = tlv493d_id, +}; +module_i2c_driver(tlv493d_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Infineon TLV493D Low-Power 3D Magnetic Sensor"); +MODULE_AUTHOR("Dixit Parmar <dixitparmar19@gmail.com>"); diff --git a/drivers/iio/magnetometer/tmag5273.c b/drivers/iio/magnetometer/tmag5273.c index 2ca5c26f0091..2adc3c036ab4 100644 --- a/drivers/iio/magnetometer/tmag5273.c +++ b/drivers/iio/magnetometer/tmag5273.c @@ -287,7 +287,6 @@ static int tmag5273_read_raw(struct iio_dev *indio_dev, int ret; switch (mask) { - case IIO_CHAN_INFO_PROCESSED: case IIO_CHAN_INFO_RAW: ret = pm_runtime_resume_and_get(data->dev); if (ret < 0) @@ -295,7 +294,6 @@ static int tmag5273_read_raw(struct iio_dev *indio_dev, ret = tmag5273_get_measure(data, &t, &x, &y, &z, &angle, &magnitude); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); if (ret) @@ -642,7 +640,7 @@ static int tmag5273_probe(struct i2c_client *i2c) */ ret = devm_add_action_or_reset(dev, tmag5273_power_down, data); if (ret) - return dev_err_probe(dev, ret, "failed to add powerdown action\n"); + return ret; ret = pm_runtime_set_active(dev); if (ret < 0) @@ -668,7 +666,6 @@ static int tmag5273_probe(struct i2c_client *i2c) indio_dev->channels = tmag5273_channels; indio_dev->num_channels = ARRAY_SIZE(tmag5273_channels); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); ret = devm_iio_device_register(dev, indio_dev); diff --git a/drivers/iio/magnetometer/yamaha-yas530.c b/drivers/iio/magnetometer/yamaha-yas530.c index 340607111d9a..d49e37edcbed 100644 --- a/drivers/iio/magnetometer/yamaha-yas530.c +++ b/drivers/iio/magnetometer/yamaha-yas530.c @@ -623,7 +623,6 @@ static int yas5xx_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_RAW: pm_runtime_get_sync(yas5xx->dev); ret = ci->get_measure(yas5xx, &t, &x, &y, &z); - pm_runtime_mark_last_busy(yas5xx->dev); pm_runtime_put_autosuspend(yas5xx->dev); if (ret) return ret; @@ -664,7 +663,6 @@ static void yas5xx_fill_buffer(struct iio_dev *indio_dev) pm_runtime_get_sync(yas5xx->dev); ret = ci->get_measure(yas5xx, &t, &x, &y, &z); - pm_runtime_mark_last_busy(yas5xx->dev); pm_runtime_put_autosuspend(yas5xx->dev); if (ret) { dev_err(yas5xx->dev, "error refilling buffer\n"); diff --git a/drivers/iio/potentiostat/lmp91000.c b/drivers/iio/potentiostat/lmp91000.c index 030498d0b763..eccc2a34358f 100644 --- a/drivers/iio/potentiostat/lmp91000.c +++ b/drivers/iio/potentiostat/lmp91000.c @@ -321,10 +321,8 @@ static int lmp91000_probe(struct i2c_client *client) data->trig = devm_iio_trigger_alloc(dev, "%s-mux%d", indio_dev->name, iio_device_id(indio_dev)); - if (!data->trig) { - dev_err(dev, "cannot allocate iio trigger.\n"); + if (!data->trig) return -ENOMEM; - } init_completion(&data->completion); diff --git a/drivers/iio/pressure/bmp280-core.c b/drivers/iio/pressure/bmp280-core.c index 6cdc8ed53520..c04e8bb4c993 100644 --- a/drivers/iio/pressure/bmp280-core.c +++ b/drivers/iio/pressure/bmp280-core.c @@ -752,7 +752,6 @@ static int bmp280_read_raw(struct iio_dev *indio_dev, pm_runtime_get_sync(data->dev); ret = bmp280_read_raw_impl(indio_dev, chan, val, val2, mask); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return ret; @@ -927,7 +926,6 @@ static int bmp280_write_raw(struct iio_dev *indio_dev, pm_runtime_get_sync(data->dev); ret = bmp280_write_raw_impl(indio_dev, chan, val, val2, mask); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return ret; @@ -2255,7 +2253,6 @@ static int bmp580_nvmem_read(void *priv, unsigned int offset, void *val, pm_runtime_get_sync(data->dev); ret = bmp580_nvmem_read_impl(priv, offset, val, bytes); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return ret; @@ -2330,7 +2327,6 @@ static int bmp580_nvmem_write(void *priv, unsigned int offset, void *val, pm_runtime_get_sync(data->dev); ret = bmp580_nvmem_write_impl(priv, offset, val, bytes); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return ret; @@ -3120,7 +3116,6 @@ static int bmp280_buffer_postdisable(struct iio_dev *indio_dev) { struct bmp280_data *data = iio_priv(indio_dev); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return 0; @@ -3217,8 +3212,7 @@ int bmp280_common_probe(struct device *dev, return dev_err_probe(dev, PTR_ERR(gpiod), "failed to get reset GPIO\n"); /* Deassert the signal */ - dev_info(dev, "release reset\n"); - gpiod_set_value(gpiod, 0); + gpiod_set_value_cansleep(gpiod, 0); data->regmap = regmap; diff --git a/drivers/iio/pressure/dlhl60d.c b/drivers/iio/pressure/dlhl60d.c index 6a13cf2eaf50..8bad7162fec6 100644 --- a/drivers/iio/pressure/dlhl60d.c +++ b/drivers/iio/pressure/dlhl60d.c @@ -289,10 +289,8 @@ static int dlh_probe(struct i2c_client *client) } indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); - if (!indio_dev) { - dev_err(&client->dev, "failed to allocate iio device\n"); + if (!indio_dev) return -ENOMEM; - } i2c_set_clientdata(client, indio_dev); diff --git a/drivers/iio/pressure/icp10100.c b/drivers/iio/pressure/icp10100.c index 1951c1cc84cf..3d83d0098a57 100644 --- a/drivers/iio/pressure/icp10100.c +++ b/drivers/iio/pressure/icp10100.c @@ -265,7 +265,6 @@ static int icp10100_get_measures(struct icp10100_state *st, (be16_to_cpu(measures[1]) >> 8); *temperature = be16_to_cpu(measures[2]); - pm_runtime_mark_last_busy(&st->client->dev); error_measure: pm_runtime_put_autosuspend(&st->client->dev); return ret; diff --git a/drivers/iio/pressure/mpl115.c b/drivers/iio/pressure/mpl115.c index 71beb28b7f2c..830a5065c008 100644 --- a/drivers/iio/pressure/mpl115.c +++ b/drivers/iio/pressure/mpl115.c @@ -108,7 +108,6 @@ static int mpl115_read_raw(struct iio_dev *indio_dev, ret = mpl115_comp_pressure(data, val, val2); if (ret < 0) return ret; - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return IIO_VAL_INT_PLUS_MICRO; @@ -118,7 +117,6 @@ static int mpl115_read_raw(struct iio_dev *indio_dev, ret = mpl115_read_temp(data); if (ret < 0) return ret; - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); *val = ret >> 6; diff --git a/drivers/iio/pressure/zpa2326.c b/drivers/iio/pressure/zpa2326.c index 6eef37c0952d..4923a558a26a 100644 --- a/drivers/iio/pressure/zpa2326.c +++ b/drivers/iio/pressure/zpa2326.c @@ -697,7 +697,6 @@ static void zpa2326_suspend(struct iio_dev *indio_dev) zpa2326_sleep(indio_dev); - pm_runtime_mark_last_busy(parent); pm_runtime_put_autosuspend(parent); } @@ -708,7 +707,6 @@ static void zpa2326_init_runtime(struct device *parent) pm_runtime_enable(parent); pm_runtime_set_autosuspend_delay(parent, 1000); pm_runtime_use_autosuspend(parent); - pm_runtime_mark_last_busy(parent); pm_runtime_put_autosuspend(parent); } diff --git a/drivers/iio/proximity/d3323aa.c b/drivers/iio/proximity/d3323aa.c index d4c3dbea9bb0..30821f583454 100644 --- a/drivers/iio/proximity/d3323aa.c +++ b/drivers/iio/proximity/d3323aa.c @@ -722,8 +722,7 @@ static int d3323aa_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "Could not allocate iio device\n"); + return -ENOMEM; data = iio_priv(indio_dev); data->dev = dev; diff --git a/drivers/iio/proximity/hx9023s.c b/drivers/iio/proximity/hx9023s.c index 33781c314728..2918dfc0df54 100644 --- a/drivers/iio/proximity/hx9023s.c +++ b/drivers/iio/proximity/hx9023s.c @@ -1141,8 +1141,7 @@ static int hx9023s_probe(struct i2c_client *client) indio_dev->name, iio_device_id(indio_dev)); if (!data->trig) - return dev_err_probe(dev, -ENOMEM, - "iio trigger alloc failed\n"); + return -ENOMEM; data->trig->ops = &hx9023s_trigger_ops; iio_trigger_set_drvdata(data->trig, indio_dev); diff --git a/drivers/iio/proximity/irsd200.c b/drivers/iio/proximity/irsd200.c index 253e4aef22fb..65af31d43453 100644 --- a/drivers/iio/proximity/irsd200.c +++ b/drivers/iio/proximity/irsd200.c @@ -862,8 +862,7 @@ static int irsd200_probe(struct i2c_client *client) indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) - return dev_err_probe(&client->dev, -ENOMEM, - "Could not allocate iio device\n"); + return -ENOMEM; data = iio_priv(indio_dev); data->dev = &client->dev; @@ -916,8 +915,7 @@ static int irsd200_probe(struct i2c_client *client) trigger = devm_iio_trigger_alloc(data->dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); if (!trigger) - return dev_err_probe(data->dev, -ENOMEM, - "Could not allocate iio trigger\n"); + return -ENOMEM; trigger->ops = &irsd200_trigger_ops; iio_trigger_set_drvdata(trigger, data); diff --git a/drivers/iio/proximity/mb1232.c b/drivers/iio/proximity/mb1232.c index 01783486bc7d..34b49c54e68b 100644 --- a/drivers/iio/proximity/mb1232.c +++ b/drivers/iio/proximity/mb1232.c @@ -42,11 +42,6 @@ struct mb1232_data { */ struct completion ranging; int irqnr; - /* Ensure correct alignment of data to push to IIO buffer */ - struct { - s16 distance; - aligned_s64 ts; - } scan; }; static irqreturn_t mb1232_handle_irq(int irq, void *dev_id) @@ -120,12 +115,16 @@ static irqreturn_t mb1232_trigger_handler(int irq, void *p) struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct mb1232_data *data = iio_priv(indio_dev); + struct { + s16 distance; + aligned_s64 ts; + } scan = { }; - data->scan.distance = mb1232_read_distance(data); - if (data->scan.distance < 0) + scan.distance = mb1232_read_distance(data); + if (scan.distance < 0) goto err; - iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); err: diff --git a/drivers/iio/proximity/ping.c b/drivers/iio/proximity/ping.c index c5b4e1378b7d..e3487094d7be 100644 --- a/drivers/iio/proximity/ping.c +++ b/drivers/iio/proximity/ping.c @@ -280,10 +280,8 @@ static int ping_probe(struct platform_device *pdev) struct iio_dev *indio_dev; indio_dev = devm_iio_device_alloc(dev, sizeof(struct ping_data)); - if (!indio_dev) { - dev_err(dev, "failed to allocate IIO device\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->dev = dev; diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c index 1deaf70e92ce..21336b8f122a 100644 --- a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c +++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c @@ -43,12 +43,6 @@ struct lidar_data { int (*xfer)(struct lidar_data *data, u8 reg, u8 *val, int len); int i2c_enabled; - - /* Ensure timestamp is naturally aligned */ - struct { - u16 chan; - aligned_s64 timestamp; - } scan; }; static const struct iio_chan_spec lidar_channels[] = { @@ -191,7 +185,6 @@ static int lidar_get_measurement(struct lidar_data *data, u16 *reg) } ret = -EIO; } - pm_runtime_mark_last_busy(&client->dev); pm_runtime_put_autosuspend(&client->dev); return ret; @@ -235,11 +228,14 @@ static irqreturn_t lidar_trigger_handler(int irq, void *private) struct iio_dev *indio_dev = pf->indio_dev; struct lidar_data *data = iio_priv(indio_dev); int ret; + struct { + u16 chan; + aligned_s64 timestamp; + } scan = { }; - ret = lidar_get_measurement(data, &data->scan.chan); + ret = lidar_get_measurement(data, &scan.chan); if (!ret) { - iio_push_to_buffers_with_ts(indio_dev, &data->scan, - sizeof(data->scan), + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), iio_get_time_ns(indio_dev)); } else if (ret != -EINVAL) { dev_err(&data->client->dev, "cannot read LIDAR measurement"); diff --git a/drivers/iio/proximity/srf04.c b/drivers/iio/proximity/srf04.c index b059bac1078b..e97f9a20ac7a 100644 --- a/drivers/iio/proximity/srf04.c +++ b/drivers/iio/proximity/srf04.c @@ -117,10 +117,8 @@ static int srf04_read(struct srf04_data *data) udelay(data->cfg->trigger_pulse_us); gpiod_set_value(data->gpiod_trig, 0); - if (data->gpiod_power) { - pm_runtime_mark_last_busy(data->dev); + if (data->gpiod_power) pm_runtime_put_autosuspend(data->dev); - } /* it should not take more than 20 ms until echo is rising */ ret = wait_for_completion_killable_timeout(&data->rising, HZ/50); @@ -253,10 +251,8 @@ static int srf04_probe(struct platform_device *pdev) int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(struct srf04_data)); - if (!indio_dev) { - dev_err(dev, "failed to allocate IIO device\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->dev = dev; diff --git a/drivers/iio/proximity/srf08.c b/drivers/iio/proximity/srf08.c index 6e32fdfd161b..d7e4cc48cfbf 100644 --- a/drivers/iio/proximity/srf08.c +++ b/drivers/iio/proximity/srf08.c @@ -63,12 +63,6 @@ struct srf08_data { int range_mm; struct mutex lock; - /* Ensure timestamp is naturally aligned */ - struct { - s16 chan; - aligned_s64 timestamp; - } scan; - /* Sensor-Type */ enum srf08_sensor_type sensor_type; @@ -182,16 +176,18 @@ static irqreturn_t srf08_trigger_handler(int irq, void *p) struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct srf08_data *data = iio_priv(indio_dev); - s16 sensor_data; + struct { + s16 chan; + aligned_s64 timestamp; + } scan = { }; - sensor_data = srf08_read_ranging(data); - if (sensor_data < 0) + scan.chan = srf08_read_ranging(data); + if (scan.chan < 0) goto err; mutex_lock(&data->lock); - data->scan.chan = sensor_data; - iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); mutex_unlock(&data->lock); diff --git a/drivers/iio/proximity/sx9500.c b/drivers/iio/proximity/sx9500.c index 05844f17a15f..6c67bae7488c 100644 --- a/drivers/iio/proximity/sx9500.c +++ b/drivers/iio/proximity/sx9500.c @@ -88,7 +88,6 @@ struct sx9500_data { bool prox_stat[SX9500_NUM_CHANNELS]; bool event_enabled[SX9500_NUM_CHANNELS]; bool trigger_enabled; - u16 *buffer; /* Remember enabled channels and sample rate during suspend. */ unsigned int suspend_ctrl0; struct completion completion; @@ -578,22 +577,6 @@ out_unlock: return ret; } -static int sx9500_update_scan_mode(struct iio_dev *indio_dev, - const unsigned long *scan_mask) -{ - struct sx9500_data *data = iio_priv(indio_dev); - - mutex_lock(&data->mutex); - kfree(data->buffer); - data->buffer = kzalloc(indio_dev->scan_bytes, GFP_KERNEL); - mutex_unlock(&data->mutex); - - if (data->buffer == NULL) - return -ENOMEM; - - return 0; -} - static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( "2.500000 3.333333 5 6.666666 8.333333 11.111111 16.666666 33.333333"); @@ -612,7 +595,6 @@ static const struct iio_info sx9500_info = { .write_raw = &sx9500_write_raw, .read_event_config = &sx9500_read_event_config, .write_event_config = &sx9500_write_event_config, - .update_scan_mode = &sx9500_update_scan_mode, }; static int sx9500_set_trigger_state(struct iio_trigger *trig, @@ -649,6 +631,10 @@ static irqreturn_t sx9500_trigger_handler(int irq, void *private) struct iio_dev *indio_dev = pf->indio_dev; struct sx9500_data *data = iio_priv(indio_dev); int val, bit, ret, i = 0; + struct { + u16 chan[SX9500_NUM_CHANNELS]; + aligned_s64 timestamp; + } scan = { }; mutex_lock(&data->mutex); @@ -658,10 +644,10 @@ static irqreturn_t sx9500_trigger_handler(int irq, void *private) if (ret < 0) goto out; - data->buffer[i++] = val; + scan.chan[i++] = val; } - iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_push_to_buffers_with_timestamp(indio_dev, &scan, iio_get_time_ns(indio_dev)); out: @@ -984,7 +970,6 @@ static void sx9500_remove(struct i2c_client *client) iio_triggered_buffer_cleanup(indio_dev); if (client->irq > 0) iio_trigger_unregister(data->trig); - kfree(data->buffer); } static int sx9500_suspend(struct device *dev) diff --git a/drivers/iio/proximity/vl53l0x-i2c.c b/drivers/iio/proximity/vl53l0x-i2c.c index ef4aa7b2835e..ad3e46d47fa8 100644 --- a/drivers/iio/proximity/vl53l0x-i2c.c +++ b/drivers/iio/proximity/vl53l0x-i2c.c @@ -57,11 +57,6 @@ struct vl53l0x_data { struct regulator *vdd_supply; struct gpio_desc *reset_gpio; struct iio_trigger *trig; - - struct { - u16 chan; - aligned_s64 timestamp; - } scan; }; static int vl53l0x_clear_irq(struct vl53l0x_data *data) @@ -84,6 +79,10 @@ static irqreturn_t vl53l0x_trigger_handler(int irq, void *priv) struct vl53l0x_data *data = iio_priv(indio_dev); u8 buffer[12]; int ret; + struct { + u16 chan; + aligned_s64 timestamp; + } scan = { }; ret = i2c_smbus_read_i2c_block_data(data->client, VL_REG_RESULT_RANGE_STATUS, @@ -93,8 +92,8 @@ static irqreturn_t vl53l0x_trigger_handler(int irq, void *priv) else if (ret != 12) return -EREMOTEIO; - data->scan.chan = get_unaligned_be16(&buffer[10]); - iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + scan.chan = get_unaligned_be16(&buffer[10]); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), iio_get_time_ns(indio_dev)); iio_trigger_notify_done(indio_dev->trig); @@ -312,7 +311,6 @@ static int vl53l0x_probe(struct i2c_client *client) { struct vl53l0x_data *data; struct iio_dev *indio_dev; - int error; int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); @@ -345,15 +343,14 @@ static int vl53l0x_probe(struct i2c_client *client) return dev_err_probe(&client->dev, PTR_ERR(data->reset_gpio), "Cannot get reset GPIO\n"); - error = vl53l0x_power_on(data); - if (error) - return dev_err_probe(&client->dev, error, + ret = vl53l0x_power_on(data); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to power on the chip\n"); - error = devm_add_action_or_reset(&client->dev, vl53l0x_power_off, data); - if (error) - return dev_err_probe(&client->dev, error, - "Failed to install poweroff action\n"); + ret = devm_add_action_or_reset(&client->dev, vl53l0x_power_off, data); + if (ret) + return ret; indio_dev->name = "vl53l0x"; indio_dev->info = &vl53l0x_info; diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig index 1244d8e17d50..9328b2250ace 100644 --- a/drivers/iio/temperature/Kconfig +++ b/drivers/iio/temperature/Kconfig @@ -173,11 +173,13 @@ config MAX31865 will be called max31865. config MCP9600 - tristate "MCP9600 thermocouple EMF converter" + tristate "MCP9600 and similar thermocouple EMF converters" depends on I2C help - If you say yes here you get support for MCP9600 - thermocouple EMF converter connected via I2C. + If you say yes here you get support for... + - MCP9600 + - MCP9601 + ...thermocouple EMF converters connected via I2C. This driver can also be built as a module. If so, the module will be called mcp9600. diff --git a/drivers/iio/temperature/mcp9600.c b/drivers/iio/temperature/mcp9600.c index 6e9108d5cf75..aa42c2b1a369 100644 --- a/drivers/iio/temperature/mcp9600.c +++ b/drivers/iio/temperature/mcp9600.c @@ -22,26 +22,31 @@ #include <linux/iio/events.h> #include <linux/iio/iio.h> +#include <dt-bindings/iio/temperature/thermocouple.h> + /* MCP9600 registers */ -#define MCP9600_HOT_JUNCTION 0x0 -#define MCP9600_COLD_JUNCTION 0x2 -#define MCP9600_STATUS 0x4 +#define MCP9600_HOT_JUNCTION 0x00 +#define MCP9600_COLD_JUNCTION 0x02 +#define MCP9600_STATUS 0x04 #define MCP9600_STATUS_ALERT(x) BIT(x) -#define MCP9600_ALERT_CFG1 0x8 +#define MCP9600_SENSOR_CFG 0x05 +#define MCP9600_SENSOR_TYPE_MASK GENMASK(6, 4) +#define MCP9600_ALERT_CFG1 0x08 #define MCP9600_ALERT_CFG(x) (MCP9600_ALERT_CFG1 + (x - 1)) #define MCP9600_ALERT_CFG_ENABLE BIT(0) #define MCP9600_ALERT_CFG_ACTIVE_HIGH BIT(2) #define MCP9600_ALERT_CFG_FALLING BIT(3) #define MCP9600_ALERT_CFG_COLD_JUNCTION BIT(4) -#define MCP9600_ALERT_HYSTERESIS1 0xc +#define MCP9600_ALERT_HYSTERESIS1 0x0c #define MCP9600_ALERT_HYSTERESIS(x) (MCP9600_ALERT_HYSTERESIS1 + (x - 1)) #define MCP9600_ALERT_LIMIT1 0x10 #define MCP9600_ALERT_LIMIT(x) (MCP9600_ALERT_LIMIT1 + (x - 1)) #define MCP9600_ALERT_LIMIT_MASK GENMASK(15, 2) -#define MCP9600_DEVICE_ID 0x20 +#define MCP9600_DEVICE_ID 0x20 /* MCP9600 device id value */ -#define MCP9600_DEVICE_ID_MCP9600 0x40 +#define MCP9600_DEVICE_ID_MCP9600 0x40 +#define MCP9600_DEVICE_ID_MCP9601 0x41 #define MCP9600_ALERT_COUNT 4 @@ -65,6 +70,30 @@ static const char * const mcp9600_alert_name[MCP9600_ALERT_COUNT] = { [MCP9600_ALERT4] = "alert4", }; +/* Map between dt-bindings enum and the chip's type value */ +static const unsigned int mcp9600_type_map[] = { + [THERMOCOUPLE_TYPE_K] = 0, + [THERMOCOUPLE_TYPE_J] = 1, + [THERMOCOUPLE_TYPE_T] = 2, + [THERMOCOUPLE_TYPE_N] = 3, + [THERMOCOUPLE_TYPE_S] = 4, + [THERMOCOUPLE_TYPE_E] = 5, + [THERMOCOUPLE_TYPE_B] = 6, + [THERMOCOUPLE_TYPE_R] = 7, +}; + +/* Map thermocouple type to a char for iio info in sysfs */ +static const int mcp9600_tc_types[] = { + [THERMOCOUPLE_TYPE_K] = 'K', + [THERMOCOUPLE_TYPE_J] = 'J', + [THERMOCOUPLE_TYPE_T] = 'T', + [THERMOCOUPLE_TYPE_N] = 'N', + [THERMOCOUPLE_TYPE_S] = 'S', + [THERMOCOUPLE_TYPE_E] = 'E', + [THERMOCOUPLE_TYPE_B] = 'B', + [THERMOCOUPLE_TYPE_R] = 'R', +}; + static const struct iio_event_spec mcp9600_events[] = { { .type = IIO_EV_TYPE_THRESH, @@ -82,12 +111,41 @@ static const struct iio_event_spec mcp9600_events[] = { }, }; +struct mcp_chip_info { + u8 chip_id; + const char *chip_name; +}; + +struct mcp9600_data { + struct i2c_client *client; + u32 thermocouple_type; +}; + +static int mcp9600_config(struct mcp9600_data *data) +{ + struct i2c_client *client = data->client; + int ret; + u8 cfg; + + cfg = FIELD_PREP(MCP9600_SENSOR_TYPE_MASK, + mcp9600_type_map[data->thermocouple_type]); + + ret = i2c_smbus_write_byte_data(client, MCP9600_SENSOR_CFG, cfg); + if (ret < 0) { + dev_err(&client->dev, "Failed to set sensor configuration\n"); + return ret; + } + + return 0; +} + #define MCP9600_CHANNELS(hj_num_ev, hj_ev_spec_off, cj_num_ev, cj_ev_spec_off) \ { \ { \ .type = IIO_TEMP, \ .address = MCP9600_HOT_JUNCTION, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_THERMOCOUPLE_TYPE) | \ BIT(IIO_CHAN_INFO_SCALE), \ .event_spec = &mcp9600_events[hj_ev_spec_off], \ .num_event_specs = hj_num_ev, \ @@ -123,10 +181,6 @@ static const struct iio_chan_spec mcp9600_channels[][2] = { MCP9600_CHANNELS(2, 0, 2, 0), /* Alerts: 1 2 3 4 */ }; -struct mcp9600_data { - struct i2c_client *client; -}; - static int mcp9600_read(struct mcp9600_data *data, struct iio_chan_spec const *chan, int *val) { @@ -159,6 +213,9 @@ static int mcp9600_read_raw(struct iio_dev *indio_dev, *val = 62; *val2 = 500000; return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_THERMOCOUPLE_TYPE: + *val = mcp9600_tc_types[data->thermocouple_type]; + return IIO_VAL_CHAR; default: return -EINVAL; } @@ -416,45 +473,93 @@ static int mcp9600_probe_alerts(struct iio_dev *indio_dev) static int mcp9600_probe(struct i2c_client *client) { + struct device *dev = &client->dev; + const struct mcp_chip_info *chip_info; struct iio_dev *indio_dev; struct mcp9600_data *data; - int ret, ch_sel; + int ch_sel, dev_id, ret; + + chip_info = i2c_get_match_data(client); + if (!chip_info) + return dev_err_probe(dev, -ENODEV, + "No chip-info found for device\n"); + + dev_id = i2c_smbus_read_byte_data(client, MCP9600_DEVICE_ID); + if (dev_id < 0) + return dev_err_probe(dev, dev_id, "Failed to read device ID\n"); + + switch (dev_id) { + case MCP9600_DEVICE_ID_MCP9600: + case MCP9600_DEVICE_ID_MCP9601: + if (dev_id != chip_info->chip_id) + dev_warn(dev, + "Expected id %02x, but device responded with %02x\n", + chip_info->chip_id, dev_id); + break; - ret = i2c_smbus_read_byte_data(client, MCP9600_DEVICE_ID); - if (ret < 0) - return dev_err_probe(&client->dev, ret, "Failed to read device ID\n"); - if (ret != MCP9600_DEVICE_ID_MCP9600) - dev_warn(&client->dev, "Expected ID %x, got %x\n", - MCP9600_DEVICE_ID_MCP9600, ret); + default: + dev_warn(dev, "Unknown id %x, using %x\n", dev_id, + chip_info->chip_id); + } - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; data = iio_priv(indio_dev); data->client = client; + /* Accept type from dt with default of Type-K. */ + data->thermocouple_type = THERMOCOUPLE_TYPE_K; + ret = device_property_read_u32(dev, "thermocouple-type", + &data->thermocouple_type); + if (ret && ret != -EINVAL) + return dev_err_probe(dev, ret, + "Error reading thermocouple-type property\n"); + + if (data->thermocouple_type >= ARRAY_SIZE(mcp9600_type_map)) + return dev_err_probe(dev, -EINVAL, + "Invalid thermocouple-type property %u.\n", + data->thermocouple_type); + + /* Set initial config. */ + ret = mcp9600_config(data); + if (ret) + return ret; + ch_sel = mcp9600_probe_alerts(indio_dev); if (ch_sel < 0) return ch_sel; indio_dev->info = &mcp9600_info; - indio_dev->name = "mcp9600"; + indio_dev->name = chip_info->chip_name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = mcp9600_channels[ch_sel]; indio_dev->num_channels = ARRAY_SIZE(mcp9600_channels[ch_sel]); - return devm_iio_device_register(&client->dev, indio_dev); + return devm_iio_device_register(dev, indio_dev); } +static const struct mcp_chip_info mcp9600_chip_info = { + .chip_id = MCP9600_DEVICE_ID_MCP9600, + .chip_name = "mcp9600", +}; + +static const struct mcp_chip_info mcp9601_chip_info = { + .chip_id = MCP9600_DEVICE_ID_MCP9601, + .chip_name = "mcp9601", +}; + static const struct i2c_device_id mcp9600_id[] = { - { "mcp9600" }, + { "mcp9600", .driver_data = (kernel_ulong_t)&mcp9600_chip_info }, + { "mcp9601", .driver_data = (kernel_ulong_t)&mcp9601_chip_info }, { } }; MODULE_DEVICE_TABLE(i2c, mcp9600_id); static const struct of_device_id mcp9600_of_match[] = { - { .compatible = "microchip,mcp9600" }, + { .compatible = "microchip,mcp9600", .data = &mcp9600_chip_info }, + { .compatible = "microchip,mcp9601", .data = &mcp9601_chip_info }, { } }; MODULE_DEVICE_TABLE(of, mcp9600_of_match); diff --git a/drivers/iio/temperature/mlx90614.c b/drivers/iio/temperature/mlx90614.c index 740018d4b3df..8a44a00bfd5e 100644 --- a/drivers/iio/temperature/mlx90614.c +++ b/drivers/iio/temperature/mlx90614.c @@ -225,7 +225,6 @@ static void mlx90614_power_put(struct mlx90614_data *data) if (!data->wakeup_gpio) return; - pm_runtime_mark_last_busy(&data->client->dev); pm_runtime_put_autosuspend(&data->client->dev); } #else diff --git a/drivers/iio/temperature/mlx90632.c b/drivers/iio/temperature/mlx90632.c index ae4ea587e7f9..b44f7036c2cc 100644 --- a/drivers/iio/temperature/mlx90632.c +++ b/drivers/iio/temperature/mlx90632.c @@ -1043,7 +1043,6 @@ static int mlx90632_read_raw(struct iio_dev *indio_dev, } mlx90632_read_raw_pm: - pm_runtime_mark_last_busy(&data->client->dev); pm_runtime_put_autosuspend(&data->client->dev); return ret; } @@ -1178,10 +1177,8 @@ static int mlx90632_probe(struct i2c_client *client) int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mlx90632)); - if (!indio_dev) { - dev_err(&client->dev, "Failed to allocate device\n"); + if (!indio_dev) return -ENOMEM; - } regmap = devm_regmap_init_i2c(client, &mlx90632_regmap); if (IS_ERR(regmap)) { diff --git a/drivers/iio/temperature/mlx90635.c b/drivers/iio/temperature/mlx90635.c index f7f88498ba0e..1c8948ca54df 100644 --- a/drivers/iio/temperature/mlx90635.c +++ b/drivers/iio/temperature/mlx90635.c @@ -749,7 +749,6 @@ static int mlx90635_read_raw(struct iio_dev *indio_dev, } mlx90635_read_raw_pm: - pm_runtime_mark_last_busy(&data->client->dev); pm_runtime_put_autosuspend(&data->client->dev); return ret; } @@ -939,7 +938,7 @@ static int mlx90635_probe(struct i2c_client *client) indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mlx90635)); if (!indio_dev) - return dev_err_probe(&client->dev, -ENOMEM, "failed to allocate device\n"); + return -ENOMEM; regmap = devm_regmap_init_i2c(client, &mlx90635_regmap); if (IS_ERR(regmap)) @@ -977,8 +976,7 @@ static int mlx90635_probe(struct i2c_client *client) ret = devm_add_action_or_reset(&client->dev, mlx90635_disable_regulator, mlx90635); if (ret < 0) - return dev_err_probe(&client->dev, ret, - "failed to setup regulator cleanup action\n"); + return ret; ret = mlx90635_wakeup(mlx90635); if (ret < 0) @@ -986,8 +984,7 @@ static int mlx90635_probe(struct i2c_client *client) ret = devm_add_action_or_reset(&client->dev, mlx90635_sleep, mlx90635); if (ret < 0) - return dev_err_probe(&client->dev, ret, - "failed to setup low power cleanup\n"); + return ret; ret = regmap_read(mlx90635->regmap_ee, MLX90635_EE_VERSION, &dsp_version); if (ret < 0) diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig index 7a181cac3cc9..6e65e929791c 100644 --- a/drivers/iio/test/Kconfig +++ b/drivers/iio/test/Kconfig @@ -41,3 +41,15 @@ config IIO_FORMAT_KUNIT_TEST to the KUnit documentation in Documentation/dev-tools/kunit/. If unsure, say N. + +config IIO_MULTIPLY_KUNIT_TEST + tristate "Test IIO multiply functions" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + build unit tests for the IIO multiply functions. + + 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. diff --git a/drivers/iio/test/Makefile b/drivers/iio/test/Makefile index e9a4cf1ff57f..0c846bc21acd 100644 --- a/drivers/iio/test/Makefile +++ b/drivers/iio/test/Makefile @@ -7,4 +7,5 @@ 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 +obj-$(CONFIG_IIO_MULTIPLY_KUNIT_TEST) += iio-test-multiply.o CFLAGS_iio-test-format.o += $(DISABLE_STRUCTLEAK_PLUGIN) diff --git a/drivers/iio/test/iio-test-multiply.c b/drivers/iio/test/iio-test-multiply.c new file mode 100644 index 000000000000..432e279ffe5b --- /dev/null +++ b/drivers/iio/test/iio-test-multiply.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Unit tests for IIO multiply functions + * + * Copyright (c) 2025 Hans de Goede <hans@hansg.org> + * Based on iio-test-format.c which is: + * Copyright (c) 2020 Lars-Peter Clausen <lars@metafoo.de> + */ + +#include <kunit/test.h> +#include <linux/iio/consumer.h> +#include <linux/math64.h> +#include <linux/types.h> + +static void __iio_test_iio_multiply_value_integer(struct kunit *test, s64 multiplier) +{ + int ret, result, val; + + val = 42; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT, val, 0); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, multiplier * val); + + val = -23; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT, val, 0); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, multiplier * val); + + val = 0; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT, val, 0); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, multiplier * val); +} + +static void iio_test_iio_multiply_value_integer(struct kunit *test) +{ + __iio_test_iio_multiply_value_integer(test, 20); + __iio_test_iio_multiply_value_integer(test, -20); +} + +static void __iio_test_iio_multiply_value_fixedpoint(struct kunit *test, s64 multiplier) +{ + int ret, result, val, val2; + + /* positive >= 1 (1.5) */ + val = 1; + val2 = 500000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 15, 10)); + + val = 1; + val2 = 500000000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 15, 10)); + + /* positive < 1 (0.5) */ + val = 0; + val2 = 500000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 5, 10)); + + val = 0; + val2 = 500000000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 5, 10)); + + /* negative <= -1 (-1.5) */ + val = -1; + val2 = 500000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -15, 10)); + + val = -1; + val2 = 500000000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -15, 10)); + + /* negative > -1 (-0.5) */ + val = 0; + val2 = -500000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -5, 10)); + + val = 0; + val2 = -500000000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -5, 10)); +} + +static void iio_test_iio_multiply_value_fixedpoint(struct kunit *test) +{ + __iio_test_iio_multiply_value_fixedpoint(test, 20); + __iio_test_iio_multiply_value_fixedpoint(test, -20); +} + +static void __iio_test_iio_multiply_value_fractional(struct kunit *test, s64 multiplier) +{ + int ret, result, val, val2; + + /* positive < 1 (1/10)*/ + val = 1; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); + + /* positive >= 1 (100/3)*/ + val = 100; + val2 = 3; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); + + /* negative > -1 (-1/10) */ + val = -1; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); + + /* negative <= -1 (-200/3)*/ + val = -200; + val2 = 3; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); + + /* Zero (0/-10) */ + val = 0; + val2 = -10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); +} + +static void iio_test_iio_multiply_value_fractional(struct kunit *test) +{ + __iio_test_iio_multiply_value_fractional(test, 20); + __iio_test_iio_multiply_value_fractional(test, -20); +} + +static void __iio_test_iio_multiply_value_fractional_log2(struct kunit *test, s64 multiplier) +{ + int ret, result, val, val2; + + /* positive < 1 (123/1024) */ + val = 123; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); + + /* positive >= 1 (1234567/1024) */ + val = 1234567; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); + + /* negative > -1 (-123/1024) */ + val = -123; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); + + /* negative <= -1 (-1234567/1024) */ + val = -1234567; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); + + /* Zero (0/1024) */ + val = 0; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); +} + +static void iio_test_iio_multiply_value_fractional_log2(struct kunit *test) +{ + __iio_test_iio_multiply_value_fractional_log2(test, 20); + __iio_test_iio_multiply_value_fractional_log2(test, -20); +} + +static struct kunit_case iio_multiply_test_cases[] = { + KUNIT_CASE(iio_test_iio_multiply_value_integer), + KUNIT_CASE(iio_test_iio_multiply_value_fixedpoint), + KUNIT_CASE(iio_test_iio_multiply_value_fractional), + KUNIT_CASE(iio_test_iio_multiply_value_fractional_log2), + { } +}; + +static struct kunit_suite iio_multiply_test_suite = { + .name = "iio-multiply", + .test_cases = iio_multiply_test_cases, +}; +kunit_test_suite(iio_multiply_test_suite); + +MODULE_AUTHOR("Hans de Goede <hans@hansg.org>"); +MODULE_DESCRIPTION("Test IIO multiply functions"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_UNIT_TEST"); diff --git a/drivers/input/input-mt.c b/drivers/input/input-mt.c index 337006dd9dcf..09f518897d4a 100644 --- a/drivers/input/input-mt.c +++ b/drivers/input/input-mt.c @@ -198,6 +198,7 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count) struct input_mt *mt = dev->mt; struct input_mt_slot *oldest; int oldid, count, i; + int p, reported_p = 0; if (!mt) return; @@ -216,6 +217,13 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count) oldest = ps; oldid = id; } + if (test_bit(ABS_MT_PRESSURE, dev->absbit)) { + p = input_mt_get_value(ps, ABS_MT_PRESSURE); + if (mt->flags & INPUT_MT_TOTAL_FORCE) + reported_p += p; + else if (oldid == id) + reported_p = p; + } count++; } @@ -245,10 +253,8 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count) input_event(dev, EV_ABS, ABS_X, x); input_event(dev, EV_ABS, ABS_Y, y); - if (test_bit(ABS_MT_PRESSURE, dev->absbit)) { - int p = input_mt_get_value(oldest, ABS_MT_PRESSURE); - input_event(dev, EV_ABS, ABS_PRESSURE, p); - } + if (test_bit(ABS_MT_PRESSURE, dev->absbit)) + input_event(dev, EV_ABS, ABS_PRESSURE, reported_p); } else { if (test_bit(ABS_MT_PRESSURE, dev->absbit)) input_event(dev, EV_ABS, ABS_PRESSURE, 0); diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c index 3ebf37ddfc18..6cc979b26151 100644 --- a/drivers/interconnect/core.c +++ b/drivers/interconnect/core.c @@ -385,7 +385,7 @@ struct icc_node_data *of_icc_get_from_provider(const struct of_phandle_args *spe mutex_lock(&icc_lock); list_for_each_entry(provider, &icc_providers, provider_list) { - if (provider->dev->of_node == spec->np) { + if (device_match_of_node(provider->dev, spec->np)) { if (provider->xlate_extended) { data = provider->xlate_extended(spec, provider->data); if (!IS_ERR(data)) { diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig index 31dc4781abef..5b4bb9f1382b 100644 --- a/drivers/interconnect/qcom/Kconfig +++ b/drivers/interconnect/qcom/Kconfig @@ -8,6 +8,15 @@ config INTERCONNECT_QCOM config INTERCONNECT_QCOM_BCM_VOTER tristate +config INTERCONNECT_QCOM_GLYMUR + tristate "Qualcomm GLYMUR interconnect driver" + depends on INTERCONNECT_QCOM_RPMH_POSSIBLE + select INTERCONNECT_QCOM_RPMH + select INTERCONNECT_QCOM_BCM_VOTER + help + This is a driver for the Qualcomm Network-on-Chip on glymur-based + platforms. + config INTERCONNECT_QCOM_MSM8909 tristate "Qualcomm MSM8909 interconnect driver" depends on INTERCONNECT_QCOM diff --git a/drivers/interconnect/qcom/Makefile b/drivers/interconnect/qcom/Makefile index f16ac242eba5..cf8cba73ee3e 100644 --- a/drivers/interconnect/qcom/Makefile +++ b/drivers/interconnect/qcom/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_INTERCONNECT_QCOM) += interconnect_qcom.o interconnect_qcom-y := icc-common.o icc-bcm-voter-objs := bcm-voter.o +qnoc-glymur-objs := glymur.o qnoc-milos-objs := milos.o qnoc-msm8909-objs := msm8909.o qnoc-msm8916-objs := msm8916.o @@ -46,6 +47,7 @@ qnoc-x1e80100-objs := x1e80100.o icc-smd-rpm-objs := smd-rpm.o icc-rpm.o icc-rpm-clocks.o obj-$(CONFIG_INTERCONNECT_QCOM_BCM_VOTER) += icc-bcm-voter.o +obj-$(CONFIG_INTERCONNECT_QCOM_GLYMUR) += qnoc-glymur.o obj-$(CONFIG_INTERCONNECT_QCOM_MILOS) += qnoc-milos.o obj-$(CONFIG_INTERCONNECT_QCOM_MSM8909) += qnoc-msm8909.o obj-$(CONFIG_INTERCONNECT_QCOM_MSM8916) += qnoc-msm8916.o diff --git a/drivers/interconnect/qcom/glymur.c b/drivers/interconnect/qcom/glymur.c new file mode 100644 index 000000000000..cf20b5752dbb --- /dev/null +++ b/drivers/interconnect/qcom/glymur.c @@ -0,0 +1,2543 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved. + * + */ + +#include <linux/device.h> +#include <linux/interconnect.h> +#include <linux/interconnect-provider.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <dt-bindings/interconnect/qcom,glymur-rpmh.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, +}; + +static struct qcom_icc_node qup1_core_slave = { + .name = "qup1_core_slave", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qup2_core_slave = { + .name = "qup2_core_slave", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_ahb2phy0 = { + .name = "qhs_ahb2phy0", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_ahb2phy1 = { + .name = "qhs_ahb2phy1", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_ahb2phy2 = { + .name = "qhs_ahb2phy2", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_ahb2phy3 = { + .name = "qhs_ahb2phy3", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_av1_enc_cfg = { + .name = "qhs_av1_enc_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_camera_cfg = { + .name = "qhs_camera_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_clk_ctl = { + .name = "qhs_clk_ctl", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_crypto0_cfg = { + .name = "qhs_crypto0_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_display_cfg = { + .name = "qhs_display_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_gpuss_cfg = { + .name = "qhs_gpuss_cfg", + .channels = 1, + .buswidth = 8, +}; + +static struct qcom_icc_node qhs_imem_cfg = { + .name = "qhs_imem_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie0_cfg = { + .name = "qhs_pcie0_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie1_cfg = { + .name = "qhs_pcie1_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie2_cfg = { + .name = "qhs_pcie2_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie3a_cfg = { + .name = "qhs_pcie3a_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie3b_cfg = { + .name = "qhs_pcie3b_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie4_cfg = { + .name = "qhs_pcie4_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie5_cfg = { + .name = "qhs_pcie5_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie6_cfg = { + .name = "qhs_pcie6_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie_rscc = { + .name = "qhs_pcie_rscc", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pdm = { + .name = "qhs_pdm", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_prng = { + .name = "qhs_prng", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_qdss_cfg = { + .name = "qhs_qdss_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_qspi = { + .name = "qhs_qspi", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_qup0 = { + .name = "qhs_qup0", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_qup1 = { + .name = "qhs_qup1", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_qup2 = { + .name = "qhs_qup2", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_sdc2 = { + .name = "qhs_sdc2", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_sdc4 = { + .name = "qhs_sdc4", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_smmuv3_cfg = { + .name = "qhs_smmuv3_cfg", + .channels = 1, + .buswidth = 8, +}; + +static struct qcom_icc_node qhs_tcsr = { + .name = "qhs_tcsr", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_tlmm = { + .name = "qhs_tlmm", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_ufs_mem_cfg = { + .name = "qhs_ufs_mem_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb2_0_cfg = { + .name = "qhs_usb2_0_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb3_0_cfg = { + .name = "qhs_usb3_0_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb3_1_cfg = { + .name = "qhs_usb3_1_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb3_2_cfg = { + .name = "qhs_usb3_2_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb3_mp_cfg = { + .name = "qhs_usb3_mp_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb4_0_cfg = { + .name = "qhs_usb4_0_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb4_1_cfg = { + .name = "qhs_usb4_1_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb4_2_cfg = { + .name = "qhs_usb4_2_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_venus_cfg = { + .name = "qhs_venus_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qss_lpass_qtb_cfg = { + .name = "qss_lpass_qtb_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qss_nsp_qtb_cfg = { + .name = "qss_nsp_qtb_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node xs_qdss_stm = { + .name = "xs_qdss_stm", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node xs_sys_tcu_cfg = { + .name = "xs_sys_tcu_cfg", + .channels = 1, + .buswidth = 8, +}; + +static struct qcom_icc_node qhs_aoss = { + .name = "qhs_aoss", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_ipc_router = { + .name = "qhs_ipc_router", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_soccp = { + .name = "qhs_soccp", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_tme_cfg = { + .name = "qhs_tme_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qns_apss = { + .name = "qns_apss", + .channels = 1, + .buswidth = 8, +}; + +static struct qcom_icc_node qxs_boot_imem = { + .name = "qxs_boot_imem", + .channels = 1, + .buswidth = 16, +}; + +static struct qcom_icc_node qxs_imem = { + .name = "qxs_imem", + .channels = 1, + .buswidth = 8, +}; + +static struct qcom_icc_node ebi = { + .name = "ebi", + .channels = 12, + .buswidth = 4, +}; + +static struct qcom_icc_node srvc_mnoc = { + .name = "srvc_mnoc", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node srvc_nsinoc = { + .name = "srvc_nsinoc", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node srvc_pcie_east_aggre_noc = { + .name = "srvc_pcie_east_aggre_noc", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_hscnoc_pcie_east_ms_mpu_cfg = { + .name = "qhs_hscnoc_pcie_east_ms_mpu_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node srvc_pcie_east = { + .name = "srvc_pcie_east", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node xs_pcie_0 = { + .name = "xs_pcie_0", + .channels = 1, + .buswidth = 16, +}; + +static struct qcom_icc_node xs_pcie_1 = { + .name = "xs_pcie_1", + .channels = 1, + .buswidth = 32, +}; + +static struct qcom_icc_node xs_pcie_5 = { + .name = "xs_pcie_5", + .channels = 1, + .buswidth = 32, +}; + +static struct qcom_icc_node srvc_pcie_west_aggre_noc = { + .name = "srvc_pcie_west_aggre_noc", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_hscnoc_pcie_west_ms_mpu_cfg = { + .name = "qhs_hscnoc_pcie_west_ms_mpu_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node srvc_pcie_west = { + .name = "srvc_pcie_west", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node xs_pcie_2 = { + .name = "xs_pcie_2", + .channels = 1, + .buswidth = 16, +}; + +static struct qcom_icc_node xs_pcie_3a = { + .name = "xs_pcie_3a", + .channels = 1, + .buswidth = 64, +}; + +static struct qcom_icc_node xs_pcie_3b = { + .name = "xs_pcie_3b", + .channels = 1, + .buswidth = 32, +}; + +static struct qcom_icc_node xs_pcie_4 = { + .name = "xs_pcie_4", + .channels = 1, + .buswidth = 16, +}; + +static struct qcom_icc_node xs_pcie_6 = { + .name = "xs_pcie_6", + .channels = 1, + .buswidth = 16, +}; + +static struct qcom_icc_node qup0_core_master = { + .name = "qup0_core_master", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qup0_core_slave }, +}; + +static struct qcom_icc_node qup1_core_master = { + .name = "qup1_core_master", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qup1_core_slave }, +}; + +static struct qcom_icc_node qup2_core_master = { + .name = "qup2_core_master", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qup2_core_slave }, +}; + +static struct qcom_icc_node llcc_mc = { + .name = "llcc_mc", + .channels = 12, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &ebi }, +}; + +static struct qcom_icc_node qsm_mnoc_cfg = { + .name = "qsm_mnoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &srvc_mnoc }, +}; + +static struct qcom_icc_node qsm_pcie_east_anoc_cfg = { + .name = "qsm_pcie_east_anoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &srvc_pcie_east_aggre_noc }, +}; + +static struct qcom_icc_node qnm_hscnoc_pcie_east = { + .name = "qnm_hscnoc_pcie_east", + .channels = 1, + .buswidth = 32, + .num_links = 3, + .link_nodes = (struct qcom_icc_node *[]) { &xs_pcie_0, &xs_pcie_1, + &xs_pcie_5 }, +}; + +static struct qcom_icc_node qsm_cnoc_pcie_east_slave_cfg = { + .name = "qsm_cnoc_pcie_east_slave_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qhs_hscnoc_pcie_east_ms_mpu_cfg, + &srvc_pcie_east }, +}; + +static struct qcom_icc_node qsm_pcie_west_anoc_cfg = { + .name = "qsm_pcie_west_anoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &srvc_pcie_west_aggre_noc }, +}; + +static struct qcom_icc_node qnm_hscnoc_pcie_west = { + .name = "qnm_hscnoc_pcie_west", + .channels = 1, + .buswidth = 32, + .num_links = 5, + .link_nodes = (struct qcom_icc_node *[]) { &xs_pcie_2, &xs_pcie_3a, + &xs_pcie_3b, &xs_pcie_4, + &xs_pcie_6 }, +}; + +static struct qcom_icc_node qsm_cnoc_pcie_west_slave_cfg = { + .name = "qsm_cnoc_pcie_west_slave_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qhs_hscnoc_pcie_west_ms_mpu_cfg, + &srvc_pcie_west }, +}; + +static struct qcom_icc_node qss_cnoc_pcie_slave_east_cfg = { + .name = "qss_cnoc_pcie_slave_east_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qsm_cnoc_pcie_east_slave_cfg }, +}; + +static struct qcom_icc_node qss_cnoc_pcie_slave_west_cfg = { + .name = "qss_cnoc_pcie_slave_west_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qsm_cnoc_pcie_west_slave_cfg }, +}; + +static struct qcom_icc_node qss_mnoc_cfg = { + .name = "qss_mnoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qsm_mnoc_cfg }, +}; + +static struct qcom_icc_node qss_pcie_east_anoc_cfg = { + .name = "qss_pcie_east_anoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qsm_pcie_east_anoc_cfg }, +}; + +static struct qcom_icc_node qss_pcie_west_anoc_cfg = { + .name = "qss_pcie_west_anoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qsm_pcie_west_anoc_cfg }, +}; + +static struct qcom_icc_node qns_llcc = { + .name = "qns_llcc", + .channels = 12, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &llcc_mc }, +}; + +static struct qcom_icc_node qns_pcie_east = { + .name = "qns_pcie_east", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_hscnoc_pcie_east }, +}; + +static struct qcom_icc_node qns_pcie_west = { + .name = "qns_pcie_west", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_hscnoc_pcie_west }, +}; + +static struct qcom_icc_node qsm_cfg = { + .name = "qsm_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 51, + .link_nodes = (struct qcom_icc_node *[]) { &qhs_ahb2phy0, &qhs_ahb2phy1, + &qhs_ahb2phy2, &qhs_ahb2phy3, + &qhs_av1_enc_cfg, &qhs_camera_cfg, + &qhs_clk_ctl, &qhs_crypto0_cfg, + &qhs_display_cfg, &qhs_gpuss_cfg, + &qhs_imem_cfg, &qhs_pcie0_cfg, + &qhs_pcie1_cfg, &qhs_pcie2_cfg, + &qhs_pcie3a_cfg, &qhs_pcie3b_cfg, + &qhs_pcie4_cfg, &qhs_pcie5_cfg, + &qhs_pcie6_cfg, &qhs_pcie_rscc, + &qhs_pdm, &qhs_prng, + &qhs_qdss_cfg, &qhs_qspi, + &qhs_qup0, &qhs_qup1, + &qhs_qup2, &qhs_sdc2, + &qhs_sdc4, &qhs_smmuv3_cfg, + &qhs_tcsr, &qhs_tlmm, + &qhs_ufs_mem_cfg, &qhs_usb2_0_cfg, + &qhs_usb3_0_cfg, &qhs_usb3_1_cfg, + &qhs_usb3_2_cfg, &qhs_usb3_mp_cfg, + &qhs_usb4_0_cfg, &qhs_usb4_1_cfg, + &qhs_usb4_2_cfg, &qhs_venus_cfg, + &qss_cnoc_pcie_slave_east_cfg, &qss_cnoc_pcie_slave_west_cfg, + &qss_lpass_qtb_cfg, &qss_mnoc_cfg, + &qss_nsp_qtb_cfg, &qss_pcie_east_anoc_cfg, + &qss_pcie_west_anoc_cfg, &xs_qdss_stm, + &xs_sys_tcu_cfg }, +}; + +static struct qcom_icc_node xm_gic = { + .name = "xm_gic", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x33000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_llcc }, +}; + +static struct qcom_icc_node qss_cfg = { + .name = "qss_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qsm_cfg }, +}; + +static struct qcom_icc_node qnm_hscnoc_cnoc = { + .name = "qnm_hscnoc_cnoc", + .channels = 1, + .buswidth = 16, + .num_links = 8, + .link_nodes = (struct qcom_icc_node *[]) { &qhs_aoss, &qhs_ipc_router, + &qhs_soccp, &qhs_tme_cfg, + &qns_apss, &qss_cfg, + &qxs_boot_imem, &qxs_imem }, +}; + +static struct qcom_icc_node qns_hscnoc_cnoc = { + .name = "qns_hscnoc_cnoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_hscnoc_cnoc }, +}; + +static struct qcom_icc_node alm_gpu_tcu = { + .name = "alm_gpu_tcu", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x933000 }, + .prio = 1, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc }, +}; + +static struct qcom_icc_node alm_pcie_qtc = { + .name = "alm_pcie_qtc", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x51f000 }, + .prio = 3, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc }, +}; + +static struct qcom_icc_node alm_sys_tcu = { + .name = "alm_sys_tcu", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x51f080 }, + .prio = 6, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc }, +}; + +static struct qcom_icc_node chm_apps = { + .name = "chm_apps", + .channels = 6, + .buswidth = 32, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_aggre_noc_east = { + .name = "qnm_aggre_noc_east", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x934000 }, + .prio = 2, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_gpu = { + .name = "qnm_gpu", + .channels = 4, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 4, + .port_offsets = { 0x935000, 0x936000, 0x937000, 0x938000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_lpass = { + .name = "qnm_lpass", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x939000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_mnoc_hf = { + .name = "qnm_mnoc_hf", + .channels = 2, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 2, + .port_offsets = { 0x721000, 0x721080 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_mnoc_sf = { + .name = "qnm_mnoc_sf", + .channels = 2, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 2, + .port_offsets = { 0x721100, 0x721180 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_nsp_noc = { + .name = "qnm_nsp_noc", + .channels = 4, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 4, + .port_offsets = { 0x816000, 0x816080, 0x816100, 0x816180 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_pcie_east = { + .name = "qnm_pcie_east", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x93a000 }, + .prio = 2, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc }, +}; + +static struct qcom_icc_node qnm_pcie_west = { + .name = "qnm_pcie_west", + .channels = 1, + .buswidth = 64, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x721200 }, + .prio = 2, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc }, +}; + +static struct qcom_icc_node qnm_snoc_sf = { + .name = "qnm_snoc_sf", + .channels = 1, + .buswidth = 64, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x51f100 }, + .prio = 2, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qxm_wlan_q6 = { + .name = "qxm_wlan_q6", + .channels = 1, + .buswidth = 8, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qns_a4noc_hscnoc = { + .name = "qns_a4noc_hscnoc", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_aggre_noc_east }, +}; + +static struct qcom_icc_node qns_lpass_ag_noc_gemnoc = { + .name = "qns_lpass_ag_noc_gemnoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_lpass }, +}; + +static struct qcom_icc_node qns_mem_noc_hf = { + .name = "qns_mem_noc_hf", + .channels = 2, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_mnoc_hf }, +}; + +static struct qcom_icc_node qns_mem_noc_sf = { + .name = "qns_mem_noc_sf", + .channels = 2, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_mnoc_sf }, +}; + +static struct qcom_icc_node qns_nsp_hscnoc = { + .name = "qns_nsp_hscnoc", + .channels = 4, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_nsp_noc }, +}; + +static struct qcom_icc_node qns_pcie_east_mem_noc = { + .name = "qns_pcie_east_mem_noc", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_pcie_east }, +}; + +static struct qcom_icc_node qns_pcie_west_mem_noc = { + .name = "qns_pcie_west_mem_noc", + .channels = 1, + .buswidth = 64, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_pcie_west }, +}; + +static struct qcom_icc_node qns_gemnoc_sf = { + .name = "qns_gemnoc_sf", + .channels = 1, + .buswidth = 64, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_snoc_sf }, +}; + +static struct qcom_icc_node xm_usb3_0 = { + .name = "xm_usb3_0", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xa000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a4noc_hscnoc }, +}; + +static struct qcom_icc_node xm_usb3_1 = { + .name = "xm_usb3_1", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xb000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a4noc_hscnoc }, +}; + +static struct qcom_icc_node xm_usb4_0 = { + .name = "xm_usb4_0", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xc000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a4noc_hscnoc }, +}; + +static struct qcom_icc_node xm_usb4_1 = { + .name = "xm_usb4_1", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a4noc_hscnoc }, +}; + +static struct qcom_icc_node qnm_lpiaon_noc = { + .name = "qnm_lpiaon_noc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_lpass_ag_noc_gemnoc }, +}; + +static struct qcom_icc_node qnm_av1_enc = { + .name = "qnm_av1_enc", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x30000 }, + .prio = 4, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_camnoc_hf = { + .name = "qnm_camnoc_hf", + .channels = 2, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 2, + .port_offsets = { 0x29000, 0x2a000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_hf }, +}; + +static struct qcom_icc_node qnm_camnoc_icp = { + .name = "qnm_camnoc_icp", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x2b000 }, + .prio = 4, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_camnoc_sf = { + .name = "qnm_camnoc_sf", + .channels = 2, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 2, + .port_offsets = { 0x2c000, 0x2d000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_eva = { + .name = "qnm_eva", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x34000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_mdp = { + .name = "qnm_mdp", + .channels = 2, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 2, + .port_offsets = { 0x2e000, 0x2f000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_hf }, +}; + +static struct qcom_icc_node qnm_vapss_hcp = { + .name = "qnm_vapss_hcp", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_video = { + .name = "qnm_video", + .channels = 4, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 4, + .port_offsets = { 0x31000, 0x32000, 0x37000, 0x38000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_video_cv_cpu = { + .name = "qnm_video_cv_cpu", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x33000 }, + .prio = 4, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_video_v_cpu = { + .name = "qnm_video_v_cpu", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x35000 }, + .prio = 4, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_nsp = { + .name = "qnm_nsp", + .channels = 4, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_nsp_hscnoc }, +}; + +static struct qcom_icc_node xm_pcie_0 = { + .name = "xm_pcie_0", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xb000 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_east_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_1 = { + .name = "xm_pcie_1", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xc000 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_east_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_5 = { + .name = "xm_pcie_5", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd000 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_east_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_2 = { + .name = "xm_pcie_2", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd000 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_3a = { + .name = "xm_pcie_3a", + .channels = 1, + .buswidth = 64, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd200 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_3b = { + .name = "xm_pcie_3b", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd400 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_4 = { + .name = "xm_pcie_4", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd600 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_6 = { + .name = "xm_pcie_6", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd800 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc }, +}; + +static struct qcom_icc_node qnm_aggre1_noc = { + .name = "qnm_aggre1_noc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf }, +}; + +static struct qcom_icc_node qnm_aggre2_noc = { + .name = "qnm_aggre2_noc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf }, +}; + +static struct qcom_icc_node qnm_aggre3_noc = { + .name = "qnm_aggre3_noc", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf }, +}; + +static struct qcom_icc_node qnm_nsi_noc = { + .name = "qnm_nsi_noc", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x1c000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf }, +}; + +static struct qcom_icc_node qnm_oobmss = { + .name = "qnm_oobmss", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x1b000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf }, +}; + +static struct qcom_icc_node qns_a1noc_snoc = { + .name = "qns_a1noc_snoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_aggre1_noc }, +}; + +static struct qcom_icc_node qns_a2noc_snoc = { + .name = "qns_a2noc_snoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_aggre2_noc }, +}; + +static struct qcom_icc_node qns_a3noc_snoc = { + .name = "qns_a3noc_snoc", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_aggre3_noc }, +}; + +static struct qcom_icc_node qns_lpass_aggnoc = { + .name = "qns_lpass_aggnoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_lpiaon_noc }, +}; + +static struct qcom_icc_node qns_system_noc = { + .name = "qns_system_noc", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_nsi_noc }, +}; + +static struct qcom_icc_node qns_oobmss_snoc = { + .name = "qns_oobmss_snoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_oobmss }, +}; + +static struct qcom_icc_node qxm_crypto = { + .name = "qxm_crypto", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xb000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a1noc_snoc }, +}; + +static struct qcom_icc_node qxm_soccp = { + .name = "qxm_soccp", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xe000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a1noc_snoc }, +}; + +static struct qcom_icc_node xm_qdss_etr_0 = { + .name = "xm_qdss_etr_0", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xc000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a1noc_snoc }, +}; + +static struct qcom_icc_node xm_qdss_etr_1 = { + .name = "xm_qdss_etr_1", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a1noc_snoc }, +}; + +static struct qcom_icc_node xm_ufs_mem = { + .name = "xm_ufs_mem", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xa000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a2noc_snoc }, +}; + +static struct qcom_icc_node xm_usb3_2 = { + .name = "xm_usb3_2", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x8000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a2noc_snoc }, +}; + +static struct qcom_icc_node xm_usb4_2 = { + .name = "xm_usb4_2", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x9000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a2noc_snoc }, +}; + +static struct qcom_icc_node qhm_qspi = { + .name = "qhm_qspi", + .channels = 1, + .buswidth = 4, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x10000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node qhm_qup0 = { + .name = "qhm_qup0", + .channels = 1, + .buswidth = 4, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x11000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node qhm_qup1 = { + .name = "qhm_qup1", + .channels = 1, + .buswidth = 4, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x12000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node qhm_qup2 = { + .name = "qhm_qup2", + .channels = 1, + .buswidth = 4, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x13000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node qxm_sp = { + .name = "qxm_sp", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node xm_sdc2 = { + .name = "xm_sdc2", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x18000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node xm_sdc4 = { + .name = "xm_sdc4", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x14000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node xm_usb2_0 = { + .name = "xm_usb2_0", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x15000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node xm_usb3_mp = { + .name = "xm_usb3_mp", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x16000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node qnm_lpass_lpinoc = { + .name = "qnm_lpass_lpinoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_lpass_aggnoc }, +}; + +static struct qcom_icc_node xm_cpucp = { + .name = "xm_cpucp", + .channels = 1, + .buswidth = 8, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qns_system_noc, &srvc_nsinoc }, +}; + +static struct qcom_icc_node xm_mem_sp = { + .name = "xm_mem_sp", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_oobmss_snoc }, +}; + +static struct qcom_icc_node qns_lpi_aon_noc = { + .name = "qns_lpi_aon_noc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_lpass_lpinoc }, +}; + +static struct qcom_icc_node qnm_lpinoc_dsp_qns4m = { + .name = "qnm_lpinoc_dsp_qns4m", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_lpi_aon_noc }, +}; + +static struct qcom_icc_bcm bcm_acv = { + .name = "ACV", + .enable_mask = BIT(3), + .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 = BIT(0), + .num_nodes = 60, + .nodes = { &qsm_cfg, &qhs_ahb2phy0, + &qhs_ahb2phy1, &qhs_ahb2phy2, + &qhs_ahb2phy3, &qhs_av1_enc_cfg, + &qhs_camera_cfg, &qhs_clk_ctl, + &qhs_crypto0_cfg, &qhs_gpuss_cfg, + &qhs_imem_cfg, &qhs_pcie0_cfg, + &qhs_pcie1_cfg, &qhs_pcie2_cfg, + &qhs_pcie3a_cfg, &qhs_pcie3b_cfg, + &qhs_pcie4_cfg, &qhs_pcie5_cfg, + &qhs_pcie6_cfg, &qhs_pcie_rscc, + &qhs_pdm, &qhs_prng, + &qhs_qdss_cfg, &qhs_qspi, + &qhs_qup0, &qhs_qup1, + &qhs_qup2, &qhs_sdc2, + &qhs_sdc4, &qhs_smmuv3_cfg, + &qhs_tcsr, &qhs_tlmm, + &qhs_ufs_mem_cfg, &qhs_usb2_0_cfg, + &qhs_usb3_0_cfg, &qhs_usb3_1_cfg, + &qhs_usb3_2_cfg, &qhs_usb3_mp_cfg, + &qhs_usb4_0_cfg, &qhs_usb4_1_cfg, + &qhs_usb4_2_cfg, &qhs_venus_cfg, + &qss_cnoc_pcie_slave_east_cfg, &qss_cnoc_pcie_slave_west_cfg, + &qss_lpass_qtb_cfg, &qss_mnoc_cfg, + &qss_nsp_qtb_cfg, &qss_pcie_east_anoc_cfg, + &qss_pcie_west_anoc_cfg, &xs_qdss_stm, + &xs_sys_tcu_cfg, &qnm_hscnoc_cnoc, + &qhs_aoss, &qhs_ipc_router, + &qhs_soccp, &qhs_tme_cfg, + &qns_apss, &qss_cfg, + &qxs_boot_imem, &qxs_imem }, +}; + +static struct qcom_icc_bcm bcm_cn1 = { + .name = "CN1", + .num_nodes = 1, + .nodes = { &qhs_display_cfg }, +}; + +static struct qcom_icc_bcm bcm_co0 = { + .name = "CO0", + .enable_mask = BIT(0), + .num_nodes = 2, + .nodes = { &qnm_nsp, &qns_nsp_hscnoc }, +}; + +static struct qcom_icc_bcm bcm_lp0 = { + .name = "LP0", + .num_nodes = 2, + .nodes = { &qnm_lpass_lpinoc, &qns_lpass_aggnoc }, +}; + +static struct qcom_icc_bcm bcm_mc0 = { + .name = "MC0", + .keepalive = true, + .num_nodes = 1, + .nodes = { &ebi }, +}; + +static struct qcom_icc_bcm bcm_mm0 = { + .name = "MM0", + .num_nodes = 1, + .nodes = { &qns_mem_noc_hf }, +}; + +static struct qcom_icc_bcm bcm_mm1 = { + .name = "MM1", + .enable_mask = BIT(0), + .num_nodes = 11, + .nodes = { &qnm_av1_enc, &qnm_camnoc_hf, + &qnm_camnoc_icp, &qnm_camnoc_sf, + &qnm_eva, &qnm_mdp, + &qnm_vapss_hcp, &qnm_video, + &qnm_video_cv_cpu, &qnm_video_v_cpu, + &qns_mem_noc_sf }, +}; + +static struct qcom_icc_bcm bcm_qup0 = { + .name = "QUP0", + .vote_scale = 1, + .keepalive = true, + .num_nodes = 1, + .nodes = { &qup0_core_slave }, +}; + +static struct qcom_icc_bcm bcm_qup1 = { + .name = "QUP1", + .vote_scale = 1, + .keepalive = true, + .num_nodes = 1, + .nodes = { &qup1_core_slave }, +}; + +static struct qcom_icc_bcm bcm_qup2 = { + .name = "QUP2", + .vote_scale = 1, + .keepalive = true, + .num_nodes = 1, + .nodes = { &qup2_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 = BIT(0), + .num_nodes = 18, + .nodes = { &alm_gpu_tcu, &alm_pcie_qtc, + &alm_sys_tcu, &chm_apps, + &qnm_aggre_noc_east, &qnm_gpu, + &qnm_lpass, &qnm_mnoc_hf, + &qnm_mnoc_sf, &qnm_nsp_noc, + &qnm_pcie_east, &qnm_pcie_west, + &qnm_snoc_sf, &qxm_wlan_q6, + &xm_gic, &qns_hscnoc_cnoc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_bcm bcm_sn0 = { + .name = "SN0", + .keepalive = true, + .num_nodes = 1, + .nodes = { &qns_gemnoc_sf }, +}; + +static struct qcom_icc_bcm bcm_sn1 = { + .name = "SN1", + .enable_mask = BIT(0), + .num_nodes = 1, + .nodes = { &qnm_oobmss }, +}; + +static struct qcom_icc_bcm bcm_sn2 = { + .name = "SN2", + .num_nodes = 1, + .nodes = { &qnm_aggre1_noc }, +}; + +static struct qcom_icc_bcm bcm_sn3 = { + .name = "SN3", + .num_nodes = 1, + .nodes = { &qnm_aggre2_noc }, +}; + +static struct qcom_icc_bcm bcm_sn4 = { + .name = "SN4", + .num_nodes = 1, + .nodes = { &qnm_aggre3_noc }, +}; + +static struct qcom_icc_bcm bcm_sn5 = { + .name = "SN5", + .num_nodes = 1, + .nodes = { &qns_a4noc_hscnoc }, +}; + +static struct qcom_icc_bcm bcm_sn6 = { + .name = "SN6", + .num_nodes = 4, + .nodes = { &qns_pcie_east_mem_noc, &qnm_hscnoc_pcie_east, + &qns_pcie_west_mem_noc, &qnm_hscnoc_pcie_west }, +}; + +static struct qcom_icc_bcm * const aggre1_noc_bcms[] = { + &bcm_ce0, +}; + +static struct qcom_icc_node * const aggre1_noc_nodes[] = { + [MASTER_CRYPTO] = &qxm_crypto, + [MASTER_SOCCP_PROC] = &qxm_soccp, + [MASTER_QDSS_ETR] = &xm_qdss_etr_0, + [MASTER_QDSS_ETR_1] = &xm_qdss_etr_1, + [SLAVE_A1NOC_SNOC] = &qns_a1noc_snoc, +}; + +static const struct regmap_config glymur_aggre1_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x14400, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_aggre1_noc = { + .config = &glymur_aggre1_noc_regmap_config, + .nodes = aggre1_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre1_noc_nodes), + .bcms = aggre1_noc_bcms, + .num_bcms = ARRAY_SIZE(aggre1_noc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_node * const aggre2_noc_nodes[] = { + [MASTER_UFS_MEM] = &xm_ufs_mem, + [MASTER_USB3_2] = &xm_usb3_2, + [MASTER_USB4_2] = &xm_usb4_2, + [SLAVE_A2NOC_SNOC] = &qns_a2noc_snoc, +}; + +static const struct regmap_config glymur_aggre2_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x14400, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_aggre2_noc = { + .config = &glymur_aggre2_noc_regmap_config, + .nodes = aggre2_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre2_noc_nodes), + .alloc_dyn_id = true, + .qos_requires_clocks = true, +}; + +static struct qcom_icc_node * const aggre3_noc_nodes[] = { + [MASTER_QSPI_0] = &qhm_qspi, + [MASTER_QUP_0] = &qhm_qup0, + [MASTER_QUP_1] = &qhm_qup1, + [MASTER_QUP_2] = &qhm_qup2, + [MASTER_SP] = &qxm_sp, + [MASTER_SDCC_2] = &xm_sdc2, + [MASTER_SDCC_4] = &xm_sdc4, + [MASTER_USB2] = &xm_usb2_0, + [MASTER_USB3_MP] = &xm_usb3_mp, + [SLAVE_A3NOC_SNOC] = &qns_a3noc_snoc, +}; + +static const struct regmap_config glymur_aggre3_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1d400, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_aggre3_noc = { + .config = &glymur_aggre3_noc_regmap_config, + .nodes = aggre3_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre3_noc_nodes), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const aggre4_noc_bcms[] = { + &bcm_sn5, +}; + +static struct qcom_icc_node * const aggre4_noc_nodes[] = { + [MASTER_USB3_0] = &xm_usb3_0, + [MASTER_USB3_1] = &xm_usb3_1, + [MASTER_USB4_0] = &xm_usb4_0, + [MASTER_USB4_1] = &xm_usb4_1, + [SLAVE_A4NOC_HSCNOC] = &qns_a4noc_hscnoc, +}; + +static const struct regmap_config glymur_aggre4_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x14400, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_aggre4_noc = { + .config = &glymur_aggre4_noc_regmap_config, + .nodes = aggre4_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre4_noc_nodes), + .bcms = aggre4_noc_bcms, + .num_bcms = ARRAY_SIZE(aggre4_noc_bcms), + .alloc_dyn_id = true, + .qos_requires_clocks = true, +}; + +static struct qcom_icc_bcm * const clk_virt_bcms[] = { + &bcm_qup0, + &bcm_qup1, + &bcm_qup2, +}; + +static struct qcom_icc_node * const clk_virt_nodes[] = { + [MASTER_QUP_CORE_0] = &qup0_core_master, + [MASTER_QUP_CORE_1] = &qup1_core_master, + [MASTER_QUP_CORE_2] = &qup2_core_master, + [SLAVE_QUP_CORE_0] = &qup0_core_slave, + [SLAVE_QUP_CORE_1] = &qup1_core_slave, + [SLAVE_QUP_CORE_2] = &qup2_core_slave, +}; + +static const struct qcom_icc_desc glymur_clk_virt = { + .nodes = clk_virt_nodes, + .num_nodes = ARRAY_SIZE(clk_virt_nodes), + .bcms = clk_virt_bcms, + .num_bcms = ARRAY_SIZE(clk_virt_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const cnoc_cfg_bcms[] = { + &bcm_cn0, + &bcm_cn1, +}; + +static struct qcom_icc_node * const cnoc_cfg_nodes[] = { + [MASTER_CNOC_CFG] = &qsm_cfg, + [SLAVE_AHB2PHY_SOUTH] = &qhs_ahb2phy0, + [SLAVE_AHB2PHY_NORTH] = &qhs_ahb2phy1, + [SLAVE_AHB2PHY_2] = &qhs_ahb2phy2, + [SLAVE_AHB2PHY_3] = &qhs_ahb2phy3, + [SLAVE_AV1_ENC_CFG] = &qhs_av1_enc_cfg, + [SLAVE_CAMERA_CFG] = &qhs_camera_cfg, + [SLAVE_CLK_CTL] = &qhs_clk_ctl, + [SLAVE_CRYPTO_0_CFG] = &qhs_crypto0_cfg, + [SLAVE_DISPLAY_CFG] = &qhs_display_cfg, + [SLAVE_GFX3D_CFG] = &qhs_gpuss_cfg, + [SLAVE_IMEM_CFG] = &qhs_imem_cfg, + [SLAVE_PCIE_0_CFG] = &qhs_pcie0_cfg, + [SLAVE_PCIE_1_CFG] = &qhs_pcie1_cfg, + [SLAVE_PCIE_2_CFG] = &qhs_pcie2_cfg, + [SLAVE_PCIE_3A_CFG] = &qhs_pcie3a_cfg, + [SLAVE_PCIE_3B_CFG] = &qhs_pcie3b_cfg, + [SLAVE_PCIE_4_CFG] = &qhs_pcie4_cfg, + [SLAVE_PCIE_5_CFG] = &qhs_pcie5_cfg, + [SLAVE_PCIE_6_CFG] = &qhs_pcie6_cfg, + [SLAVE_PCIE_RSCC] = &qhs_pcie_rscc, + [SLAVE_PDM] = &qhs_pdm, + [SLAVE_PRNG] = &qhs_prng, + [SLAVE_QDSS_CFG] = &qhs_qdss_cfg, + [SLAVE_QSPI_0] = &qhs_qspi, + [SLAVE_QUP_0] = &qhs_qup0, + [SLAVE_QUP_1] = &qhs_qup1, + [SLAVE_QUP_2] = &qhs_qup2, + [SLAVE_SDCC_2] = &qhs_sdc2, + [SLAVE_SDCC_4] = &qhs_sdc4, + [SLAVE_SMMUV3_CFG] = &qhs_smmuv3_cfg, + [SLAVE_TCSR] = &qhs_tcsr, + [SLAVE_TLMM] = &qhs_tlmm, + [SLAVE_UFS_MEM_CFG] = &qhs_ufs_mem_cfg, + [SLAVE_USB2] = &qhs_usb2_0_cfg, + [SLAVE_USB3_0] = &qhs_usb3_0_cfg, + [SLAVE_USB3_1] = &qhs_usb3_1_cfg, + [SLAVE_USB3_2] = &qhs_usb3_2_cfg, + [SLAVE_USB3_MP] = &qhs_usb3_mp_cfg, + [SLAVE_USB4_0] = &qhs_usb4_0_cfg, + [SLAVE_USB4_1] = &qhs_usb4_1_cfg, + [SLAVE_USB4_2] = &qhs_usb4_2_cfg, + [SLAVE_VENUS_CFG] = &qhs_venus_cfg, + [SLAVE_CNOC_PCIE_SLAVE_EAST_CFG] = &qss_cnoc_pcie_slave_east_cfg, + [SLAVE_CNOC_PCIE_SLAVE_WEST_CFG] = &qss_cnoc_pcie_slave_west_cfg, + [SLAVE_LPASS_QTB_CFG] = &qss_lpass_qtb_cfg, + [SLAVE_CNOC_MNOC_CFG] = &qss_mnoc_cfg, + [SLAVE_NSP_QTB_CFG] = &qss_nsp_qtb_cfg, + [SLAVE_PCIE_EAST_ANOC_CFG] = &qss_pcie_east_anoc_cfg, + [SLAVE_PCIE_WEST_ANOC_CFG] = &qss_pcie_west_anoc_cfg, + [SLAVE_QDSS_STM] = &xs_qdss_stm, + [SLAVE_TCU] = &xs_sys_tcu_cfg, +}; + +static const struct regmap_config glymur_cnoc_cfg_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x6600, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_cnoc_cfg = { + .config = &glymur_cnoc_cfg_regmap_config, + .nodes = cnoc_cfg_nodes, + .num_nodes = ARRAY_SIZE(cnoc_cfg_nodes), + .bcms = cnoc_cfg_bcms, + .num_bcms = ARRAY_SIZE(cnoc_cfg_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const cnoc_main_bcms[] = { + &bcm_cn0, +}; + +static struct qcom_icc_node * const cnoc_main_nodes[] = { + [MASTER_HSCNOC_CNOC] = &qnm_hscnoc_cnoc, + [SLAVE_AOSS] = &qhs_aoss, + [SLAVE_IPC_ROUTER_CFG] = &qhs_ipc_router, + [SLAVE_SOCCP] = &qhs_soccp, + [SLAVE_TME_CFG] = &qhs_tme_cfg, + [SLAVE_APPSS] = &qns_apss, + [SLAVE_CNOC_CFG] = &qss_cfg, + [SLAVE_BOOT_IMEM] = &qxs_boot_imem, + [SLAVE_IMEM] = &qxs_imem, +}; + +static const struct regmap_config glymur_cnoc_main_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x17080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_cnoc_main = { + .config = &glymur_cnoc_main_regmap_config, + .nodes = cnoc_main_nodes, + .num_nodes = ARRAY_SIZE(cnoc_main_nodes), + .bcms = cnoc_main_bcms, + .num_bcms = ARRAY_SIZE(cnoc_main_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const hscnoc_bcms[] = { + &bcm_sh0, + &bcm_sh1, +}; + +static struct qcom_icc_node * const hscnoc_nodes[] = { + [MASTER_GPU_TCU] = &alm_gpu_tcu, + [MASTER_PCIE_TCU] = &alm_pcie_qtc, + [MASTER_SYS_TCU] = &alm_sys_tcu, + [MASTER_APPSS_PROC] = &chm_apps, + [MASTER_AGGRE_NOC_EAST] = &qnm_aggre_noc_east, + [MASTER_GFX3D] = &qnm_gpu, + [MASTER_LPASS_GEM_NOC] = &qnm_lpass, + [MASTER_MNOC_HF_MEM_NOC] = &qnm_mnoc_hf, + [MASTER_MNOC_SF_MEM_NOC] = &qnm_mnoc_sf, + [MASTER_COMPUTE_NOC] = &qnm_nsp_noc, + [MASTER_PCIE_EAST] = &qnm_pcie_east, + [MASTER_PCIE_WEST] = &qnm_pcie_west, + [MASTER_SNOC_SF_MEM_NOC] = &qnm_snoc_sf, + [MASTER_WLAN_Q6] = &qxm_wlan_q6, + [MASTER_GIC] = &xm_gic, + [SLAVE_HSCNOC_CNOC] = &qns_hscnoc_cnoc, + [SLAVE_LLCC] = &qns_llcc, + [SLAVE_PCIE_EAST] = &qns_pcie_east, + [SLAVE_PCIE_WEST] = &qns_pcie_west, +}; + +static const struct regmap_config glymur_hscnoc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x93a080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_hscnoc = { + .config = &glymur_hscnoc_regmap_config, + .nodes = hscnoc_nodes, + .num_nodes = ARRAY_SIZE(hscnoc_nodes), + .bcms = hscnoc_bcms, + .num_bcms = ARRAY_SIZE(hscnoc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_node * const lpass_ag_noc_nodes[] = { + [MASTER_LPIAON_NOC] = &qnm_lpiaon_noc, + [SLAVE_LPASS_GEM_NOC] = &qns_lpass_ag_noc_gemnoc, +}; + +static const struct regmap_config glymur_lpass_ag_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xe080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_lpass_ag_noc = { + .config = &glymur_lpass_ag_noc_regmap_config, + .nodes = lpass_ag_noc_nodes, + .num_nodes = ARRAY_SIZE(lpass_ag_noc_nodes), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const lpass_lpiaon_noc_bcms[] = { + &bcm_lp0, +}; + +static struct qcom_icc_node * const lpass_lpiaon_noc_nodes[] = { + [MASTER_LPASS_LPINOC] = &qnm_lpass_lpinoc, + [SLAVE_LPIAON_NOC_LPASS_AG_NOC] = &qns_lpass_aggnoc, +}; + +static const struct regmap_config glymur_lpass_lpiaon_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x19080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_lpass_lpiaon_noc = { + .config = &glymur_lpass_lpiaon_noc_regmap_config, + .nodes = lpass_lpiaon_noc_nodes, + .num_nodes = ARRAY_SIZE(lpass_lpiaon_noc_nodes), + .bcms = lpass_lpiaon_noc_bcms, + .num_bcms = ARRAY_SIZE(lpass_lpiaon_noc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_node * const lpass_lpicx_noc_nodes[] = { + [MASTER_LPASS_PROC] = &qnm_lpinoc_dsp_qns4m, + [SLAVE_LPICX_NOC_LPIAON_NOC] = &qns_lpi_aon_noc, +}; + +static const struct regmap_config glymur_lpass_lpicx_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x44080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_lpass_lpicx_noc = { + .config = &glymur_lpass_lpicx_noc_regmap_config, + .nodes = lpass_lpicx_noc_nodes, + .num_nodes = ARRAY_SIZE(lpass_lpicx_noc_nodes), + .alloc_dyn_id = true, +}; + +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 glymur_mc_virt = { + .nodes = mc_virt_nodes, + .num_nodes = ARRAY_SIZE(mc_virt_nodes), + .bcms = mc_virt_bcms, + .num_bcms = ARRAY_SIZE(mc_virt_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const mmss_noc_bcms[] = { + &bcm_mm0, + &bcm_mm1, +}; + +static struct qcom_icc_node * const mmss_noc_nodes[] = { + [MASTER_AV1_ENC] = &qnm_av1_enc, + [MASTER_CAMNOC_HF] = &qnm_camnoc_hf, + [MASTER_CAMNOC_ICP] = &qnm_camnoc_icp, + [MASTER_CAMNOC_SF] = &qnm_camnoc_sf, + [MASTER_EVA] = &qnm_eva, + [MASTER_MDP] = &qnm_mdp, + [MASTER_CDSP_HCP] = &qnm_vapss_hcp, + [MASTER_VIDEO] = &qnm_video, + [MASTER_VIDEO_CV_PROC] = &qnm_video_cv_cpu, + [MASTER_VIDEO_V_PROC] = &qnm_video_v_cpu, + [MASTER_CNOC_MNOC_CFG] = &qsm_mnoc_cfg, + [SLAVE_MNOC_HF_MEM_NOC] = &qns_mem_noc_hf, + [SLAVE_MNOC_SF_MEM_NOC] = &qns_mem_noc_sf, + [SLAVE_SERVICE_MNOC] = &srvc_mnoc, +}; + +static const struct regmap_config glymur_mmss_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x5b800, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_mmss_noc = { + .config = &glymur_mmss_noc_regmap_config, + .nodes = mmss_noc_nodes, + .num_nodes = ARRAY_SIZE(mmss_noc_nodes), + .bcms = mmss_noc_bcms, + .num_bcms = ARRAY_SIZE(mmss_noc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_node * const nsinoc_nodes[] = { + [MASTER_CPUCP] = &xm_cpucp, + [SLAVE_NSINOC_SYSTEM_NOC] = &qns_system_noc, + [SLAVE_SERVICE_NSINOC] = &srvc_nsinoc, +}; + +static const struct regmap_config glymur_nsinoc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x14080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_nsinoc = { + .config = &glymur_nsinoc_regmap_config, + .nodes = nsinoc_nodes, + .num_nodes = ARRAY_SIZE(nsinoc_nodes), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const nsp_noc_bcms[] = { + &bcm_co0, +}; + +static struct qcom_icc_node * const nsp_noc_nodes[] = { + [MASTER_CDSP_PROC] = &qnm_nsp, + [SLAVE_NSP0_HSC_NOC] = &qns_nsp_hscnoc, +}; + +static const struct regmap_config glymur_nsp_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x21280, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_nsp_noc = { + .config = &glymur_nsp_noc_regmap_config, + .nodes = nsp_noc_nodes, + .num_nodes = ARRAY_SIZE(nsp_noc_nodes), + .bcms = nsp_noc_bcms, + .num_bcms = ARRAY_SIZE(nsp_noc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_node * const oobm_ss_noc_nodes[] = { + [MASTER_OOBMSS_SP_PROC] = &xm_mem_sp, + [SLAVE_OOBMSS_SNOC] = &qns_oobmss_snoc, +}; + +static const struct regmap_config glymur_oobm_ss_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1e080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_oobm_ss_noc = { + .config = &glymur_oobm_ss_noc_regmap_config, + .nodes = oobm_ss_noc_nodes, + .num_nodes = ARRAY_SIZE(oobm_ss_noc_nodes), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const pcie_east_anoc_bcms[] = { + &bcm_sn6, +}; + +static struct qcom_icc_node * const pcie_east_anoc_nodes[] = { + [MASTER_PCIE_EAST_ANOC_CFG] = &qsm_pcie_east_anoc_cfg, + [MASTER_PCIE_0] = &xm_pcie_0, + [MASTER_PCIE_1] = &xm_pcie_1, + [MASTER_PCIE_5] = &xm_pcie_5, + [SLAVE_PCIE_EAST_MEM_NOC] = &qns_pcie_east_mem_noc, + [SLAVE_SERVICE_PCIE_EAST_AGGRE_NOC] = &srvc_pcie_east_aggre_noc, +}; + +static const struct regmap_config glymur_pcie_east_anoc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xf300, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_pcie_east_anoc = { + .config = &glymur_pcie_east_anoc_regmap_config, + .nodes = pcie_east_anoc_nodes, + .num_nodes = ARRAY_SIZE(pcie_east_anoc_nodes), + .bcms = pcie_east_anoc_bcms, + .num_bcms = ARRAY_SIZE(pcie_east_anoc_bcms), + .alloc_dyn_id = true, + .qos_requires_clocks = true, +}; + +static struct qcom_icc_bcm * const pcie_east_slv_noc_bcms[] = { + &bcm_sn6, +}; + +static struct qcom_icc_node * const pcie_east_slv_noc_nodes[] = { + [MASTER_HSCNOC_PCIE_EAST] = &qnm_hscnoc_pcie_east, + [MASTER_CNOC_PCIE_EAST_SLAVE_CFG] = &qsm_cnoc_pcie_east_slave_cfg, + [SLAVE_HSCNOC_PCIE_EAST_MS_MPU_CFG] = &qhs_hscnoc_pcie_east_ms_mpu_cfg, + [SLAVE_SERVICE_PCIE_EAST] = &srvc_pcie_east, + [SLAVE_PCIE_0] = &xs_pcie_0, + [SLAVE_PCIE_1] = &xs_pcie_1, + [SLAVE_PCIE_5] = &xs_pcie_5, +}; + +static const struct regmap_config glymur_pcie_east_slv_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xe080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_pcie_east_slv_noc = { + .config = &glymur_pcie_east_slv_noc_regmap_config, + .nodes = pcie_east_slv_noc_nodes, + .num_nodes = ARRAY_SIZE(pcie_east_slv_noc_nodes), + .bcms = pcie_east_slv_noc_bcms, + .num_bcms = ARRAY_SIZE(pcie_east_slv_noc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const pcie_west_anoc_bcms[] = { + &bcm_sn6, +}; + +static struct qcom_icc_node * const pcie_west_anoc_nodes[] = { + [MASTER_PCIE_WEST_ANOC_CFG] = &qsm_pcie_west_anoc_cfg, + [MASTER_PCIE_2] = &xm_pcie_2, + [MASTER_PCIE_3A] = &xm_pcie_3a, + [MASTER_PCIE_3B] = &xm_pcie_3b, + [MASTER_PCIE_4] = &xm_pcie_4, + [MASTER_PCIE_6] = &xm_pcie_6, + [SLAVE_PCIE_WEST_MEM_NOC] = &qns_pcie_west_mem_noc, + [SLAVE_SERVICE_PCIE_WEST_AGGRE_NOC] = &srvc_pcie_west_aggre_noc, +}; + +static const struct regmap_config glymur_pcie_west_anoc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xf580, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_pcie_west_anoc = { + .config = &glymur_pcie_west_anoc_regmap_config, + .nodes = pcie_west_anoc_nodes, + .num_nodes = ARRAY_SIZE(pcie_west_anoc_nodes), + .bcms = pcie_west_anoc_bcms, + .num_bcms = ARRAY_SIZE(pcie_west_anoc_bcms), + .alloc_dyn_id = true, + .qos_requires_clocks = true, +}; + +static struct qcom_icc_bcm * const pcie_west_slv_noc_bcms[] = { + &bcm_sn6, +}; + +static struct qcom_icc_node * const pcie_west_slv_noc_nodes[] = { + [MASTER_HSCNOC_PCIE_WEST] = &qnm_hscnoc_pcie_west, + [MASTER_CNOC_PCIE_WEST_SLAVE_CFG] = &qsm_cnoc_pcie_west_slave_cfg, + [SLAVE_HSCNOC_PCIE_WEST_MS_MPU_CFG] = &qhs_hscnoc_pcie_west_ms_mpu_cfg, + [SLAVE_SERVICE_PCIE_WEST] = &srvc_pcie_west, + [SLAVE_PCIE_2] = &xs_pcie_2, + [SLAVE_PCIE_3A] = &xs_pcie_3a, + [SLAVE_PCIE_3B] = &xs_pcie_3b, + [SLAVE_PCIE_4] = &xs_pcie_4, + [SLAVE_PCIE_6] = &xs_pcie_6, +}; + +static const struct regmap_config glymur_pcie_west_slv_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xf180, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_pcie_west_slv_noc = { + .config = &glymur_pcie_west_slv_noc_regmap_config, + .nodes = pcie_west_slv_noc_nodes, + .num_nodes = ARRAY_SIZE(pcie_west_slv_noc_nodes), + .bcms = pcie_west_slv_noc_bcms, + .num_bcms = ARRAY_SIZE(pcie_west_slv_noc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const system_noc_bcms[] = { + &bcm_sn0, + &bcm_sn1, + &bcm_sn2, + &bcm_sn3, + &bcm_sn4, +}; + +static struct qcom_icc_node * const system_noc_nodes[] = { + [MASTER_A1NOC_SNOC] = &qnm_aggre1_noc, + [MASTER_A2NOC_SNOC] = &qnm_aggre2_noc, + [MASTER_A3NOC_SNOC] = &qnm_aggre3_noc, + [MASTER_NSINOC_SNOC] = &qnm_nsi_noc, + [MASTER_OOBMSS] = &qnm_oobmss, + [SLAVE_SNOC_GEM_NOC_SF] = &qns_gemnoc_sf, +}; + +static const struct regmap_config glymur_system_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1c080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_system_noc = { + .config = &glymur_system_noc_regmap_config, + .nodes = system_noc_nodes, + .num_nodes = ARRAY_SIZE(system_noc_nodes), + .bcms = system_noc_bcms, + .num_bcms = ARRAY_SIZE(system_noc_bcms), + .alloc_dyn_id = true, +}; + +static const struct of_device_id qnoc_of_match[] = { + { .compatible = "qcom,glymur-aggre1-noc", .data = &glymur_aggre1_noc}, + { .compatible = "qcom,glymur-aggre2-noc", .data = &glymur_aggre2_noc}, + { .compatible = "qcom,glymur-aggre3-noc", .data = &glymur_aggre3_noc}, + { .compatible = "qcom,glymur-aggre4-noc", .data = &glymur_aggre4_noc}, + { .compatible = "qcom,glymur-clk-virt", .data = &glymur_clk_virt}, + { .compatible = "qcom,glymur-cnoc-cfg", .data = &glymur_cnoc_cfg}, + { .compatible = "qcom,glymur-cnoc-main", .data = &glymur_cnoc_main}, + { .compatible = "qcom,glymur-hscnoc", .data = &glymur_hscnoc}, + { .compatible = "qcom,glymur-lpass-ag-noc", .data = &glymur_lpass_ag_noc}, + { .compatible = "qcom,glymur-lpass-lpiaon-noc", .data = &glymur_lpass_lpiaon_noc}, + { .compatible = "qcom,glymur-lpass-lpicx-noc", .data = &glymur_lpass_lpicx_noc}, + { .compatible = "qcom,glymur-mc-virt", .data = &glymur_mc_virt}, + { .compatible = "qcom,glymur-mmss-noc", .data = &glymur_mmss_noc}, + { .compatible = "qcom,glymur-nsinoc", .data = &glymur_nsinoc}, + { .compatible = "qcom,glymur-nsp-noc", .data = &glymur_nsp_noc}, + { .compatible = "qcom,glymur-oobm-ss-noc", .data = &glymur_oobm_ss_noc}, + { .compatible = "qcom,glymur-pcie-east-anoc", .data = &glymur_pcie_east_anoc}, + { .compatible = "qcom,glymur-pcie-east-slv-noc", .data = &glymur_pcie_east_slv_noc}, + { .compatible = "qcom,glymur-pcie-west-anoc", .data = &glymur_pcie_west_anoc}, + { .compatible = "qcom,glymur-pcie-west-slv-noc", .data = &glymur_pcie_west_slv_noc}, + { .compatible = "qcom,glymur-system-noc", .data = &glymur_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-glymur", + .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("GLYMUR NoC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/interconnect/qcom/icc-rpmh.h b/drivers/interconnect/qcom/icc-rpmh.h index bd8d730249b1..307f48412563 100644 --- a/drivers/interconnect/qcom/icc-rpmh.h +++ b/drivers/interconnect/qcom/icc-rpmh.h @@ -53,7 +53,7 @@ struct bcm_db { u8 reserved; }; -#define MAX_PORTS 2 +#define MAX_PORTS 4 /** * struct qcom_icc_qosbox - Qualcomm specific QoS config diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index fa61f1d0ea2c..fb6afb8e84f0 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -538,7 +538,7 @@ static int uvc_parse_streaming(struct uvc_device *dev, unsigned int nformats = 0, nframes = 0, nintervals = 0; unsigned int size, i, n, p; u32 *interval; - u16 psize; + u32 psize; int ret = -EINVAL; if (intf->cur_altsetting->desc.bInterfaceSubClass @@ -774,7 +774,7 @@ static int uvc_parse_streaming(struct uvc_device *dev, streaming->header.bEndpointAddress); if (ep == NULL) continue; - psize = uvc_endpoint_max_bpi(dev->udev, ep); + psize = usb_endpoint_max_periodic_payload(dev->udev, ep); if (psize > streaming->maxpsize) streaming->maxpsize = psize; } diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index 784be9330320..2094e059d7d3 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -1870,24 +1870,6 @@ static void uvc_video_stop_transfer(struct uvc_streaming *stream, } /* - * Compute the maximum number of bytes per interval for an endpoint. - */ -u16 uvc_endpoint_max_bpi(struct usb_device *dev, struct usb_host_endpoint *ep) -{ - u16 psize; - - switch (dev->speed) { - case USB_SPEED_SUPER: - case USB_SPEED_SUPER_PLUS: - return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval); - default: - psize = usb_endpoint_maxp(&ep->desc); - psize *= usb_endpoint_maxp_mult(&ep->desc); - return psize; - } -} - -/* * Initialize isochronous URBs and allocate transfer buffers. The packet size * is given by the endpoint. */ @@ -1897,10 +1879,10 @@ static int uvc_init_video_isoc(struct uvc_streaming *stream, struct urb *urb; struct uvc_urb *uvc_urb; unsigned int npackets, i; - u16 psize; + u32 psize; u32 size; - psize = uvc_endpoint_max_bpi(stream->dev->udev, ep); + psize = usb_endpoint_max_periodic_payload(stream->dev->udev, ep); size = stream->ctrl.dwMaxVideoFrameSize; npackets = uvc_alloc_urb_buffers(stream, size, psize, gfp_flags); @@ -2043,7 +2025,7 @@ static int uvc_video_start_transfer(struct uvc_streaming *stream, continue; /* Check if the bandwidth is high enough. */ - psize = uvc_endpoint_max_bpi(stream->dev->udev, ep); + psize = usb_endpoint_max_periodic_payload(stream->dev->udev, ep); if (psize >= bandwidth && psize < best_psize) { altsetting = alts->desc.bAlternateSetting; best_psize = psize; diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 24292efbe47d..ed7bad31f75c 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -458,7 +458,7 @@ struct uvc_streaming { struct usb_interface *intf; int intfnum; - u16 maxpsize; + u32 maxpsize; struct uvc_streaming_header header; enum v4l2_buf_type type; @@ -797,8 +797,6 @@ void uvc_ctrl_cleanup_fh(struct uvc_fh *handle); /* Utility functions */ struct usb_host_endpoint *uvc_find_endpoint(struct usb_host_interface *alts, u8 epaddr); -u16 uvc_endpoint_max_bpi(struct usb_device *dev, struct usb_host_endpoint *ep); - /* Quirks support */ void uvc_video_decode_isight(struct uvc_urb *uvc_urb, struct uvc_buffer *buf, diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index e2e66f5f4fb8..b32a2597d246 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -23,7 +23,6 @@ obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o obj-$(CONFIG_KGDB_TESTS) += kgdbts.o -obj-$(CONFIG_TEST_MISC_MINOR) += misc_minor_kunit.o obj-$(CONFIG_SGI_XP) += sgi-xp/ obj-$(CONFIG_SGI_GRU) += sgi-gru/ obj-$(CONFIG_SMPRO_ERRMON) += smpro-errmon.o diff --git a/drivers/misc/ad525x_dpot.c b/drivers/misc/ad525x_dpot.c index 756ef6912b5a..04683b981e54 100644 --- a/drivers/misc/ad525x_dpot.c +++ b/drivers/misc/ad525x_dpot.c @@ -73,6 +73,7 @@ #include <linux/kernel.h> #include <linux/delay.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include "ad525x_dpot.h" @@ -418,10 +419,8 @@ static ssize_t sysfs_show_reg(struct device *dev, s32 value; if (reg & DPOT_ADDR_OTP_EN) - return sprintf(buf, "%s\n", - test_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask) ? - "enabled" : "disabled"); - + return sprintf(buf, "%s\n", str_enabled_disabled( + test_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask))); mutex_lock(&data->update_lock); value = dpot_read(data, reg); diff --git a/drivers/misc/amd-sbi/Kconfig b/drivers/misc/amd-sbi/Kconfig index 4840831c84ca..4aae0733d0fc 100644 --- a/drivers/misc/amd-sbi/Kconfig +++ b/drivers/misc/amd-sbi/Kconfig @@ -2,6 +2,7 @@ config AMD_SBRMI_I2C tristate "AMD side band RMI support" depends on I2C + select REGMAP_I2C help Side band RMI over I2C support for AMD out of band management. diff --git a/drivers/misc/apds990x.c b/drivers/misc/apds990x.c index e7d73c972f65..58946c4ff1a5 100644 --- a/drivers/misc/apds990x.c +++ b/drivers/misc/apds990x.c @@ -984,7 +984,6 @@ static ssize_t apds990x_power_state_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", !pm_runtime_suspended(dev)); - return 0; } static ssize_t apds990x_power_state_store(struct device *dev, diff --git a/drivers/misc/cardreader/rts5227.c b/drivers/misc/cardreader/rts5227.c index cd512284bfb3..46444bb47f65 100644 --- a/drivers/misc/cardreader/rts5227.c +++ b/drivers/misc/cardreader/rts5227.c @@ -79,6 +79,10 @@ static void rts5227_fetch_vendor_settings(struct rtsx_pcr *pcr) pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg); if (rtsx_reg_check_reverse_socket(reg)) pcr->flags |= PCR_REVERSE_SOCKET; + if (rtsx_reg_check_cd_reverse(reg)) + pcr->option.sd_cd_reverse_en = 1; + if (rtsx_reg_check_wp_reverse(reg)) + pcr->option.sd_wp_reverse_en = 1; } static void rts5227_init_from_cfg(struct rtsx_pcr *pcr) @@ -127,8 +131,10 @@ static int rts5227_extra_init_hw(struct rtsx_pcr *pcr) /* Configure force_clock_req */ if (pcr->flags & PCR_REVERSE_SOCKET) rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x30, 0x30); - else - rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x30, 0x00); + else { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x20, option->sd_cd_reverse_en << 5); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x10, option->sd_wp_reverse_en << 4); + } if (CHK_PCI_PID(pcr, 0x522A)) rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, RTS522A_AUTOLOAD_CFG1, @@ -350,6 +356,8 @@ void rts5227_init_params(struct rtsx_pcr *pcr) pcr->ms_pull_ctl_disable_tbl = rts5227_ms_pull_ctl_disable_tbl; pcr->reg_pm_ctrl3 = PM_CTRL3; + pcr->option.sd_cd_reverse_en = 0; + pcr->option.sd_wp_reverse_en = 0; } static int rts522a_optimize_phy(struct rtsx_pcr *pcr) @@ -508,5 +516,4 @@ void rts522a_init_params(struct rtsx_pcr *pcr) pcr->hw_param.interrupt_en |= SD_OC_INT_EN; pcr->hw_param.ocp_glitch = SD_OCP_GLITCH_10M; pcr->option.sd_800mA_ocp_thd = RTS522A_OCP_THD_800; - } diff --git a/drivers/misc/cardreader/rts5228.c b/drivers/misc/cardreader/rts5228.c index 0c7f10bcf6f1..db7e735ac24f 100644 --- a/drivers/misc/cardreader/rts5228.c +++ b/drivers/misc/cardreader/rts5228.c @@ -84,6 +84,10 @@ static void rtsx5228_fetch_vendor_settings(struct rtsx_pcr *pcr) pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg); if (rtsx_reg_check_reverse_socket(reg)) pcr->flags |= PCR_REVERSE_SOCKET; + if (rtsx_reg_check_cd_reverse(reg)) + pcr->option.sd_cd_reverse_en = 1; + if (rtsx_reg_check_wp_reverse(reg)) + pcr->option.sd_wp_reverse_en = 1; } static int rts5228_optimize_phy(struct rtsx_pcr *pcr) @@ -432,8 +436,10 @@ static int rts5228_extra_init_hw(struct rtsx_pcr *pcr) if (pcr->flags & PCR_REVERSE_SOCKET) rtsx_pci_write_register(pcr, PETXCFG, 0x30, 0x30); - else - rtsx_pci_write_register(pcr, PETXCFG, 0x30, 0x00); + else { + rtsx_pci_write_register(pcr, PETXCFG, 0x20, option->sd_cd_reverse_en << 5); + rtsx_pci_write_register(pcr, PETXCFG, 0x10, option->sd_wp_reverse_en << 4); + } /* * If u_force_clkreq_0 is enabled, CLKREQ# PIN will be forced @@ -720,4 +726,6 @@ void rts5228_init_params(struct rtsx_pcr *pcr) hw_param->interrupt_en |= SD_OC_INT_EN; hw_param->ocp_glitch = SD_OCP_GLITCH_800U; option->sd_800mA_ocp_thd = RTS5228_LDO1_OCP_THD_930; + option->sd_cd_reverse_en = 0; + option->sd_wp_reverse_en = 0; } diff --git a/drivers/misc/cardreader/rts5249.c b/drivers/misc/cardreader/rts5249.c index 6c81040e18be..38aefd8db452 100644 --- a/drivers/misc/cardreader/rts5249.c +++ b/drivers/misc/cardreader/rts5249.c @@ -60,6 +60,7 @@ static void rtsx_base_fetch_vendor_settings(struct rtsx_pcr *pcr) pci_read_config_dword(pdev, PCR_SETTING_REG1, ®); pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG1, reg); + pci_write_config_dword(pdev, 0x718, 0x0007C000); if (!rtsx_vendor_setting_valid(reg)) { pcr_dbg(pcr, "skip fetch vendor setting\n"); @@ -82,6 +83,10 @@ static void rtsx_base_fetch_vendor_settings(struct rtsx_pcr *pcr) pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg); if (rtsx_reg_check_reverse_socket(reg)) pcr->flags |= PCR_REVERSE_SOCKET; + if (rtsx_reg_check_cd_reverse(reg)) + pcr->option.sd_cd_reverse_en = 1; + if (rtsx_reg_check_wp_reverse(reg)) + pcr->option.sd_wp_reverse_en = 1; } static void rts5249_init_from_cfg(struct rtsx_pcr *pcr) @@ -254,9 +259,11 @@ static int rts5249_extra_init_hw(struct rtsx_pcr *pcr) /* Configure driving */ rts5249_fill_driving(pcr, OUTPUT_3V3); if (pcr->flags & PCR_REVERSE_SOCKET) - rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0xB0, 0xB0); - else - rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0xB0, 0x80); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x30, 0x30); + else { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x20, option->sd_cd_reverse_en << 5); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x10, option->sd_wp_reverse_en << 4); + } rtsx_pci_send_cmd(pcr, CMD_TIMEOUT_DEF); @@ -572,6 +579,9 @@ void rts5249_init_params(struct rtsx_pcr *pcr) option->ltr_l1off_sspwrgate = LTR_L1OFF_SSPWRGATE_5249_DEF; option->ltr_l1off_snooze_sspwrgate = LTR_L1OFF_SNOOZE_SSPWRGATE_5249_DEF; + + option->sd_cd_reverse_en = 0; + option->sd_wp_reverse_en = 0; } static int rts524a_write_phy(struct rtsx_pcr *pcr, u8 addr, u16 val) diff --git a/drivers/misc/cardreader/rts5264.c b/drivers/misc/cardreader/rts5264.c index d050c9fff7ac..99a2d5ea6421 100644 --- a/drivers/misc/cardreader/rts5264.c +++ b/drivers/misc/cardreader/rts5264.c @@ -527,8 +527,16 @@ static void rts5264_init_from_hw(struct rtsx_pcr *pcr) pcr->rtd3_en = rts5264_reg_to_rtd3(lval2); - if (rts5264_reg_check_reverse_socket(lval2)) - pcr->flags |= PCR_REVERSE_SOCKET; + if (rts5264_reg_check_reverse_socket(lval2)) { + if (is_version_higher_than(pcr, PID_5264, RTS5264_IC_VER_B)) + pcr->option.sd_cd_reverse_en = 1; + else + pcr->flags |= PCR_REVERSE_SOCKET; + } + + if (rts5264_reg_check_wp_reverse(lval2) && + is_version_higher_than(pcr, PID_5264, RTS5264_IC_VER_B)) + pcr->option.sd_wp_reverse_en = 1; pci_read_config_dword(pdev, setting_reg1, &lval1); pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", setting_reg1, lval1); @@ -622,8 +630,10 @@ static int rts5264_extra_init_hw(struct rtsx_pcr *pcr) if (pcr->flags & PCR_REVERSE_SOCKET) rtsx_pci_write_register(pcr, PETXCFG, 0x30, 0x30); - else - rtsx_pci_write_register(pcr, PETXCFG, 0x30, 0x00); + else { + rtsx_pci_write_register(pcr, PETXCFG, 0x20, option->sd_cd_reverse_en << 5); + rtsx_pci_write_register(pcr, PETXCFG, 0x10, option->sd_wp_reverse_en << 4); + } /* * If u_force_clkreq_0 is enabled, CLKREQ# PIN will be forced @@ -957,4 +967,6 @@ void rts5264_init_params(struct rtsx_pcr *pcr) hw_param->interrupt_en |= (SD_OC_INT_EN | SD_OVP_INT_EN); hw_param->ocp_glitch = SD_OCP_GLITCH_800U | SDVIO_OCP_GLITCH_800U; option->sd_800mA_ocp_thd = RTS5264_LDO1_OCP_THD_1150; + option->sd_cd_reverse_en = 0; + option->sd_wp_reverse_en = 0; } diff --git a/drivers/misc/cardreader/rts5264.h b/drivers/misc/cardreader/rts5264.h index f3e81daa708d..611ee253367c 100644 --- a/drivers/misc/cardreader/rts5264.h +++ b/drivers/misc/cardreader/rts5264.h @@ -14,6 +14,7 @@ #define rts5264_reg_to_aspm(reg) \ (((~(reg) >> 28) & 0x02) | (((reg) >> 28) & 0x01)) #define rts5264_reg_check_reverse_socket(reg) ((reg) & 0x04) +#define rts5264_reg_check_wp_reverse(reg) ((reg) & 0x8000) #define rts5264_reg_to_sd30_drive_sel_1v8(reg) (((reg) >> 22) & 0x03) #define rts5264_reg_to_sd30_drive_sel_3v3(reg) (((reg) >> 16) & 0x03) #define rts5264_reg_to_rtd3(reg) ((reg) & 0x08) diff --git a/drivers/misc/cardreader/rtsx_pcr.h b/drivers/misc/cardreader/rtsx_pcr.h index 8e5951b61143..40562ff2be13 100644 --- a/drivers/misc/cardreader/rtsx_pcr.h +++ b/drivers/misc/cardreader/rtsx_pcr.h @@ -100,6 +100,8 @@ static inline u8 map_sd_drive(int idx) #define rtsx_reg_to_sd30_drive_sel_3v3(reg) (((reg) >> 5) & 0x03) #define rtsx_reg_to_card_drive_sel(reg) ((((reg) >> 25) & 0x01) << 6) #define rtsx_reg_check_reverse_socket(reg) ((reg) & 0x4000) +#define rtsx_reg_check_cd_reverse(reg) ((reg) & 0x800000) +#define rtsx_reg_check_wp_reverse(reg) ((reg) & 0x400000) #define rts5209_reg_to_aspm(reg) (((reg) >> 5) & 0x03) #define rts5209_reg_check_ms_pmos(reg) (!((reg) & 0x08)) #define rts5209_reg_to_sd30_drive_sel_1v8(reg) (((reg) >> 3) & 0x07) diff --git a/drivers/misc/dw-xdata-pcie.c b/drivers/misc/dw-xdata-pcie.c index efd0ca8cc925..a604c0e9c038 100644 --- a/drivers/misc/dw-xdata-pcie.c +++ b/drivers/misc/dw-xdata-pcie.c @@ -16,6 +16,7 @@ #include <linux/mutex.h> #include <linux/delay.h> #include <linux/pci.h> +#include <linux/string_choices.h> #define DW_XDATA_DRIVER_NAME "dw-xdata-pcie" @@ -132,7 +133,7 @@ static void dw_xdata_start(struct dw_xdata *dw, bool write) if (!(status & STATUS_DONE)) dev_dbg(dev, "xData: started %s direction\n", - write ? "write" : "read"); + str_write_read(write)); } static void dw_xdata_perf_meas(struct dw_xdata *dw, u64 *data, bool write) @@ -195,7 +196,7 @@ static void dw_xdata_perf(struct dw_xdata *dw, u64 *rate, bool write) mutex_unlock(&dw->mutex); dev_dbg(dev, "xData: time=%llu us, %s=%llu MB/s\n", - diff, write ? "write" : "read", *rate); + diff, str_write_read(write), *rate); } static struct dw_xdata *misc_dev_to_dw(struct miscdevice *misc_dev) diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig index 0bef5b93bd6d..4d0ce47aa282 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig @@ -120,4 +120,22 @@ config EEPROM_EE1004 This driver can also be built as a module. If so, the module will be called ee1004. +config EEPROM_M24LR + tristate "STMicroelectronics M24LR RFID/NFC EEPROM support" + depends on I2C && SYSFS + select REGMAP_I2C + select NVMEM + select NVMEM_SYSFS + help + This enables support for STMicroelectronics M24LR RFID/NFC EEPROM + chips. These dual-interface devices expose two I2C addresses: + one for EEPROM memory access and another for control and system + configuration (e.g. UID, password handling). + + This driver provides a sysfs interface for control functions and + integrates with the nvmem subsystem for EEPROM access. + + To compile this driver as a module, choose M here: the + module will be called m24lr. + endmenu diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile index 65794e526d5d..8f311fd6a4ce 100644 --- a/drivers/misc/eeprom/Makefile +++ b/drivers/misc/eeprom/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o obj-$(CONFIG_EEPROM_EE1004) += ee1004.o +obj-$(CONFIG_EEPROM_M24LR) += m24lr.o diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index 2d0492867054..e2868f7bdb03 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -379,37 +379,49 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) struct at25_data *at25 = container_of(chip, struct at25_data, chip); u8 sernum[FM25_SN_LEN]; u8 id[FM25_ID_LEN]; + u32 val; int i; strscpy(chip->name, "fm25", sizeof(chip->name)); - /* Get ID of chip */ - fm25_aux_read(at25, id, FM25_RDID, FM25_ID_LEN); - /* There are inside-out FRAM variations, detect them and reverse the ID bytes */ - if (id[6] == 0x7f && id[2] == 0xc2) - for (i = 0; i < ARRAY_SIZE(id) / 2; i++) { - u8 tmp = id[i]; - int j = ARRAY_SIZE(id) - i - 1; + if (!device_property_read_u32(dev, "size", &val)) { + chip->byte_len = val; + } else { + /* Get ID of chip */ + fm25_aux_read(at25, id, FM25_RDID, FM25_ID_LEN); + /* There are inside-out FRAM variations, detect them and reverse the ID bytes */ + if (id[6] == 0x7f && id[2] == 0xc2) + for (i = 0; i < ARRAY_SIZE(id) / 2; i++) { + u8 tmp = id[i]; + int j = ARRAY_SIZE(id) - i - 1; + + id[i] = id[j]; + id[j] = tmp; + } + if (id[6] != 0xc2) { + dev_err(dev, "Error: no Cypress FRAM with device ID (manufacturer ID bank 7: %02x)\n", id[6]); + return -ENODEV; + } - id[i] = id[j]; - id[j] = tmp; + switch (id[7]) { + case 0x21 ... 0x26: + chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024; + break; + case 0x2a ... 0x30: + /* CY15B116QN ... CY15B116QN */ + chip->byte_len = BIT(((id[7] >> 1) & 0xf) + 13); + break; + default: + dev_err(dev, "Error: unsupported size (id %02x)\n", id[7]); + return -ENODEV; } - if (id[6] != 0xc2) { - dev_err(dev, "Error: no Cypress FRAM (id %02x)\n", id[6]); - return -ENODEV; - } - switch (id[7]) { - case 0x21 ... 0x26: - chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024; - break; - case 0x2a ... 0x30: - /* CY15B116QN ... CY15B116QN */ - chip->byte_len = BIT(((id[7] >> 1) & 0xf) + 13); - break; - default: - dev_err(dev, "Error: unsupported size (id %02x)\n", id[7]); - return -ENODEV; + if (id[8]) { + fm25_aux_read(at25, sernum, FM25_RDSN, FM25_SN_LEN); + /* Swap byte order */ + for (i = 0; i < FM25_SN_LEN; i++) + at25->sernum[i] = sernum[FM25_SN_LEN - 1 - i]; + } } if (chip->byte_len > 64 * 1024) @@ -417,13 +429,6 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) else chip->flags |= EE_ADDR2; - if (id[8]) { - fm25_aux_read(at25, sernum, FM25_RDSN, FM25_SN_LEN); - /* Swap byte order */ - for (i = 0; i < FM25_SN_LEN; i++) - at25->sernum[i] = sernum[FM25_SN_LEN - 1 - i]; - } - chip->page_size = PAGE_SIZE; return 0; } diff --git a/drivers/misc/eeprom/m24lr.c b/drivers/misc/eeprom/m24lr.c new file mode 100644 index 000000000000..7a9fd45a8e46 --- /dev/null +++ b/drivers/misc/eeprom/m24lr.c @@ -0,0 +1,606 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * m24lr.c - Sysfs control interface for ST M24LR series RFID/NFC chips + * + * Copyright (c) 2025 Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com> + * + * This driver implements both the sysfs-based control interface and EEPROM + * access for STMicroelectronics M24LR series chips (e.g., M24LR04E-R). + * It provides access to control registers for features such as password + * authentication, memory protection, and device configuration. In addition, + * it manages read and write operations to the EEPROM region of the chip. + */ + +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> + +#define M24LR_WRITE_TIMEOUT 25u +#define M24LR_READ_TIMEOUT (M24LR_WRITE_TIMEOUT) + +/** + * struct m24lr_chip - describes chip-specific sysfs layout + * @sss_len: the length of the sss region + * @page_size: chip-specific limit on the maximum number of bytes allowed + * in a single write operation. + * @eeprom_size: size of the EEPROM in byte + * + * Supports multiple M24LR chip variants (e.g., M24LRxx) by allowing each + * to define its own set of sysfs attributes, depending on its available + * registers and features. + */ +struct m24lr_chip { + unsigned int sss_len; + unsigned int page_size; + unsigned int eeprom_size; +}; + +/** + * struct m24lr - core driver data for M24LR chip control + * @uid: 64 bits unique identifier stored in the device + * @sss_len: the length of the sss region + * @page_size: chip-specific limit on the maximum number of bytes allowed + * in a single write operation. + * @eeprom_size: size of the EEPROM in byte + * @ctl_regmap: regmap interface for accessing the system parameter sector + * @eeprom_regmap: regmap interface for accessing the EEPROM + * @lock: mutex to synchronize operations to the device + * + * Central data structure holding the state and resources used by the + * M24LR device driver. + */ +struct m24lr { + u64 uid; + unsigned int sss_len; + unsigned int page_size; + unsigned int eeprom_size; + struct regmap *ctl_regmap; + struct regmap *eeprom_regmap; + struct mutex lock; /* synchronize operations to the device */ +}; + +static const struct regmap_range m24lr_ctl_vo_ranges[] = { + regmap_reg_range(0, 63), +}; + +static const struct regmap_access_table m24lr_ctl_vo_table = { + .yes_ranges = m24lr_ctl_vo_ranges, + .n_yes_ranges = ARRAY_SIZE(m24lr_ctl_vo_ranges), +}; + +static const struct regmap_config m24lr_ctl_regmap_conf = { + .name = "m24lr_ctl", + .reg_stride = 1, + .reg_bits = 16, + .val_bits = 8, + .disable_locking = false, + .cache_type = REGCACHE_RBTREE,/* Flat can't be used, there's huge gap */ + .volatile_table = &m24lr_ctl_vo_table, +}; + +/* Chip descriptor for M24LR04E-R variant */ +static const struct m24lr_chip m24lr04e_r_chip = { + .page_size = 4, + .eeprom_size = 512, + .sss_len = 4, +}; + +/* Chip descriptor for M24LR16E-R variant */ +static const struct m24lr_chip m24lr16e_r_chip = { + .page_size = 4, + .eeprom_size = 2048, + .sss_len = 16, +}; + +/* Chip descriptor for M24LR64E-R variant */ +static const struct m24lr_chip m24lr64e_r_chip = { + .page_size = 4, + .eeprom_size = 8192, + .sss_len = 64, +}; + +static const struct i2c_device_id m24lr_ids[] = { + { "m24lr04e-r", (kernel_ulong_t)&m24lr04e_r_chip}, + { "m24lr16e-r", (kernel_ulong_t)&m24lr16e_r_chip}, + { "m24lr64e-r", (kernel_ulong_t)&m24lr64e_r_chip}, + { } +}; +MODULE_DEVICE_TABLE(i2c, m24lr_ids); + +static const struct of_device_id m24lr_of_match[] = { + { .compatible = "st,m24lr04e-r", .data = &m24lr04e_r_chip}, + { .compatible = "st,m24lr16e-r", .data = &m24lr16e_r_chip}, + { .compatible = "st,m24lr64e-r", .data = &m24lr64e_r_chip}, + { } +}; +MODULE_DEVICE_TABLE(of, m24lr_of_match); + +/** + * m24lr_regmap_read - read data using regmap with retry on failure + * @regmap: regmap instance for the device + * @buf: buffer to store the read data + * @size: number of bytes to read + * @offset: starting register address + * + * Attempts to read a block of data from the device with retries and timeout. + * Some M24LR chips may transiently NACK reads (e.g., during internal write + * cycles), so this function retries with a short sleep until the timeout + * expires. + * + * Returns: + * Number of bytes read on success, + * -ETIMEDOUT if the read fails within the timeout window. + */ +static ssize_t m24lr_regmap_read(struct regmap *regmap, u8 *buf, + size_t size, unsigned int offset) +{ + int err; + unsigned long timeout, read_time; + ssize_t ret = -ETIMEDOUT; + + timeout = jiffies + msecs_to_jiffies(M24LR_READ_TIMEOUT); + do { + read_time = jiffies; + + err = regmap_bulk_read(regmap, offset, buf, size); + if (!err) { + ret = size; + break; + } + + usleep_range(1000, 2000); + } while (time_before(read_time, timeout)); + + return ret; +} + +/** + * m24lr_regmap_write - write data using regmap with retry on failure + * @regmap: regmap instance for the device + * @buf: buffer containing the data to write + * @size: number of bytes to write + * @offset: starting register address + * + * Attempts to write a block of data to the device with retries and a timeout. + * Some M24LR devices may NACK I2C writes while an internal write operation + * is in progress. This function retries the write operation with a short delay + * until it succeeds or the timeout is reached. + * + * Returns: + * Number of bytes written on success, + * -ETIMEDOUT if the write fails within the timeout window. + */ +static ssize_t m24lr_regmap_write(struct regmap *regmap, const u8 *buf, + size_t size, unsigned int offset) +{ + int err; + unsigned long timeout, write_time; + ssize_t ret = -ETIMEDOUT; + + timeout = jiffies + msecs_to_jiffies(M24LR_WRITE_TIMEOUT); + + do { + write_time = jiffies; + + err = regmap_bulk_write(regmap, offset, buf, size); + if (!err) { + ret = size; + break; + } + + usleep_range(1000, 2000); + } while (time_before(write_time, timeout)); + + return ret; +} + +static ssize_t m24lr_read(struct m24lr *m24lr, u8 *buf, size_t size, + unsigned int offset, bool is_eeprom) +{ + struct regmap *regmap; + ssize_t ret; + + if (is_eeprom) + regmap = m24lr->eeprom_regmap; + else + regmap = m24lr->ctl_regmap; + + mutex_lock(&m24lr->lock); + ret = m24lr_regmap_read(regmap, buf, size, offset); + mutex_unlock(&m24lr->lock); + + return ret; +} + +/** + * m24lr_write - write buffer to M24LR device with page alignment handling + * @m24lr: pointer to driver context + * @buf: data buffer to write + * @size: number of bytes to write + * @offset: target register address in the device + * @is_eeprom: true if the write should target the EEPROM, + * false if it should target the system parameters sector. + * + * Writes data to the M24LR device using regmap, split into chunks no larger + * than page_size to respect device-specific write limitations (e.g., page + * size or I2C hold-time concerns). Each chunk is aligned to the page boundary + * defined by page_size. + * + * Returns: + * Total number of bytes written on success, + * A negative error code if any write fails. + */ +static ssize_t m24lr_write(struct m24lr *m24lr, const u8 *buf, size_t size, + unsigned int offset, bool is_eeprom) +{ + unsigned int n, next_sector; + struct regmap *regmap; + ssize_t ret = 0; + ssize_t err; + + if (is_eeprom) + regmap = m24lr->eeprom_regmap; + else + regmap = m24lr->ctl_regmap; + + n = min_t(unsigned int, size, m24lr->page_size); + next_sector = roundup(offset + 1, m24lr->page_size); + if (offset + n > next_sector) + n = next_sector - offset; + + mutex_lock(&m24lr->lock); + while (n) { + err = m24lr_regmap_write(regmap, buf + offset, n, offset); + if (IS_ERR_VALUE(err)) { + if (!ret) + ret = err; + + break; + } + + offset += n; + size -= n; + ret += n; + n = min_t(unsigned int, size, m24lr->page_size); + } + mutex_unlock(&m24lr->lock); + + return ret; +} + +/** + * m24lr_write_pass - Write password to M24LR043-R using secure format + * @m24lr: Pointer to device control structure + * @buf: Input buffer containing hex-encoded password + * @count: Number of bytes in @buf + * @code: Operation code to embed between password copies + * + * This function parses a 4-byte password, encodes it in big-endian format, + * and constructs a 9-byte sequence of the form: + * + * [BE(password), code, BE(password)] + * + * The result is written to register 0x0900 (2304), which is the password + * register in M24LR04E-R chip. + * + * Return: Number of bytes written on success, or negative error code on failure + */ +static ssize_t m24lr_write_pass(struct m24lr *m24lr, const char *buf, + size_t count, u8 code) +{ + __be32 be_pass; + u8 output[9]; + ssize_t ret; + u32 pass; + int err; + + if (!count) + return -EINVAL; + + if (count > 8) + return -EINVAL; + + err = kstrtou32(buf, 16, &pass); + if (err) + return err; + + be_pass = cpu_to_be32(pass); + + memcpy(output, &be_pass, sizeof(be_pass)); + output[4] = code; + memcpy(output + 5, &be_pass, sizeof(be_pass)); + + mutex_lock(&m24lr->lock); + ret = m24lr_regmap_write(m24lr->ctl_regmap, output, 9, 2304); + mutex_unlock(&m24lr->lock); + + return ret; +} + +static ssize_t m24lr_read_reg_le(struct m24lr *m24lr, u64 *val, + unsigned int reg_addr, + unsigned int reg_size) +{ + ssize_t ret; + __le64 input = 0; + + ret = m24lr_read(m24lr, (u8 *)&input, reg_size, reg_addr, false); + if (IS_ERR_VALUE(ret)) + return ret; + + if (ret != reg_size) + return -EINVAL; + + switch (reg_size) { + case 1: + *val = *(u8 *)&input; + break; + case 2: + *val = le16_to_cpu((__le16)input); + break; + case 4: + *val = le32_to_cpu((__le32)input); + break; + case 8: + *val = le64_to_cpu((__le64)input); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int m24lr_nvmem_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + ssize_t err; + struct m24lr *m24lr = priv; + + if (!bytes) + return bytes; + + if (offset + bytes > m24lr->eeprom_size) + return -EINVAL; + + err = m24lr_read(m24lr, val, bytes, offset, true); + if (IS_ERR_VALUE(err)) + return err; + + return 0; +} + +static int m24lr_nvmem_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + ssize_t err; + struct m24lr *m24lr = priv; + + if (!bytes) + return -EINVAL; + + if (offset + bytes > m24lr->eeprom_size) + return -EINVAL; + + err = m24lr_write(m24lr, val, bytes, offset, true); + if (IS_ERR_VALUE(err)) + return err; + + return 0; +} + +static ssize_t m24lr_ctl_sss_read(struct file *filep, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t offset, size_t count) +{ + struct m24lr *m24lr = attr->private; + + if (!count) + return count; + + if (size_add(offset, count) > m24lr->sss_len) + return -EINVAL; + + return m24lr_read(m24lr, buf, count, offset, false); +} + +static ssize_t m24lr_ctl_sss_write(struct file *filep, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t offset, size_t count) +{ + struct m24lr *m24lr = attr->private; + + if (!count) + return -EINVAL; + + if (size_add(offset, count) > m24lr->sss_len) + return -EINVAL; + + return m24lr_write(m24lr, buf, count, offset, false); +} +static BIN_ATTR(sss, 0600, m24lr_ctl_sss_read, m24lr_ctl_sss_write, 0); + +static ssize_t new_pass_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return m24lr_write_pass(m24lr, buf, count, 7); +} +static DEVICE_ATTR_WO(new_pass); + +static ssize_t unlock_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return m24lr_write_pass(m24lr, buf, count, 9); +} +static DEVICE_ATTR_WO(unlock); + +static ssize_t uid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return sysfs_emit(buf, "%llx\n", m24lr->uid); +} +static DEVICE_ATTR_RO(uid); + +static ssize_t total_sectors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return sysfs_emit(buf, "%x\n", m24lr->sss_len); +} +static DEVICE_ATTR_RO(total_sectors); + +static struct attribute *m24lr_ctl_dev_attrs[] = { + &dev_attr_unlock.attr, + &dev_attr_new_pass.attr, + &dev_attr_uid.attr, + &dev_attr_total_sectors.attr, + NULL, +}; + +static const struct m24lr_chip *m24lr_get_chip(struct device *dev) +{ + const struct m24lr_chip *ret; + const struct i2c_device_id *id; + + id = i2c_match_id(m24lr_ids, to_i2c_client(dev)); + + if (dev->of_node && of_match_device(m24lr_of_match, dev)) + ret = of_device_get_match_data(dev); + else if (id) + ret = (void *)id->driver_data; + else + ret = acpi_device_get_match_data(dev); + + return ret; +} + +static int m24lr_probe(struct i2c_client *client) +{ + struct regmap_config eeprom_regmap_conf = {0}; + struct nvmem_config nvmem_conf = {0}; + struct device *dev = &client->dev; + struct i2c_client *eeprom_client; + const struct m24lr_chip *chip; + struct regmap *eeprom_regmap; + struct nvmem_device *nvmem; + struct regmap *ctl_regmap; + struct m24lr *m24lr; + u32 regs[2]; + long err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + chip = m24lr_get_chip(dev); + if (!chip) + return -ENODEV; + + m24lr = devm_kzalloc(dev, sizeof(struct m24lr), GFP_KERNEL); + if (!m24lr) + return -ENOMEM; + + err = device_property_read_u32_array(dev, "reg", regs, ARRAY_SIZE(regs)); + if (err) + return dev_err_probe(dev, err, "Failed to read 'reg' property\n"); + + /* Create a second I2C client for the eeprom interface */ + eeprom_client = devm_i2c_new_dummy_device(dev, client->adapter, regs[1]); + if (IS_ERR(eeprom_client)) + return dev_err_probe(dev, PTR_ERR(eeprom_client), + "Failed to create dummy I2C client for the EEPROM\n"); + + ctl_regmap = devm_regmap_init_i2c(client, &m24lr_ctl_regmap_conf); + if (IS_ERR(ctl_regmap)) + return dev_err_probe(dev, PTR_ERR(ctl_regmap), + "Failed to init regmap\n"); + + eeprom_regmap_conf.name = "m24lr_eeprom"; + eeprom_regmap_conf.reg_bits = 16; + eeprom_regmap_conf.val_bits = 8; + eeprom_regmap_conf.disable_locking = true; + eeprom_regmap_conf.max_register = chip->eeprom_size - 1; + + eeprom_regmap = devm_regmap_init_i2c(eeprom_client, + &eeprom_regmap_conf); + if (IS_ERR(eeprom_regmap)) + return dev_err_probe(dev, PTR_ERR(eeprom_regmap), + "Failed to init regmap\n"); + + mutex_init(&m24lr->lock); + m24lr->sss_len = chip->sss_len; + m24lr->page_size = chip->page_size; + m24lr->eeprom_size = chip->eeprom_size; + m24lr->eeprom_regmap = eeprom_regmap; + m24lr->ctl_regmap = ctl_regmap; + + nvmem_conf.dev = &eeprom_client->dev; + nvmem_conf.owner = THIS_MODULE; + nvmem_conf.type = NVMEM_TYPE_EEPROM; + nvmem_conf.reg_read = m24lr_nvmem_read; + nvmem_conf.reg_write = m24lr_nvmem_write; + nvmem_conf.size = chip->eeprom_size; + nvmem_conf.word_size = 1; + nvmem_conf.stride = 1; + nvmem_conf.priv = m24lr; + + nvmem = devm_nvmem_register(dev, &nvmem_conf); + if (IS_ERR(nvmem)) + return dev_err_probe(dev, PTR_ERR(nvmem), + "Failed to register nvmem\n"); + + i2c_set_clientdata(client, m24lr); + i2c_set_clientdata(eeprom_client, m24lr); + + bin_attr_sss.size = chip->sss_len; + bin_attr_sss.private = m24lr; + err = sysfs_create_bin_file(&dev->kobj, &bin_attr_sss); + if (err) + return dev_err_probe(dev, err, + "Failed to create sss bin file\n"); + + /* test by reading the uid, if success store it */ + err = m24lr_read_reg_le(m24lr, &m24lr->uid, 2324, sizeof(m24lr->uid)); + if (IS_ERR_VALUE(err)) + goto remove_bin_file; + + return 0; + +remove_bin_file: + sysfs_remove_bin_file(&dev->kobj, &bin_attr_sss); + + return err; +} + +static void m24lr_remove(struct i2c_client *client) +{ + sysfs_remove_bin_file(&client->dev.kobj, &bin_attr_sss); +} + +ATTRIBUTE_GROUPS(m24lr_ctl_dev); + +static struct i2c_driver m24lr_driver = { + .driver = { + .name = "m24lr", + .of_match_table = m24lr_of_match, + .dev_groups = m24lr_ctl_dev_groups, + }, + .probe = m24lr_probe, + .remove = m24lr_remove, + .id_table = m24lr_ids, +}; +module_i2c_driver(m24lr_driver); + +MODULE_AUTHOR("Abd-Alrhman Masalkhi"); +MODULE_DESCRIPTION("st m24lr control driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 53e88a1bc430..8e1d97873423 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -27,8 +27,7 @@ #define MDSP_DOMAIN_ID (1) #define SDSP_DOMAIN_ID (2) #define CDSP_DOMAIN_ID (3) -#define CDSP1_DOMAIN_ID (4) -#define FASTRPC_DEV_MAX 5 /* adsp, mdsp, slpi, cdsp, cdsp1 */ +#define GDSP_DOMAIN_ID (4) #define FASTRPC_MAX_SESSIONS 14 #define FASTRPC_MAX_VMIDS 16 #define FASTRPC_ALIGN 128 @@ -106,8 +105,6 @@ #define miscdev_to_fdevice(d) container_of(d, struct fastrpc_device, miscdev) -static const char *domains[FASTRPC_DEV_MAX] = { "adsp", "mdsp", - "sdsp", "cdsp", "cdsp1" }; struct fastrpc_phy_page { u64 addr; /* physical address */ u64 size; /* size of contiguous region */ @@ -1723,7 +1720,6 @@ static int fastrpc_get_info_from_kernel(struct fastrpc_ioctl_capability *cap, uint32_t attribute_id = cap->attribute_id; uint32_t *dsp_attributes; unsigned long flags; - uint32_t domain = cap->domain; int err; spin_lock_irqsave(&cctx->lock, flags); @@ -1741,7 +1737,7 @@ static int fastrpc_get_info_from_kernel(struct fastrpc_ioctl_capability *cap, err = fastrpc_get_info_from_dsp(fl, dsp_attributes, FASTRPC_MAX_DSP_ATTRIBUTES); if (err == DSP_UNSUPPORTED_API) { dev_info(&cctx->rpdev->dev, - "Warning: DSP capabilities not supported on domain: %d\n", domain); + "Warning: DSP capabilities not supported\n"); kfree(dsp_attributes); return -EOPNOTSUPP; } else if (err) { @@ -1769,17 +1765,6 @@ static int fastrpc_get_dsp_info(struct fastrpc_user *fl, char __user *argp) return -EFAULT; cap.capability = 0; - if (cap.domain >= FASTRPC_DEV_MAX) { - dev_err(&fl->cctx->rpdev->dev, "Error: Invalid domain id:%d, err:%d\n", - cap.domain, err); - return -ECHRNG; - } - - /* Fastrpc Capablities does not support modem domain */ - if (cap.domain == MDSP_DOMAIN_ID) { - dev_err(&fl->cctx->rpdev->dev, "Error: modem not supported %d\n", err); - return -ECHRNG; - } if (cap.attribute_id >= FASTRPC_MAX_DSP_ATTRIBUTES) { dev_err(&fl->cctx->rpdev->dev, "Error: invalid attribute: %d, err: %d\n", @@ -2255,6 +2240,22 @@ static int fastrpc_device_register(struct device *dev, struct fastrpc_channel_ct return err; } +static int fastrpc_get_domain_id(const char *domain) +{ + if (!strncmp(domain, "adsp", 4)) + return ADSP_DOMAIN_ID; + else if (!strncmp(domain, "cdsp", 4)) + return CDSP_DOMAIN_ID; + else if (!strncmp(domain, "mdsp", 4)) + return MDSP_DOMAIN_ID; + else if (!strncmp(domain, "sdsp", 4)) + return SDSP_DOMAIN_ID; + else if (!strncmp(domain, "gdsp", 4)) + return GDSP_DOMAIN_ID; + + return -EINVAL; +} + static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) { struct device *rdev = &rpdev->dev; @@ -2270,15 +2271,10 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) return err; } - for (i = 0; i < FASTRPC_DEV_MAX; i++) { - if (!strcmp(domains[i], domain)) { - domain_id = i; - break; - } - } + domain_id = fastrpc_get_domain_id(domain); if (domain_id < 0) { - dev_info(rdev, "FastRPC Invalid Domain ID %d\n", domain_id); + dev_info(rdev, "FastRPC Domain %s not supported\n", domain); return -EINVAL; } @@ -2325,21 +2321,21 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) case ADSP_DOMAIN_ID: case MDSP_DOMAIN_ID: case SDSP_DOMAIN_ID: - /* Unsigned PD offloading is only supported on CDSP and CDSP1 */ + /* Unsigned PD offloading is only supported on CDSP and GDSP */ data->unsigned_support = false; - err = fastrpc_device_register(rdev, data, secure_dsp, domains[domain_id]); + err = fastrpc_device_register(rdev, data, secure_dsp, domain); if (err) goto err_free_data; break; case CDSP_DOMAIN_ID: - case CDSP1_DOMAIN_ID: + case GDSP_DOMAIN_ID: data->unsigned_support = true; /* Create both device nodes so that we can allow both Signed and Unsigned PD */ - err = fastrpc_device_register(rdev, data, true, domains[domain_id]); + err = fastrpc_device_register(rdev, data, true, domain); if (err) goto err_free_data; - err = fastrpc_device_register(rdev, data, false, domains[domain_id]); + err = fastrpc_device_register(rdev, data, false, domain); if (err) goto err_deregister_fdev; break; diff --git a/drivers/misc/genwqe/card_ddcb.c b/drivers/misc/genwqe/card_ddcb.c index 500b1feaf1f6..fd7d5cd50d39 100644 --- a/drivers/misc/genwqe/card_ddcb.c +++ b/drivers/misc/genwqe/card_ddcb.c @@ -923,7 +923,7 @@ int __genwqe_execute_raw_ddcb(struct genwqe_dev *cd, } if (cmd->asv_length > DDCB_ASV_LENGTH) { dev_err(&pci_dev->dev, "[%s] err: wrong asv_length of %d\n", - __func__, cmd->asiv_length); + __func__, cmd->asv_length); return -EINVAL; } rc = __genwqe_enqueue_ddcb(cd, req, f_flags); diff --git a/drivers/misc/hisi_hikey_usb.c b/drivers/misc/hisi_hikey_usb.c index ffe7b945a298..2c6e448a47f1 100644 --- a/drivers/misc/hisi_hikey_usb.c +++ b/drivers/misc/hisi_hikey_usb.c @@ -18,6 +18,7 @@ #include <linux/property.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include <linux/usb/role.h> #define DEVICE_DRIVER_NAME "hisi_hikey_usb" @@ -67,7 +68,7 @@ static void hub_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value) if (ret) dev_err(hisi_hikey_usb->dev, "Can't switch regulator state to %s\n", - value ? "enabled" : "disabled"); + str_enabled_disabled(value)); } static void usb_switch_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, diff --git a/drivers/misc/ibmasm/ibmasmfs.c b/drivers/misc/ibmasm/ibmasmfs.c index 5372ed2a363e..b26c930e3edb 100644 --- a/drivers/misc/ibmasm/ibmasmfs.c +++ b/drivers/misc/ibmasm/ibmasmfs.c @@ -525,15 +525,9 @@ static ssize_t remote_settings_file_write(struct file *file, const char __user * if (*offset != 0) return 0; - buff = kzalloc (count + 1, GFP_KERNEL); - if (!buff) - return -ENOMEM; - - - if (copy_from_user(buff, ubuff, count)) { - kfree(buff); - return -EFAULT; - } + buff = memdup_user_nul(ubuff, count); + if (IS_ERR(buff)) + return PTR_ERR(buff); value = simple_strtoul(buff, NULL, 10); writel(value, address); diff --git a/drivers/misc/lis3lv02d/Kconfig b/drivers/misc/lis3lv02d/Kconfig index 56005243a230..9d546a42a563 100644 --- a/drivers/misc/lis3lv02d/Kconfig +++ b/drivers/misc/lis3lv02d/Kconfig @@ -4,7 +4,7 @@ # config SENSORS_LIS3_SPI - tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)" + tristate "STMicroelectronics LIS3LV02Dx three-axis digital accelerometer (SPI)" depends on !ACPI && SPI_MASTER && INPUT select SENSORS_LIS3LV02D help @@ -20,7 +20,7 @@ config SENSORS_LIS3_SPI is called lis3lv02d_spi. config SENSORS_LIS3_I2C - tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (I2C)" + tristate "STMicroelectronics LIS3LV02Dx three-axis digital accelerometer (I2C)" depends on I2C && INPUT select SENSORS_LIS3LV02D help diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 90dba20b2de7..e6a1d3534663 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -386,7 +386,7 @@ static int mei_nfc_if_version(struct mei_cl *cl, ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(cmd), 0, MEI_CL_IO_TX_BLOCKING); if (ret < 0) { - dev_err(bus->dev, "Could not send IF version cmd ret = %d\n", ret); + dev_err(&bus->dev, "Could not send IF version cmd ret = %d\n", ret); return ret; } @@ -401,14 +401,14 @@ static int mei_nfc_if_version(struct mei_cl *cl, bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length, &vtag, 0, 0); if (bytes_recv < 0 || (size_t)bytes_recv < if_version_length) { - dev_err(bus->dev, "Could not read IF version ret = %d\n", bytes_recv); + dev_err(&bus->dev, "Could not read IF version ret = %d\n", bytes_recv); ret = -EIO; goto err; } memcpy(ver, reply->data, sizeof(*ver)); - dev_info(bus->dev, "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n", + dev_info(&bus->dev, "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n", ver->fw_ivn, ver->vendor_id, ver->radio_type); err: diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 09aae8f9d225..2c810ab12e62 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -650,7 +650,7 @@ EXPORT_SYMBOL_GPL(mei_cldev_enabled); */ static bool mei_cl_bus_module_get(struct mei_cl_device *cldev) { - return try_module_get(cldev->bus->dev->driver->owner); + return try_module_get(cldev->bus->parent->driver->owner); } /** @@ -660,7 +660,7 @@ static bool mei_cl_bus_module_get(struct mei_cl_device *cldev) */ static void mei_cl_bus_module_put(struct mei_cl_device *cldev) { - module_put(cldev->bus->dev->driver->owner); + module_put(cldev->bus->parent->driver->owner); } /** @@ -827,7 +827,7 @@ int mei_cldev_enable(struct mei_cl_device *cldev) ret = mei_cl_connect(cl, cldev->me_cl, NULL); if (ret < 0) { - dev_err(&cldev->dev, "cannot connect\n"); + dev_dbg(&cldev->dev, "cannot connect\n"); mei_cl_bus_vtag_free(cldev); } @@ -1298,16 +1298,20 @@ static const struct bus_type mei_cl_bus_type = { static struct mei_device *mei_dev_bus_get(struct mei_device *bus) { - if (bus) - get_device(bus->dev); + if (bus) { + get_device(&bus->dev); + get_device(bus->parent); + } return bus; } static void mei_dev_bus_put(struct mei_device *bus) { - if (bus) - put_device(bus->dev); + if (bus) { + put_device(bus->parent); + put_device(&bus->dev); + } } static void mei_cl_bus_dev_release(struct device *dev) @@ -1341,7 +1345,7 @@ static const struct device_type mei_cl_device_type = { static inline void mei_cl_bus_set_name(struct mei_cl_device *cldev) { dev_set_name(&cldev->dev, "%s-%pUl", - dev_name(cldev->bus->dev), + dev_name(cldev->bus->parent), mei_me_cl_uuid(cldev->me_cl)); } @@ -1370,7 +1374,7 @@ static struct mei_cl_device *mei_cl_bus_dev_alloc(struct mei_device *bus, } device_initialize(&cldev->dev); - cldev->dev.parent = bus->dev; + cldev->dev.parent = bus->parent; cldev->dev.bus = &mei_cl_bus_type; cldev->dev.type = &mei_cl_device_type; cldev->bus = mei_dev_bus_get(bus); @@ -1505,7 +1509,7 @@ static void mei_cl_bus_dev_init(struct mei_device *bus, WARN_ON(!mutex_is_locked(&bus->cl_bus_lock)); - dev_dbg(bus->dev, "initializing %pUl", mei_me_cl_uuid(me_cl)); + dev_dbg(&bus->dev, "initializing %pUl", mei_me_cl_uuid(me_cl)); if (me_cl->bus_added) return; @@ -1556,7 +1560,7 @@ static void mei_cl_bus_rescan(struct mei_device *bus) } mutex_unlock(&bus->cl_bus_lock); - dev_dbg(bus->dev, "rescan end"); + dev_dbg(&bus->dev, "rescan end"); } void mei_cl_bus_rescan_work(struct work_struct *work) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 3db07d2a881f..159e8b841564 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -262,7 +262,7 @@ void mei_me_cl_rm_by_uuid(struct mei_device *dev, const uuid_le *uuid) { struct mei_me_client *me_cl; - dev_dbg(dev->dev, "remove %pUl\n", uuid); + dev_dbg(&dev->dev, "remove %pUl\n", uuid); down_write(&dev->me_clients_rwsem); me_cl = __mei_me_cl_by_uuid(dev, uuid); @@ -635,12 +635,12 @@ int mei_cl_link(struct mei_cl *cl) id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX); if (id >= MEI_CLIENTS_MAX) { - dev_err(dev->dev, "id exceeded %d", MEI_CLIENTS_MAX); + dev_err(&dev->dev, "id exceeded %d", MEI_CLIENTS_MAX); return -EMFILE; } if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { - dev_err(dev->dev, "open_handle_count exceeded %d", + dev_err(&dev->dev, "open_handle_count exceeded %d", MEI_MAX_OPEN_HANDLE_COUNT); return -EMFILE; } @@ -709,9 +709,9 @@ void mei_host_client_init(struct mei_device *dev) schedule_work(&dev->bus_rescan_work); - pm_runtime_mark_last_busy(dev->dev); - dev_dbg(dev->dev, "rpm: autosuspend\n"); - pm_request_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + dev_dbg(&dev->dev, "rpm: autosuspend\n"); + pm_request_autosuspend(dev->parent); } /** @@ -724,12 +724,12 @@ bool mei_hbuf_acquire(struct mei_device *dev) { if (mei_pg_state(dev) == MEI_PG_ON || mei_pg_in_transition(dev)) { - dev_dbg(dev->dev, "device is in pg\n"); + dev_dbg(&dev->dev, "device is in pg\n"); return false; } if (!dev->hbuf_is_ready) { - dev_dbg(dev->dev, "hbuf is not ready\n"); + dev_dbg(&dev->dev, "hbuf is not ready\n"); return false; } @@ -981,9 +981,9 @@ int mei_cl_disconnect(struct mei_cl *cl) return 0; } - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } @@ -991,8 +991,8 @@ int mei_cl_disconnect(struct mei_cl *cl) rets = __mei_cl_disconnect(cl); cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); return rets; } @@ -1118,9 +1118,9 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, goto nortpm; } - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); goto nortpm; } @@ -1167,8 +1167,8 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, rets = cl->status; out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); @@ -1517,9 +1517,9 @@ int mei_cl_notify_request(struct mei_cl *cl, if (!mei_cl_is_connected(cl)) return -ENODEV; - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } @@ -1554,8 +1554,8 @@ int mei_cl_notify_request(struct mei_cl *cl, out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); return rets; @@ -1683,9 +1683,9 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp) mei_cl_set_read_by_fp(cl, fp); - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); goto nortpm; } @@ -1702,8 +1702,8 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp) out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); nortpm: if (rets) mei_io_cb_free(cb); @@ -1972,9 +1972,9 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, unsigned long time blocking = cb->blocking; data = buf->data; - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %zd\n", rets); goto free; } @@ -2092,8 +2092,8 @@ out: rets = buf_len; err: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); free: mei_io_cb_free(cb); @@ -2119,8 +2119,8 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) if (waitqueue_active(&cl->tx_wait)) { wake_up_interruptible(&cl->tx_wait); } else { - pm_runtime_mark_last_busy(dev->dev); - pm_request_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_request_autosuspend(dev->parent); } break; @@ -2251,7 +2251,7 @@ int mei_cl_irq_dma_unmap(struct mei_cl *cl, struct mei_cl_cb *cb, static int mei_cl_dma_alloc(struct mei_cl *cl, u8 buf_id, size_t size) { - cl->dma.vaddr = dmam_alloc_coherent(cl->dev->dev, size, + cl->dma.vaddr = dmam_alloc_coherent(&cl->dev->dev, size, &cl->dma.daddr, GFP_KERNEL); if (!cl->dma.vaddr) return -ENOMEM; @@ -2265,7 +2265,7 @@ static int mei_cl_dma_alloc(struct mei_cl *cl, u8 buf_id, size_t size) static void mei_cl_dma_free(struct mei_cl *cl) { cl->dma.buffer_id = 0; - dmam_free_coherent(cl->dev->dev, + dmam_free_coherent(&cl->dev->dev, cl->dma.size, cl->dma.vaddr, cl->dma.daddr); cl->dma.size = 0; cl->dma.vaddr = NULL; @@ -2321,16 +2321,16 @@ int mei_cl_dma_alloc_and_map(struct mei_cl *cl, const struct file *fp, return -EPROTO; } - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } rets = mei_cl_dma_alloc(cl, buffer_id, size); if (rets) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); return rets; } @@ -2366,8 +2366,8 @@ out: mei_cl_dma_free(cl); cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); return rets; @@ -2406,9 +2406,9 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp) if (!cl->dma_mapped) return -EPROTO; - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } @@ -2444,8 +2444,8 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp) mei_cl_dma_free(cl); out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); return rets; diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 01ed26a148c4..031114478bcb 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -275,12 +275,12 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp); #define MEI_CL_PRM(cl) (cl)->host_client_id, mei_cl_me_id(cl) #define cl_dbg(dev, cl, format, arg...) \ - dev_dbg((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) + dev_dbg(&(dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) #define cl_warn(dev, cl, format, arg...) \ - dev_warn((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) + dev_warn(&(dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) #define cl_err(dev, cl, format, arg...) \ - dev_err((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) + dev_err(&(dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) #endif /* _MEI_CLIENT_H_ */ diff --git a/drivers/misc/mei/dma-ring.c b/drivers/misc/mei/dma-ring.c index 651e77ef82bd..6277c4a5b0fd 100644 --- a/drivers/misc/mei/dma-ring.c +++ b/drivers/misc/mei/dma-ring.c @@ -30,7 +30,7 @@ static int mei_dmam_dscr_alloc(struct mei_device *dev, if (dscr->vaddr) return 0; - dscr->vaddr = dmam_alloc_coherent(dev->dev, dscr->size, &dscr->daddr, + dscr->vaddr = dmam_alloc_coherent(dev->parent, dscr->size, &dscr->daddr, GFP_KERNEL); if (!dscr->vaddr) return -ENOMEM; @@ -50,7 +50,7 @@ static void mei_dmam_dscr_free(struct mei_device *dev, if (!dscr->vaddr) return; - dmam_free_coherent(dev->dev, dscr->size, dscr->vaddr, dscr->daddr); + dmam_free_coherent(dev->parent, dscr->size, dscr->vaddr, dscr->daddr); dscr->vaddr = NULL; } @@ -177,7 +177,7 @@ void mei_dma_ring_read(struct mei_device *dev, unsigned char *buf, u32 len) if (WARN_ON(!ctrl)) return; - dev_dbg(dev->dev, "reading from dma %u bytes\n", len); + dev_dbg(&dev->dev, "reading from dma %u bytes\n", len); if (!len) return; @@ -254,7 +254,7 @@ void mei_dma_ring_write(struct mei_device *dev, unsigned char *buf, u32 len) if (WARN_ON(!ctrl)) return; - dev_dbg(dev->dev, "writing to dma %u bytes\n", len); + dev_dbg(&dev->dev, "writing to dma %u bytes\n", len); hbuf_depth = mei_dma_ring_hbuf_depth(dev); wr_idx = READ_ONCE(ctrl->hbuf_wr_idx) & (hbuf_depth - 1); slots = mei_data2slots(len); diff --git a/drivers/misc/mei/gsc-me.c b/drivers/misc/mei/gsc-me.c index 5a8c26c3df13..93cba090ea08 100644 --- a/drivers/misc/mei/gsc-me.c +++ b/drivers/misc/mei/gsc-me.c @@ -106,11 +106,15 @@ static int mei_gsc_probe(struct auxiliary_device *aux_dev, } } + ret = mei_register(dev, device); + if (ret) + goto deinterrupt; + pm_runtime_get_noresume(device); pm_runtime_set_active(device); pm_runtime_enable(device); - /* Continue to char device setup in spite of firmware handshake failure. + /* Continue in spite of firmware handshake failure. * In order to provide access to the firmware status registers to the user * space via sysfs. */ @@ -120,18 +124,12 @@ static int mei_gsc_probe(struct auxiliary_device *aux_dev, pm_runtime_set_autosuspend_delay(device, MEI_GSC_RPM_TIMEOUT); pm_runtime_use_autosuspend(device); - ret = mei_register(dev, device); - if (ret) - goto register_err; - pm_runtime_put_noidle(device); return 0; -register_err: - mei_stop(dev); +deinterrupt: if (!mei_me_hw_use_polling(hw)) devm_free_irq(device, hw->irq, dev); - err: dev_err(device, "probe failed: %d\n", ret); dev_set_drvdata(device, NULL); @@ -152,13 +150,13 @@ static void mei_gsc_remove(struct auxiliary_device *aux_dev) if (mei_me_hw_use_polling(hw)) kthread_stop(hw->polling_thread); - mei_deregister(dev); - pm_runtime_disable(&aux_dev->dev); mei_disable_interrupts(dev); if (!mei_me_hw_use_polling(hw)) devm_free_irq(&aux_dev->dev, hw->irq, dev); + + mei_deregister(dev); } static int __maybe_unused mei_gsc_pm_suspend(struct device *device) @@ -252,7 +250,7 @@ static int __maybe_unused mei_gsc_pm_runtime_resume(struct device *device) irq_ret = mei_me_irq_thread_handler(1, dev); if (irq_ret != IRQ_HANDLED) - dev_err(dev->dev, "thread handler fail %d\n", irq_ret); + dev_err(&dev->dev, "thread handler fail %d\n", irq_ret); return 0; } diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 4fe9a2752d43..ccd9df5d1c7d 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -239,7 +239,7 @@ int mei_hbm_start_wait(struct mei_device *dev) if (ret == 0 && (dev->hbm_state <= MEI_HBM_STARTING)) { dev->hbm_state = MEI_HBM_IDLE; - dev_err(dev->dev, "waiting for mei start failed\n"); + dev_err(&dev->dev, "waiting for mei start failed\n"); return -ETIME; } return 0; @@ -271,8 +271,7 @@ int mei_hbm_start_req(struct mei_device *dev) dev->hbm_state = MEI_HBM_IDLE; ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, "version message write failed: ret = %d\n", - ret); + dev_err(&dev->dev, "version message write failed: ret = %d\n", ret); return ret; } @@ -312,8 +311,7 @@ static int mei_hbm_dma_setup_req(struct mei_device *dev) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, "dma setup request write failed: ret = %d.\n", - ret); + dev_err(&dev->dev, "dma setup request write failed: ret = %d.\n", ret); return ret; } @@ -351,8 +349,7 @@ static int mei_hbm_capabilities_req(struct mei_device *dev) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, - "capabilities request write failed: ret = %d.\n", ret); + dev_err(&dev->dev, "capabilities request write failed: ret = %d.\n", ret); return ret; } @@ -386,8 +383,7 @@ static int mei_hbm_enum_clients_req(struct mei_device *dev) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, "enumeration request write failed: ret = %d.\n", - ret); + dev_err(&dev->dev, "enumeration request write failed: ret = %d.\n", ret); return ret; } dev->hbm_state = MEI_HBM_ENUM_CLIENTS; @@ -443,7 +439,7 @@ static int mei_hbm_add_cl_resp(struct mei_device *dev, u8 addr, u8 status) struct hbm_add_client_response resp; int ret; - dev_dbg(dev->dev, "adding client response\n"); + dev_dbg(&dev->dev, "adding client response\n"); mei_hbm_hdr(&mei_hdr, sizeof(resp)); @@ -454,8 +450,7 @@ static int mei_hbm_add_cl_resp(struct mei_device *dev, u8 addr, u8 status) ret = mei_hbm_write_message(dev, &mei_hdr, &resp); if (ret) - dev_err(dev->dev, "add client response write failed: ret = %d\n", - ret); + dev_err(&dev->dev, "add client response write failed: ret = %d\n", ret); return ret; } @@ -752,7 +747,7 @@ static int mei_hbm_prop_req(struct mei_device *dev, unsigned long start_idx) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, "properties request write failed: ret = %d\n", + dev_err(&dev->dev, "properties request write failed: ret = %d\n", ret); return ret; } @@ -788,7 +783,7 @@ int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) - dev_err(dev->dev, "power gate command write failed.\n"); + dev_err(&dev->dev, "power gate command write failed.\n"); return ret; } EXPORT_SYMBOL_GPL(mei_hbm_pg); @@ -847,7 +842,7 @@ static int mei_hbm_add_single_tx_flow_ctrl_creds(struct mei_device *dev, me_cl = mei_me_cl_by_id(dev, fctrl->me_addr); if (!me_cl) { - dev_err(dev->dev, "no such me client %d\n", fctrl->me_addr); + dev_err(&dev->dev, "no such me client %d\n", fctrl->me_addr); return -ENOENT; } @@ -857,7 +852,7 @@ static int mei_hbm_add_single_tx_flow_ctrl_creds(struct mei_device *dev, } me_cl->tx_flow_ctrl_creds++; - dev_dbg(dev->dev, "recv flow ctrl msg ME %d (single) creds = %d.\n", + dev_dbg(&dev->dev, "recv flow ctrl msg ME %d (single) creds = %d.\n", fctrl->me_addr, me_cl->tx_flow_ctrl_creds); rets = 0; @@ -1085,7 +1080,7 @@ static int mei_hbm_pg_enter_res(struct mei_device *dev) { if (mei_pg_state(dev) != MEI_PG_OFF || dev->pg_event != MEI_PG_EVENT_WAIT) { - dev_err(dev->dev, "hbm: pg entry response: state mismatch [%s, %d]\n", + dev_err(&dev->dev, "hbm: pg entry response: state mismatch [%s, %d]\n", mei_pg_state_str(mei_pg_state(dev)), dev->pg_event); return -EPROTO; } @@ -1103,7 +1098,7 @@ static int mei_hbm_pg_enter_res(struct mei_device *dev) */ void mei_hbm_pg_resume(struct mei_device *dev) { - pm_request_resume(dev->dev); + pm_request_resume(dev->parent); } EXPORT_SYMBOL_GPL(mei_hbm_pg_resume); @@ -1119,7 +1114,7 @@ static int mei_hbm_pg_exit_res(struct mei_device *dev) if (mei_pg_state(dev) != MEI_PG_ON || (dev->pg_event != MEI_PG_EVENT_WAIT && dev->pg_event != MEI_PG_EVENT_IDLE)) { - dev_err(dev->dev, "hbm: pg exit response: state mismatch [%s, %d]\n", + dev_err(&dev->dev, "hbm: pg exit response: state mismatch [%s, %d]\n", mei_pg_state_str(mei_pg_state(dev)), dev->pg_event); return -EPROTO; } @@ -1276,19 +1271,19 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) * hbm is put to idle during system reset */ if (dev->hbm_state == MEI_HBM_IDLE) { - dev_dbg(dev->dev, "hbm: state is idle ignore spurious messages\n"); + dev_dbg(&dev->dev, "hbm: state is idle ignore spurious messages\n"); return 0; } switch (mei_msg->hbm_cmd) { case HOST_START_RES_CMD: - dev_dbg(dev->dev, "hbm: start: response message received.\n"); + dev_dbg(&dev->dev, "hbm: start: response message received.\n"); dev->init_clients_timer = 0; version_res = (struct hbm_host_version_response *)mei_msg; - dev_dbg(dev->dev, "HBM VERSION: DRIVER=%02d:%02d DEVICE=%02d:%02d\n", + dev_dbg(&dev->dev, "HBM VERSION: DRIVER=%02d:%02d DEVICE=%02d:%02d\n", HBM_MAJOR_VERSION, HBM_MINOR_VERSION, version_res->me_max_version.major_version, version_res->me_max_version.minor_version); @@ -1304,11 +1299,11 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) } if (!mei_hbm_version_is_supported(dev)) { - dev_warn(dev->dev, "hbm: start: version mismatch - stopping the driver.\n"); + dev_warn(&dev->dev, "hbm: start: version mismatch - stopping the driver.\n"); dev->hbm_state = MEI_HBM_STOPPED; if (mei_hbm_stop_req(dev)) { - dev_err(dev->dev, "hbm: start: failed to send stop request\n"); + dev_err(&dev->dev, "hbm: start: failed to send stop request\n"); return -EIO; } break; @@ -1320,10 +1315,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->hbm_state != MEI_HBM_STARTING) { if (dev->dev_state == MEI_DEV_POWER_DOWN || dev->dev_state == MEI_DEV_POWERING_DOWN) { - dev_dbg(dev->dev, "hbm: start: on shutdown, ignoring\n"); + dev_dbg(&dev->dev, "hbm: start: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: start: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: start: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1337,7 +1332,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) if (dev->hbm_f_dr_supported) { if (mei_dmam_ring_alloc(dev)) - dev_info(dev->dev, "running w/o dma ring\n"); + dev_info(&dev->dev, "running w/o dma ring\n"); if (mei_dma_ring_is_allocated(dev)) { if (mei_hbm_dma_setup_req(dev)) return -EIO; @@ -1357,7 +1352,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case MEI_HBM_CAPABILITIES_RES_CMD: - dev_dbg(dev->dev, "hbm: capabilities response: message received.\n"); + dev_dbg(&dev->dev, "hbm: capabilities response: message received.\n"); dev->init_clients_timer = 0; @@ -1365,10 +1360,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->hbm_state != MEI_HBM_CAP_SETUP) { if (dev->dev_state == MEI_DEV_POWER_DOWN || dev->dev_state == MEI_DEV_POWERING_DOWN) { - dev_dbg(dev->dev, "hbm: capabilities response: on shutdown, ignoring\n"); + dev_dbg(&dev->dev, "hbm: capabilities response: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: capabilities response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: capabilities response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1384,7 +1379,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) if (dev->hbm_f_dr_supported) { if (mei_dmam_ring_alloc(dev)) - dev_info(dev->dev, "running w/o dma ring\n"); + dev_info(&dev->dev, "running w/o dma ring\n"); if (mei_dma_ring_is_allocated(dev)) { if (mei_hbm_dma_setup_req(dev)) return -EIO; @@ -1400,7 +1395,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case MEI_HBM_DMA_SETUP_RES_CMD: - dev_dbg(dev->dev, "hbm: dma setup response: message received.\n"); + dev_dbg(&dev->dev, "hbm: dma setup response: message received.\n"); dev->init_clients_timer = 0; @@ -1408,10 +1403,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->hbm_state != MEI_HBM_DR_SETUP) { if (dev->dev_state == MEI_DEV_POWER_DOWN || dev->dev_state == MEI_DEV_POWERING_DOWN) { - dev_dbg(dev->dev, "hbm: dma setup response: on shutdown, ignoring\n"); + dev_dbg(&dev->dev, "hbm: dma setup response: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: dma setup response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: dma setup response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1422,9 +1417,9 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) u8 status = dma_setup_res->status; if (status == MEI_HBMS_NOT_ALLOWED) { - dev_dbg(dev->dev, "hbm: dma setup not allowed\n"); + dev_dbg(&dev->dev, "hbm: dma setup not allowed\n"); } else { - dev_info(dev->dev, "hbm: dma setup response: failure = %d %s\n", + dev_info(&dev->dev, "hbm: dma setup response: failure = %d %s\n", status, mei_hbm_status_str(status)); } @@ -1437,38 +1432,38 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case CLIENT_CONNECT_RES_CMD: - dev_dbg(dev->dev, "hbm: client connect response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client connect response: message received.\n"); mei_hbm_cl_res(dev, cl_cmd, MEI_FOP_CONNECT); break; case CLIENT_DISCONNECT_RES_CMD: - dev_dbg(dev->dev, "hbm: client disconnect response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client disconnect response: message received.\n"); mei_hbm_cl_res(dev, cl_cmd, MEI_FOP_DISCONNECT); break; case MEI_FLOW_CONTROL_CMD: - dev_dbg(dev->dev, "hbm: client flow control response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client flow control response: message received.\n"); fctrl = (struct hbm_flow_control *)mei_msg; mei_hbm_cl_tx_flow_ctrl_creds_res(dev, fctrl); break; case MEI_PG_ISOLATION_ENTRY_RES_CMD: - dev_dbg(dev->dev, "hbm: power gate isolation entry response received\n"); + dev_dbg(&dev->dev, "hbm: power gate isolation entry response received\n"); ret = mei_hbm_pg_enter_res(dev); if (ret) return ret; break; case MEI_PG_ISOLATION_EXIT_REQ_CMD: - dev_dbg(dev->dev, "hbm: power gate isolation exit request received\n"); + dev_dbg(&dev->dev, "hbm: power gate isolation exit request received\n"); ret = mei_hbm_pg_exit_res(dev); if (ret) return ret; break; case HOST_CLIENT_PROPERTIES_RES_CMD: - dev_dbg(dev->dev, "hbm: properties response: message received.\n"); + dev_dbg(&dev->dev, "hbm: properties response: message received.\n"); dev->init_clients_timer = 0; @@ -1476,10 +1471,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->hbm_state != MEI_HBM_CLIENT_PROPERTIES) { if (dev->dev_state == MEI_DEV_POWER_DOWN || dev->dev_state == MEI_DEV_POWERING_DOWN) { - dev_dbg(dev->dev, "hbm: properties response: on shutdown, ignoring\n"); + dev_dbg(&dev->dev, "hbm: properties response: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: properties response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: properties response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1487,10 +1482,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) props_res = (struct hbm_props_response *)mei_msg; if (props_res->status == MEI_HBMS_CLIENT_NOT_FOUND) { - dev_dbg(dev->dev, "hbm: properties response: %d CLIENT_NOT_FOUND\n", + dev_dbg(&dev->dev, "hbm: properties response: %d CLIENT_NOT_FOUND\n", props_res->me_addr); } else if (props_res->status) { - dev_err(dev->dev, "hbm: properties response: wrong status = %d %s\n", + dev_err(&dev->dev, "hbm: properties response: wrong status = %d %s\n", props_res->status, mei_hbm_status_str(props_res->status)); return -EPROTO; @@ -1505,7 +1500,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case HOST_ENUM_RES_CMD: - dev_dbg(dev->dev, "hbm: enumeration response: message received\n"); + dev_dbg(&dev->dev, "hbm: enumeration response: message received\n"); dev->init_clients_timer = 0; @@ -1519,10 +1514,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->hbm_state != MEI_HBM_ENUM_CLIENTS) { if (dev->dev_state == MEI_DEV_POWER_DOWN || dev->dev_state == MEI_DEV_POWERING_DOWN) { - dev_dbg(dev->dev, "hbm: enumeration response: on shutdown, ignoring\n"); + dev_dbg(&dev->dev, "hbm: enumeration response: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: enumeration response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: enumeration response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1536,77 +1531,77 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case HOST_STOP_RES_CMD: - dev_dbg(dev->dev, "hbm: stop response: message received\n"); + dev_dbg(&dev->dev, "hbm: stop response: message received\n"); dev->init_clients_timer = 0; if (dev->hbm_state != MEI_HBM_STOPPED) { - dev_err(dev->dev, "hbm: stop response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: stop response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } mei_set_devstate(dev, MEI_DEV_POWER_DOWN); - dev_info(dev->dev, "hbm: stop response: resetting.\n"); + dev_info(&dev->dev, "hbm: stop response: resetting.\n"); /* force the reset */ return -EPROTO; case CLIENT_DISCONNECT_REQ_CMD: - dev_dbg(dev->dev, "hbm: disconnect request: message received\n"); + dev_dbg(&dev->dev, "hbm: disconnect request: message received\n"); disconnect_req = (struct hbm_client_connect_request *)mei_msg; mei_hbm_fw_disconnect_req(dev, disconnect_req); break; case ME_STOP_REQ_CMD: - dev_dbg(dev->dev, "hbm: stop request: message received\n"); + dev_dbg(&dev->dev, "hbm: stop request: message received\n"); dev->hbm_state = MEI_HBM_STOPPED; if (mei_hbm_stop_req(dev)) { - dev_err(dev->dev, "hbm: stop request: failed to send stop request\n"); + dev_err(&dev->dev, "hbm: stop request: failed to send stop request\n"); return -EIO; } break; case MEI_HBM_ADD_CLIENT_REQ_CMD: - dev_dbg(dev->dev, "hbm: add client request received\n"); + dev_dbg(&dev->dev, "hbm: add client request received\n"); /* * after the host receives the enum_resp * message clients may be added or removed */ if (dev->hbm_state <= MEI_HBM_ENUM_CLIENTS || dev->hbm_state >= MEI_HBM_STOPPED) { - dev_err(dev->dev, "hbm: add client: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: add client: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } add_cl_req = (struct hbm_add_client_request *)mei_msg; ret = mei_hbm_fw_add_cl_req(dev, add_cl_req); if (ret) { - dev_err(dev->dev, "hbm: add client: failed to send response %d\n", + dev_err(&dev->dev, "hbm: add client: failed to send response %d\n", ret); return -EIO; } - dev_dbg(dev->dev, "hbm: add client request processed\n"); + dev_dbg(&dev->dev, "hbm: add client request processed\n"); break; case MEI_HBM_NOTIFY_RES_CMD: - dev_dbg(dev->dev, "hbm: notify response received\n"); + dev_dbg(&dev->dev, "hbm: notify response received\n"); mei_hbm_cl_res(dev, cl_cmd, notify_res_to_fop(cl_cmd)); break; case MEI_HBM_NOTIFICATION_CMD: - dev_dbg(dev->dev, "hbm: notification\n"); + dev_dbg(&dev->dev, "hbm: notification\n"); mei_hbm_cl_notify(dev, cl_cmd); break; case MEI_HBM_CLIENT_DMA_MAP_RES_CMD: - dev_dbg(dev->dev, "hbm: client dma map response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client dma map response: message received.\n"); client_dma_res = (struct hbm_client_dma_response *)mei_msg; mei_hbm_cl_dma_map_res(dev, client_dma_res); break; case MEI_HBM_CLIENT_DMA_UNMAP_RES_CMD: - dev_dbg(dev->dev, "hbm: client dma unmap response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client dma unmap response: message received.\n"); client_dma_res = (struct hbm_client_dma_response *)mei_msg; mei_hbm_cl_dma_unmap_res(dev, client_dma_res); break; diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index d11a0740b47c..d4612c659784 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -84,7 +84,7 @@ static inline u32 mei_me_mecsr_read(const struct mei_device *dev) u32 reg; reg = mei_me_reg_read(to_me_hw(dev), ME_CSR_HA); - trace_mei_reg_read(dev->dev, "ME_CSR_HA", ME_CSR_HA, reg); + trace_mei_reg_read(&dev->dev, "ME_CSR_HA", ME_CSR_HA, reg); return reg; } @@ -101,7 +101,7 @@ static inline u32 mei_hcsr_read(const struct mei_device *dev) u32 reg; reg = mei_me_reg_read(to_me_hw(dev), H_CSR); - trace_mei_reg_read(dev->dev, "H_CSR", H_CSR, reg); + trace_mei_reg_read(&dev->dev, "H_CSR", H_CSR, reg); return reg; } @@ -114,7 +114,7 @@ static inline u32 mei_hcsr_read(const struct mei_device *dev) */ static inline void mei_hcsr_write(struct mei_device *dev, u32 reg) { - trace_mei_reg_write(dev->dev, "H_CSR", H_CSR, reg); + trace_mei_reg_write(&dev->dev, "H_CSR", H_CSR, reg); mei_me_reg_write(to_me_hw(dev), H_CSR, reg); } @@ -156,7 +156,7 @@ static inline u32 mei_me_d0i3c_read(const struct mei_device *dev) u32 reg; reg = mei_me_reg_read(to_me_hw(dev), H_D0I3C); - trace_mei_reg_read(dev->dev, "H_D0I3C", H_D0I3C, reg); + trace_mei_reg_read(&dev->dev, "H_D0I3C", H_D0I3C, reg); return reg; } @@ -169,7 +169,7 @@ static inline u32 mei_me_d0i3c_read(const struct mei_device *dev) */ static inline void mei_me_d0i3c_write(struct mei_device *dev, u32 reg) { - trace_mei_reg_write(dev->dev, "H_D0I3C", H_D0I3C, reg); + trace_mei_reg_write(&dev->dev, "H_D0I3C", H_D0I3C, reg); mei_me_reg_write(to_me_hw(dev), H_D0I3C, reg); } @@ -189,7 +189,7 @@ static int mei_me_trc_status(struct mei_device *dev, u32 *trc) return -EOPNOTSUPP; *trc = mei_me_reg_read(hw, ME_TRC); - trace_mei_reg_read(dev->dev, "ME_TRC", ME_TRC, *trc); + trace_mei_reg_read(&dev->dev, "ME_TRC", ME_TRC, *trc); return 0; } @@ -217,7 +217,7 @@ static int mei_me_fw_status(struct mei_device *dev, for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) { ret = hw->read_fws(dev, fw_src->status[i], &fw_status->status[i]); - trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_X", + trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HFS_X", fw_src->status[i], fw_status->status[i]); if (ret) @@ -251,7 +251,7 @@ static int mei_me_hw_config(struct mei_device *dev) reg = 0; hw->read_fws(dev, PCI_CFG_HFS_1, ®); - trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_1", PCI_CFG_HFS_1, reg); + trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HFS_1", PCI_CFG_HFS_1, reg); hw->d0i3_supported = ((reg & PCI_CFG_HFS_1_D0I3_MSK) == PCI_CFG_HFS_1_D0I3_MSK); @@ -447,7 +447,7 @@ static void mei_gsc_pxp_check(struct mei_device *dev) return; hw->read_fws(dev, PCI_CFG_HFS_5, &fwsts5); - trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_5", PCI_CFG_HFS_5, fwsts5); + trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HFS_5", PCI_CFG_HFS_5, fwsts5); if ((fwsts5 & GSC_CFG_HFS_5_BOOT_TYPE_MSK) == GSC_CFG_HFS_5_BOOT_TYPE_PXP) { if (dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_DEFAULT) @@ -460,10 +460,10 @@ static void mei_gsc_pxp_check(struct mei_device *dev) return; if ((fwsts5 & GSC_CFG_HFS_5_BOOT_TYPE_MSK) == GSC_CFG_HFS_5_BOOT_TYPE_PXP) { - dev_dbg(dev->dev, "pxp mode is ready 0x%08x\n", fwsts5); + dev_dbg(&dev->dev, "pxp mode is ready 0x%08x\n", fwsts5); dev->pxp_mode = MEI_DEV_PXP_READY; } else { - dev_dbg(dev->dev, "pxp mode is not ready 0x%08x\n", fwsts5); + dev_dbg(&dev->dev, "pxp mode is not ready 0x%08x\n", fwsts5); } } @@ -482,7 +482,7 @@ static int mei_me_hw_ready_wait(struct mei_device *dev) dev->timeouts.hw_ready); mutex_lock(&dev->device_lock); if (!dev->recvd_hw_ready) { - dev_err(dev->dev, "wait hw ready failed\n"); + dev_err(&dev->dev, "wait hw ready failed\n"); return -ETIME; } @@ -494,43 +494,6 @@ static int mei_me_hw_ready_wait(struct mei_device *dev) } /** - * mei_me_check_fw_reset - check for the firmware reset error and exception conditions - * - * @dev: mei device - */ -static void mei_me_check_fw_reset(struct mei_device *dev) -{ - struct mei_fw_status fw_status; - char fw_sts_str[MEI_FW_STATUS_STR_SZ] = {0}; - int ret; - u32 fw_pm_event = 0; - - if (!dev->saved_fw_status_flag) - goto end; - - if (dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_PERFORMED) { - ret = mei_fw_status(dev, &fw_status); - if (!ret) { - fw_pm_event = fw_status.status[1] & PCI_CFG_HFS_2_PM_EVENT_MASK; - if (fw_pm_event != PCI_CFG_HFS_2_PM_CMOFF_TO_CMX_ERROR && - fw_pm_event != PCI_CFG_HFS_2_PM_CM_RESET_ERROR) - goto end; - } else { - dev_err(dev->dev, "failed to read firmware status: %d\n", ret); - } - } - - mei_fw_status2str(&dev->saved_fw_status, fw_sts_str, sizeof(fw_sts_str)); - dev_warn(dev->dev, "unexpected reset: fw_pm_event = 0x%x, dev_state = %u fw status = %s\n", - fw_pm_event, dev->saved_dev_state, fw_sts_str); - -end: - if (dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_PERFORMED) - dev->gsc_reset_to_pxp = MEI_DEV_RESET_TO_PXP_DONE; - dev->saved_fw_status_flag = false; -} - -/** * mei_me_hw_start - hw start routine * * @dev: mei device @@ -540,11 +503,12 @@ static int mei_me_hw_start(struct mei_device *dev) { int ret = mei_me_hw_ready_wait(dev); - if (kind_is_gsc(dev) || kind_is_gscfi(dev)) - mei_me_check_fw_reset(dev); + if ((kind_is_gsc(dev) || kind_is_gscfi(dev)) && + dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_PERFORMED) + dev->gsc_reset_to_pxp = MEI_DEV_RESET_TO_PXP_DONE; if (ret) return ret; - dev_dbg(dev->dev, "hw is ready\n"); + dev_dbg(&dev->dev, "hw is ready\n"); mei_me_host_set_ready(dev); return ret; @@ -644,14 +608,14 @@ static int mei_me_hbuf_write(struct mei_device *dev, return -EINVAL; if (!data && data_len) { - dev_err(dev->dev, "wrong parameters null data with data_len = %zu\n", data_len); + dev_err(&dev->dev, "wrong parameters null data with data_len = %zu\n", data_len); return -EINVAL; } - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); + dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); empty_slots = mei_hbuf_empty_slots(dev); - dev_dbg(dev->dev, "empty slots = %d.\n", empty_slots); + dev_dbg(&dev->dev, "empty slots = %d.\n", empty_slots); if (empty_slots < 0) return -EOVERFLOW; @@ -706,7 +670,7 @@ static int mei_me_count_full_read_slots(struct mei_device *dev) if (filled_slots > buffer_depth) return -EOVERFLOW; - dev_dbg(dev->dev, "filled_slots =%08x\n", filled_slots); + dev_dbg(&dev->dev, "filled_slots =%08x\n", filled_slots); return (int)filled_slots; } @@ -748,11 +712,11 @@ static void mei_me_pg_set(struct mei_device *dev) u32 reg; reg = mei_me_reg_read(hw, H_HPG_CSR); - trace_mei_reg_read(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); + trace_mei_reg_read(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); reg |= H_HPG_CSR_PGI; - trace_mei_reg_write(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); + trace_mei_reg_write(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); mei_me_reg_write(hw, H_HPG_CSR, reg); } @@ -767,13 +731,13 @@ static void mei_me_pg_unset(struct mei_device *dev) u32 reg; reg = mei_me_reg_read(hw, H_HPG_CSR); - trace_mei_reg_read(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); + trace_mei_reg_read(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); WARN(!(reg & H_HPG_CSR_PGI), "PGI is not set\n"); reg |= H_HPG_CSR_PGIHEXR; - trace_mei_reg_write(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); + trace_mei_reg_write(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); mei_me_reg_write(hw, H_HPG_CSR, reg); } @@ -905,7 +869,7 @@ static bool mei_me_pg_is_enabled(struct mei_device *dev) return true; notsupported: - dev_dbg(dev->dev, "pg: not supported: d0i3 = %d HGP = %d hbm version %d.%d ?= %d.%d\n", + dev_dbg(&dev->dev, "pg: not supported: d0i3 = %d HGP = %d hbm version %d.%d ?= %d.%d\n", hw->d0i3_supported, !!(reg & ME_PGIC_HRA), dev->version.major_version, @@ -974,7 +938,7 @@ static int mei_me_d0i3_enter_sync(struct mei_device *dev) reg = mei_me_d0i3c_read(dev); if (reg & H_D0I3C_I3) { /* we are in d0i3, nothing to do */ - dev_dbg(dev->dev, "d0i3 set not needed\n"); + dev_dbg(&dev->dev, "d0i3 set not needed\n"); ret = 0; goto on; } @@ -1003,7 +967,7 @@ static int mei_me_d0i3_enter_sync(struct mei_device *dev) reg = mei_me_d0i3_set(dev, true); if (!(reg & H_D0I3C_CIP)) { - dev_dbg(dev->dev, "d0i3 enter wait not needed\n"); + dev_dbg(&dev->dev, "d0i3 enter wait not needed\n"); ret = 0; goto on; } @@ -1027,7 +991,7 @@ on: hw->pg_state = MEI_PG_ON; out: dev->pg_event = MEI_PG_EVENT_IDLE; - dev_dbg(dev->dev, "d0i3 enter ret = %d\n", ret); + dev_dbg(&dev->dev, "d0i3 enter ret = %d\n", ret); return ret; } @@ -1049,7 +1013,7 @@ static int mei_me_d0i3_enter(struct mei_device *dev) reg = mei_me_d0i3c_read(dev); if (reg & H_D0I3C_I3) { /* we are in d0i3, nothing to do */ - dev_dbg(dev->dev, "already d0i3 : set not needed\n"); + dev_dbg(&dev->dev, "already d0i3 : set not needed\n"); goto on; } @@ -1057,7 +1021,7 @@ static int mei_me_d0i3_enter(struct mei_device *dev) on: hw->pg_state = MEI_PG_ON; dev->pg_event = MEI_PG_EVENT_IDLE; - dev_dbg(dev->dev, "d0i3 enter\n"); + dev_dbg(&dev->dev, "d0i3 enter\n"); return 0; } @@ -1079,14 +1043,14 @@ static int mei_me_d0i3_exit_sync(struct mei_device *dev) reg = mei_me_d0i3c_read(dev); if (!(reg & H_D0I3C_I3)) { /* we are not in d0i3, nothing to do */ - dev_dbg(dev->dev, "d0i3 exit not needed\n"); + dev_dbg(&dev->dev, "d0i3 exit not needed\n"); ret = 0; goto off; } reg = mei_me_d0i3_unset(dev); if (!(reg & H_D0I3C_CIP)) { - dev_dbg(dev->dev, "d0i3 exit wait not needed\n"); + dev_dbg(&dev->dev, "d0i3 exit wait not needed\n"); ret = 0; goto off; } @@ -1111,7 +1075,7 @@ off: out: dev->pg_event = MEI_PG_EVENT_IDLE; - dev_dbg(dev->dev, "d0i3 exit ret = %d\n", ret); + dev_dbg(&dev->dev, "d0i3 exit ret = %d\n", ret); return ret; } @@ -1154,7 +1118,7 @@ static void mei_me_d0i3_intr(struct mei_device *dev, u32 intr_source) * force H_RDY because it could be * wiped off during PG */ - dev_dbg(dev->dev, "d0i3 set host ready\n"); + dev_dbg(&dev->dev, "d0i3 set host ready\n"); mei_me_host_set_ready(dev); } } else { @@ -1170,7 +1134,7 @@ static void mei_me_d0i3_intr(struct mei_device *dev, u32 intr_source) * we got here because of HW initiated exit from D0i3. * Start runtime pm resume sequence to exit low power state. */ - dev_dbg(dev->dev, "d0i3 want resume\n"); + dev_dbg(&dev->dev, "d0i3 want resume\n"); mei_hbm_pg_resume(dev); } } @@ -1250,7 +1214,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) } } - pm_runtime_set_active(dev->dev); + pm_runtime_set_active(dev->parent); hcsr = mei_hcsr_read(dev); /* H_RST may be found lit before reset is started, @@ -1259,7 +1223,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) * we need to clean H_RST bit to start a successful reset sequence. */ if ((hcsr & H_RST) == H_RST) { - dev_warn(dev->dev, "H_RST is set = 0x%08X", hcsr); + dev_warn(&dev->dev, "H_RST is set = 0x%08X", hcsr); hcsr &= ~H_RST; mei_hcsr_set(dev, hcsr); hcsr = mei_hcsr_read(dev); @@ -1280,10 +1244,10 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) hcsr = mei_hcsr_read(dev); if ((hcsr & H_RST) == 0) - dev_warn(dev->dev, "H_RST is not set = 0x%08X", hcsr); + dev_warn(&dev->dev, "H_RST is not set = 0x%08X", hcsr); if ((hcsr & H_RDY) == H_RDY) - dev_warn(dev->dev, "H_RDY is not cleared 0x%08X", hcsr); + dev_warn(&dev->dev, "H_RDY is not cleared 0x%08X", hcsr); if (!intr_enable) { mei_me_hw_reset_release(dev); @@ -1313,7 +1277,7 @@ irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id) if (!me_intr_src(hcsr)) return IRQ_NONE; - dev_dbg(dev->dev, "interrupt source 0x%08X\n", me_intr_src(hcsr)); + dev_dbg(&dev->dev, "interrupt source 0x%08X\n", me_intr_src(hcsr)); /* disable interrupts on device */ me_intr_disable(dev, hcsr); @@ -1339,7 +1303,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) u32 hcsr; int rets = 0; - dev_dbg(dev->dev, "function called after ISR to handle the interrupt processing.\n"); + dev_dbg(&dev->dev, "function called after ISR to handle the interrupt processing.\n"); /* initialize our complete list */ mutex_lock(&dev->device_lock); @@ -1351,10 +1315,10 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) /* check if ME wants a reset */ if (!mei_hw_is_ready(dev) && dev->dev_state != MEI_DEV_RESETTING) { if (kind_is_gsc(dev) || kind_is_gscfi(dev)) { - dev_dbg(dev->dev, "FW not ready: resetting: dev_state = %d\n", + dev_dbg(&dev->dev, "FW not ready: resetting: dev_state = %d\n", dev->dev_state); } else { - dev_warn(dev->dev, "FW not ready: resetting: dev_state = %d\n", + dev_warn(&dev->dev, "FW not ready: resetting: dev_state = %d\n", dev->dev_state); } if (dev->dev_state == MEI_DEV_POWERING_DOWN || @@ -1373,18 +1337,29 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) /* check if we need to start the dev */ if (!mei_host_is_ready(dev)) { if (mei_hw_is_ready(dev)) { - dev_dbg(dev->dev, "we need to start the dev.\n"); - dev->recvd_hw_ready = true; - wake_up(&dev->wait_hw_ready); + /* synchronized by dev mutex */ + if (waitqueue_active(&dev->wait_hw_ready)) { + dev_dbg(&dev->dev, "we need to start the dev.\n"); + dev->recvd_hw_ready = true; + wake_up(&dev->wait_hw_ready); + } else if (dev->dev_state != MEI_DEV_UNINITIALIZED && + dev->dev_state != MEI_DEV_POWERING_DOWN && + dev->dev_state != MEI_DEV_POWER_DOWN) { + dev_dbg(&dev->dev, "Force link reset.\n"); + schedule_work(&dev->reset_work); + } else { + dev_dbg(&dev->dev, "Ignore this interrupt in state = %d\n", + dev->dev_state); + } } else { - dev_dbg(dev->dev, "Spurious Interrupt\n"); + dev_dbg(&dev->dev, "Spurious Interrupt\n"); } goto end; } /* check slots available for reading */ slots = mei_count_full_read_slots(dev); while (slots > 0) { - dev_dbg(dev->dev, "slots to read = %08x\n", slots); + dev_dbg(&dev->dev, "slots to read = %08x\n", slots); rets = mei_irq_read_handler(dev, &cmpl_list, &slots); /* There is a race between ME write and interrupt delivery: * Not all data is always available immediately after the @@ -1394,7 +1369,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) break; if (rets) { - dev_err(dev->dev, "mei_irq_read_handler ret = %d, state = %d.\n", + dev_err(&dev->dev, "mei_irq_read_handler ret = %d, state = %d.\n", rets, dev->dev_state); if (dev->dev_state != MEI_DEV_RESETTING && dev->dev_state != MEI_DEV_DISABLED && @@ -1421,7 +1396,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) mei_irq_compl_handler(dev, &cmpl_list); end: - dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets); + dev_dbg(&dev->dev, "interrupt thread end ret = %d\n", rets); mei_me_intr_enable(dev); mutex_unlock(&dev->device_lock); return IRQ_HANDLED; @@ -1453,7 +1428,7 @@ int mei_me_polling_thread(void *_dev) irqreturn_t irq_ret; long polling_timeout = MEI_POLLING_TIMEOUT_ACTIVE; - dev_dbg(dev->dev, "kernel thread is running\n"); + dev_dbg(&dev->dev, "kernel thread is running\n"); while (!kthread_should_stop()) { struct mei_me_hw *hw = to_me_hw(dev); u32 hcsr; @@ -1470,7 +1445,7 @@ int mei_me_polling_thread(void *_dev) 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); + dev_err(&dev->dev, "irq_ret %d\n", irq_ret); } else { /* * Increase timeout by MEI_POLLING_TIMEOUT_ACTIVE @@ -1804,7 +1779,7 @@ struct mei_device *mei_me_dev_init(struct device *parent, struct mei_me_hw *hw; int i; - dev = devm_kzalloc(parent, sizeof(*dev) + sizeof(*hw), GFP_KERNEL); + dev = kzalloc(sizeof(*dev) + sizeof(*hw), GFP_KERNEL); if (!dev) return NULL; diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index e9476f9ae25d..e4688c391027 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -160,7 +160,7 @@ static bool mei_txe_aliveness_set(struct mei_device *dev, u32 req) struct mei_txe_hw *hw = to_txe_hw(dev); bool do_req = hw->aliveness != req; - dev_dbg(dev->dev, "Aliveness current=%d request=%d\n", + dev_dbg(&dev->dev, "Aliveness current=%d request=%d\n", hw->aliveness, req); if (do_req) { dev->pg_event = MEI_PG_EVENT_WAIT; @@ -227,7 +227,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) hw->aliveness = mei_txe_aliveness_get(dev); if (hw->aliveness == expected) { dev->pg_event = MEI_PG_EVENT_IDLE; - dev_dbg(dev->dev, "aliveness settled after %lld usecs\n", + dev_dbg(&dev->dev, "aliveness settled after %lld usecs\n", ktime_to_us(ktime_sub(ktime_get(), start))); return 0; } @@ -235,7 +235,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) } while (ktime_before(ktime_get(), stop)); dev->pg_event = MEI_PG_EVENT_IDLE; - dev_err(dev->dev, "aliveness timed out\n"); + dev_err(&dev->dev, "aliveness timed out\n"); return -ETIME; } @@ -270,10 +270,10 @@ static int mei_txe_aliveness_wait(struct mei_device *dev, u32 expected) ret = hw->aliveness == expected ? 0 : -ETIME; if (ret) - dev_warn(dev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n", + dev_warn(&dev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n", err, hw->aliveness, dev->pg_event); else - dev_dbg(dev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n", + dev_dbg(&dev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n", jiffies_to_msecs(timeout - err), hw->aliveness, dev->pg_event); @@ -438,7 +438,7 @@ static void mei_txe_intr_enable(struct mei_device *dev) */ static void mei_txe_synchronize_irq(struct mei_device *dev) { - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); synchronize_irq(pdev->irq); } @@ -464,7 +464,7 @@ static bool mei_txe_pending_interrupts(struct mei_device *dev) TXE_INTR_OUT_DB)); if (ret) { - dev_dbg(dev->dev, + dev_dbg(&dev->dev, "Pending Interrupts InReady=%01d Readiness=%01d, Aliveness=%01d, OutDoor=%01d\n", !!(hw->intr_cause & TXE_INTR_IN_READY), !!(hw->intr_cause & TXE_INTR_READINESS), @@ -612,7 +612,7 @@ static int mei_txe_readiness_wait(struct mei_device *dev) msecs_to_jiffies(SEC_RESET_WAIT_TIMEOUT)); mutex_lock(&dev->device_lock); if (!dev->recvd_hw_ready) { - dev_err(dev->dev, "wait for readiness failed\n"); + dev_err(&dev->dev, "wait for readiness failed\n"); return -ETIME; } @@ -638,7 +638,7 @@ static int mei_txe_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status) { const struct mei_fw_status *fw_src = &mei_txe_fw_sts; - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); int ret; int i; @@ -649,7 +649,7 @@ static int mei_txe_fw_status(struct mei_device *dev, for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) { ret = pci_read_config_dword(pdev, fw_src->status[i], &fw_status->status[i]); - trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HSF_X", + trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HSF_X", fw_src->status[i], fw_status->status[i]); if (ret) @@ -677,7 +677,7 @@ static int mei_txe_hw_config(struct mei_device *dev) hw->aliveness = mei_txe_aliveness_get(dev); hw->readiness = mei_txe_readiness_get(dev); - dev_dbg(dev->dev, "aliveness_resp = 0x%08x, readiness = 0x%08x.\n", + dev_dbg(&dev->dev, "aliveness_resp = 0x%08x, readiness = 0x%08x.\n", hw->aliveness, hw->readiness); return 0; @@ -708,7 +708,7 @@ static int mei_txe_write(struct mei_device *dev, if (WARN_ON(!hdr || !data || hdr_len & 0x3)) return -EINVAL; - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); + dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); dw_cnt = mei_data2slots(hdr_len + data_len); if (dw_cnt > slots) @@ -724,7 +724,7 @@ static int mei_txe_write(struct mei_device *dev, char fw_sts_str[MEI_FW_STATUS_STR_SZ]; mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ); - dev_err(dev->dev, "Input is not ready %s\n", fw_sts_str); + dev_err(&dev->dev, "Input is not ready %s\n", fw_sts_str); return -EAGAIN; } @@ -828,13 +828,13 @@ static int mei_txe_read(struct mei_device *dev, reg_buf = (u32 *)buf; rem = len & 0x3; - dev_dbg(dev->dev, "buffer-length = %lu buf[0]0x%08X\n", + dev_dbg(&dev->dev, "buffer-length = %lu buf[0]0x%08X\n", len, mei_txe_out_data_read(dev, 0)); for (i = 0; i < len / MEI_SLOT_SIZE; i++) { /* skip header: index starts from 1 */ reg = mei_txe_out_data_read(dev, i + 1); - dev_dbg(dev->dev, "buf[%d] = 0x%08X\n", i, reg); + dev_dbg(&dev->dev, "buf[%d] = 0x%08X\n", i, reg); *reg_buf++ = reg; } @@ -879,7 +879,7 @@ static int mei_txe_hw_reset(struct mei_device *dev, bool intr_enable) */ if (aliveness_req != hw->aliveness) if (mei_txe_aliveness_poll(dev, aliveness_req) < 0) { - dev_err(dev->dev, "wait for aliveness settle failed ... bailing out\n"); + dev_err(&dev->dev, "wait for aliveness settle failed ... bailing out\n"); return -EIO; } @@ -889,7 +889,7 @@ static int mei_txe_hw_reset(struct mei_device *dev, bool intr_enable) if (aliveness_req) { mei_txe_aliveness_set(dev, 0); if (mei_txe_aliveness_poll(dev, 0) < 0) { - dev_err(dev->dev, "wait for aliveness failed ... bailing out\n"); + dev_err(&dev->dev, "wait for aliveness failed ... bailing out\n"); return -EIO; } } @@ -921,7 +921,7 @@ static int mei_txe_hw_start(struct mei_device *dev) ret = mei_txe_readiness_wait(dev); if (ret < 0) { - dev_err(dev->dev, "waiting for readiness failed\n"); + dev_err(&dev->dev, "waiting for readiness failed\n"); return ret; } @@ -937,11 +937,11 @@ static int mei_txe_hw_start(struct mei_device *dev) ret = mei_txe_aliveness_set_sync(dev, 1); if (ret < 0) { - dev_err(dev->dev, "wait for aliveness failed ... bailing out\n"); + dev_err(&dev->dev, "wait for aliveness failed ... bailing out\n"); return ret; } - pm_runtime_set_active(dev->dev); + pm_runtime_set_active(dev->parent); /* enable input ready interrupts: * SEC_IPC_HOST_INT_MASK.IPC_INPUT_READY_INT_MASK @@ -1049,7 +1049,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) s32 slots; int rets = 0; - dev_dbg(dev->dev, "irq thread: Interrupt Registers HHISR|HISR|SEC=%02X|%04X|%02X\n", + dev_dbg(&dev->dev, "irq thread: Interrupt Registers HHISR|HISR|SEC=%02X|%04X|%02X\n", mei_txe_br_reg_read(hw, HHISR_REG), mei_txe_br_reg_read(hw, HISR_REG), mei_txe_sec_reg_read_silent(hw, SEC_IPC_HOST_INT_STATUS_REG)); @@ -1059,7 +1059,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) mutex_lock(&dev->device_lock); INIT_LIST_HEAD(&cmpl_list); - if (pci_dev_msi_enabled(to_pci_dev(dev->dev))) + if (pci_dev_msi_enabled(to_pci_dev(dev->parent))) mei_txe_check_and_ack_intrs(dev, true); /* show irq events */ @@ -1073,17 +1073,17 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) * or TXE driver resetting the HECI interface. */ if (test_and_clear_bit(TXE_INTR_READINESS_BIT, &hw->intr_cause)) { - dev_dbg(dev->dev, "Readiness Interrupt was received...\n"); + dev_dbg(&dev->dev, "Readiness Interrupt was received...\n"); /* Check if SeC is going through reset */ if (mei_txe_readiness_is_sec_rdy(hw->readiness)) { - dev_dbg(dev->dev, "we need to start the dev.\n"); + dev_dbg(&dev->dev, "we need to start the dev.\n"); dev->recvd_hw_ready = true; } else { dev->recvd_hw_ready = false; if (dev->dev_state != MEI_DEV_RESETTING) { - dev_warn(dev->dev, "FW not ready: resetting.\n"); + dev_warn(&dev->dev, "FW not ready: resetting.\n"); schedule_work(&dev->reset_work); goto end; @@ -1100,7 +1100,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) if (test_and_clear_bit(TXE_INTR_ALIVENESS_BIT, &hw->intr_cause)) { /* Clear the interrupt cause */ - dev_dbg(dev->dev, + dev_dbg(&dev->dev, "Aliveness Interrupt: Status: %d\n", hw->aliveness); dev->pg_event = MEI_PG_EVENT_RECEIVED; if (waitqueue_active(&hw->wait_aliveness_resp)) @@ -1118,7 +1118,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) if (rets && (dev->dev_state != MEI_DEV_RESETTING && dev->dev_state != MEI_DEV_POWER_DOWN)) { - dev_err(dev->dev, + dev_err(&dev->dev, "mei_irq_read_handler ret = %d.\n", rets); schedule_work(&dev->reset_work); @@ -1136,7 +1136,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) dev->hbuf_is_ready = mei_hbuf_is_ready(dev); rets = mei_irq_write_handler(dev, &cmpl_list); if (rets && rets != -EMSGSIZE) - dev_err(dev->dev, "mei_irq_write_handler ret = %d.\n", + dev_err(&dev->dev, "mei_irq_write_handler ret = %d.\n", rets); dev->hbuf_is_ready = mei_hbuf_is_ready(dev); } @@ -1144,7 +1144,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) mei_irq_compl_handler(dev, &cmpl_list); end: - dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets); + dev_dbg(&dev->dev, "interrupt thread end ret = %d\n", rets); mutex_unlock(&dev->device_lock); @@ -1197,7 +1197,7 @@ struct mei_device *mei_txe_dev_init(struct pci_dev *pdev) struct mei_device *dev; struct mei_txe_hw *hw; - dev = devm_kzalloc(&pdev->dev, sizeof(*dev) + sizeof(*hw), GFP_KERNEL); + dev = kzalloc(sizeof(*dev) + sizeof(*hw), GFP_KERNEL); if (!dev) return NULL; diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 2e9cf6f4efb6..3771aa09c592 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -27,6 +27,8 @@ #define MKHI_RCV_TIMEOUT 500 /* receive timeout in msec */ #define MKHI_RCV_TIMEOUT_SLOW 10000 /* receive timeout in msec, slow FW */ +#define MEI_LINK_RESET_WAIT_TIMEOUT_MSEC 500 /* Max wait timeout for link reset, in msec */ + /* * FW page size for DMA allocations */ diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 8ef2b1df8ac7..b789c4d9c709 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -89,22 +89,6 @@ void mei_cancel_work(struct mei_device *dev) } EXPORT_SYMBOL_GPL(mei_cancel_work); -static void mei_save_fw_status(struct mei_device *dev) -{ - struct mei_fw_status fw_status; - int ret; - - ret = mei_fw_status(dev, &fw_status); - if (ret) { - dev_err(dev->dev, "failed to read firmware status: %d\n", ret); - return; - } - - dev->saved_dev_state = dev->dev_state; - dev->saved_fw_status_flag = true; - memcpy(&dev->saved_fw_status, &fw_status, sizeof(fw_status)); -} - /** * mei_reset - resets host and fw. * @@ -126,11 +110,10 @@ int mei_reset(struct mei_device *dev) mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ); if (kind_is_gsc(dev) || kind_is_gscfi(dev)) { - dev_dbg(dev->dev, "unexpected reset: dev_state = %s fw status = %s\n", + dev_dbg(&dev->dev, "unexpected reset: dev_state = %s fw status = %s\n", mei_dev_state_str(state), fw_sts_str); - mei_save_fw_status(dev); } else { - dev_warn(dev->dev, "unexpected reset: dev_state = %s fw status = %s\n", + dev_warn(&dev->dev, "unexpected reset: dev_state = %s fw status = %s\n", mei_dev_state_str(state), fw_sts_str); } } @@ -150,7 +133,7 @@ int mei_reset(struct mei_device *dev) dev->reset_count++; if (dev->reset_count > MEI_MAX_CONSEC_RESET) { - dev_err(dev->dev, "reset: reached maximal consecutive resets: disabling the device\n"); + dev_err(&dev->dev, "reset: reached maximal consecutive resets: disabling the device\n"); mei_set_devstate(dev, MEI_DEV_DISABLED); return -ENODEV; } @@ -170,12 +153,12 @@ int mei_reset(struct mei_device *dev) memset(dev->rd_msg_hdr, 0, sizeof(dev->rd_msg_hdr)); if (ret) { - dev_err(dev->dev, "hw_reset failed ret = %d\n", ret); + dev_err(&dev->dev, "hw_reset failed ret = %d\n", ret); return ret; } if (state == MEI_DEV_POWER_DOWN) { - dev_dbg(dev->dev, "powering down: end of reset\n"); + dev_dbg(&dev->dev, "powering down: end of reset\n"); mei_set_devstate(dev, MEI_DEV_DISABLED); return 0; } @@ -185,21 +168,21 @@ int mei_reset(struct mei_device *dev) char fw_sts_str[MEI_FW_STATUS_STR_SZ]; mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ); - dev_err(dev->dev, "hw_start failed ret = %d fw status = %s\n", ret, fw_sts_str); + dev_err(&dev->dev, "hw_start failed ret = %d fw status = %s\n", ret, fw_sts_str); return ret; } if (dev->dev_state != MEI_DEV_RESETTING) { - dev_dbg(dev->dev, "wrong state = %d on link start\n", dev->dev_state); + dev_dbg(&dev->dev, "wrong state = %d on link start\n", dev->dev_state); return 0; } - dev_dbg(dev->dev, "link is established start sending messages.\n"); + dev_dbg(&dev->dev, "link is established start sending messages.\n"); mei_set_devstate(dev, MEI_DEV_INIT_CLIENTS); ret = mei_hbm_start_req(dev); if (ret) { - dev_err(dev->dev, "hbm_start failed ret = %d\n", ret); + dev_err(&dev->dev, "hbm_start failed ret = %d\n", ret); mei_set_devstate(dev, MEI_DEV_RESETTING); return ret; } @@ -228,7 +211,7 @@ int mei_start(struct mei_device *dev) if (ret) goto err; - dev_dbg(dev->dev, "reset in start the mei device.\n"); + dev_dbg(&dev->dev, "reset in start the mei device.\n"); dev->reset_count = 0; do { @@ -236,27 +219,27 @@ int mei_start(struct mei_device *dev) ret = mei_reset(dev); if (ret == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) { - dev_err(dev->dev, "reset failed ret = %d", ret); + dev_err(&dev->dev, "reset failed ret = %d", ret); goto err; } } while (ret); if (mei_hbm_start_wait(dev)) { - dev_err(dev->dev, "HBM haven't started"); + dev_err(&dev->dev, "HBM haven't started"); goto err; } if (!mei_hbm_version_is_supported(dev)) { - dev_dbg(dev->dev, "MEI start failed.\n"); + dev_dbg(&dev->dev, "MEI start failed.\n"); goto err; } - dev_dbg(dev->dev, "link layer has been established.\n"); + dev_dbg(&dev->dev, "link layer has been established.\n"); mutex_unlock(&dev->device_lock); return 0; err: - dev_err(dev->dev, "link layer initialization failed.\n"); + dev_err(&dev->dev, "link layer initialization failed.\n"); mei_set_devstate(dev, MEI_DEV_DISABLED); mutex_unlock(&dev->device_lock); return -ENODEV; @@ -284,7 +267,7 @@ int mei_restart(struct mei_device *dev) mutex_unlock(&dev->device_lock); if (err == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) { - dev_err(dev->dev, "device disabled = %d\n", err); + dev_err(&dev->dev, "device disabled = %d\n", err); return -ENODEV; } @@ -313,7 +296,7 @@ static void mei_reset_work(struct work_struct *work) mutex_unlock(&dev->device_lock); if (dev->dev_state == MEI_DEV_DISABLED) { - dev_err(dev->dev, "device disabled = %d\n", ret); + dev_err(&dev->dev, "device disabled = %d\n", ret); return; } @@ -324,7 +307,7 @@ static void mei_reset_work(struct work_struct *work) void mei_stop(struct mei_device *dev) { - dev_dbg(dev->dev, "stopping the device.\n"); + dev_dbg(&dev->dev, "stopping the device.\n"); mutex_lock(&dev->device_lock); mei_set_devstate(dev, MEI_DEV_POWERING_DOWN); @@ -365,7 +348,7 @@ bool mei_write_is_idle(struct mei_device *dev) list_empty(&dev->write_list) && list_empty(&dev->write_waiting_list)); - dev_dbg(dev->dev, "write pg: is idle[%d] state=%s ctrl=%01d write=%01d wwait=%01d\n", + dev_dbg(&dev->dev, "write pg: is idle[%d] state=%s ctrl=%01d write=%01d wwait=%01d\n", idle, mei_dev_state_str(dev->dev_state), list_empty(&dev->ctrl_wr_list), @@ -380,12 +363,12 @@ EXPORT_SYMBOL_GPL(mei_write_is_idle); * mei_device_init - initialize mei_device structure * * @dev: the mei device - * @device: the device structure + * @parent: the parent device * @slow_fw: configure longer timeouts as FW is slow * @hw_ops: hw operations */ void mei_device_init(struct mei_device *dev, - struct device *device, + struct device *parent, bool slow_fw, const struct mei_hw_ops *hw_ops) { @@ -399,7 +382,8 @@ void mei_device_init(struct mei_device *dev, init_waitqueue_head(&dev->wait_hw_ready); init_waitqueue_head(&dev->wait_pg); init_waitqueue_head(&dev->wait_hbm_start); - dev->dev_state = MEI_DEV_INITIALIZING; + dev->dev_state = MEI_DEV_UNINITIALIZED; + init_waitqueue_head(&dev->wait_dev_state); dev->reset_count = 0; INIT_LIST_HEAD(&dev->write_list); @@ -426,7 +410,7 @@ void mei_device_init(struct mei_device *dev, dev->pg_event = MEI_PG_EVENT_IDLE; dev->ops = hw_ops; - dev->dev = device; + dev->parent = parent; dev->timeouts.hw_ready = mei_secs_to_jiffies(MEI_HW_READY_TIMEOUT); dev->timeouts.connect = MEI_CONNECT_TIMEOUT; @@ -442,6 +426,6 @@ void mei_device_init(struct mei_device *dev, dev->timeouts.hbm = mei_secs_to_jiffies(MEI_HBM_TIMEOUT); dev->timeouts.mkhi_recv = msecs_to_jiffies(MKHI_RCV_TIMEOUT); } + dev->timeouts.link_reset_wait = msecs_to_jiffies(MEI_LINK_RESET_WAIT_TIMEOUT_MSEC); } EXPORT_SYMBOL_GPL(mei_device_init); - diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index d472f6bbe767..3aa66b6b0d36 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -76,7 +76,7 @@ static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr, * that length fits into rd_msg_buf */ mei_read_slots(dev, dev->rd_msg_buf, discard_len); - dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n", + dev_dbg(&dev->dev, "discarding message " MEI_HDR_FMT "\n", MEI_HDR_PRM(hdr)); } @@ -229,8 +229,8 @@ static int mei_cl_irq_read_msg(struct mei_cl *cl, cl_dbg(dev, cl, "completed read length = %zu\n", cb->buf_idx); list_move_tail(&cb->list, cmpl_list); } else { - pm_runtime_mark_last_busy(dev->dev); - pm_request_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_request_autosuspend(dev->parent); } return 0; @@ -310,8 +310,8 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb, return ret; } - pm_runtime_mark_last_busy(dev->dev); - pm_request_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_request_autosuspend(dev->parent); list_move_tail(&cb->list, &cl->rd_pending); @@ -373,21 +373,21 @@ int mei_irq_read_handler(struct mei_device *dev, dev->rd_msg_hdr[0] = mei_read_hdr(dev); dev->rd_msg_hdr_count = 1; (*slots)--; - dev_dbg(dev->dev, "slots =%08x.\n", *slots); + dev_dbg(&dev->dev, "slots =%08x.\n", *slots); ret = hdr_is_valid(dev->rd_msg_hdr[0]); if (ret) { - dev_err(dev->dev, "corrupted message header 0x%08X\n", + dev_err(&dev->dev, "corrupted message header 0x%08X\n", dev->rd_msg_hdr[0]); goto end; } } mei_hdr = (struct mei_msg_hdr *)dev->rd_msg_hdr; - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); + dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); if (mei_slots2data(*slots) < mei_hdr->length) { - dev_err(dev->dev, "less data available than length=%08x.\n", + dev_err(&dev->dev, "less data available than length=%08x.\n", *slots); /* we can't read the message */ ret = -ENODATA; @@ -402,18 +402,18 @@ int mei_irq_read_handler(struct mei_device *dev, dev->rd_msg_hdr[1] = mei_read_hdr(dev); dev->rd_msg_hdr_count++; (*slots)--; - dev_dbg(dev->dev, "extended header is %08x\n", dev->rd_msg_hdr[1]); + dev_dbg(&dev->dev, "extended header is %08x\n", dev->rd_msg_hdr[1]); } meta_hdr = ((struct mei_ext_meta_hdr *)&dev->rd_msg_hdr[1]); if (check_add_overflow((u32)sizeof(*meta_hdr), mei_slots2data(meta_hdr->size), &hdr_size_ext)) { - dev_err(dev->dev, "extended message size too big %d\n", + dev_err(&dev->dev, "extended message size too big %d\n", meta_hdr->size); return -EBADMSG; } if (hdr_size_left < hdr_size_ext) { - dev_err(dev->dev, "corrupted message header len %d\n", + dev_err(&dev->dev, "corrupted message header len %d\n", mei_hdr->length); return -EBADMSG; } @@ -422,7 +422,7 @@ int mei_irq_read_handler(struct mei_device *dev, ext_hdr_end = meta_hdr->size + 2; for (i = dev->rd_msg_hdr_count; i < ext_hdr_end; i++) { dev->rd_msg_hdr[i] = mei_read_hdr(dev); - dev_dbg(dev->dev, "extended header %d is %08x\n", i, + dev_dbg(&dev->dev, "extended header %d is %08x\n", i, dev->rd_msg_hdr[i]); dev->rd_msg_hdr_count++; (*slots)--; @@ -431,7 +431,7 @@ int mei_irq_read_handler(struct mei_device *dev, if (mei_hdr->dma_ring) { if (hdr_size_left != sizeof(dev->rd_msg_hdr[ext_hdr_end])) { - dev_err(dev->dev, "corrupted message header len %d\n", + dev_err(&dev->dev, "corrupted message header len %d\n", mei_hdr->length); return -EBADMSG; } @@ -446,8 +446,7 @@ int mei_irq_read_handler(struct mei_device *dev, if (hdr_is_hbm(mei_hdr)) { ret = mei_hbm_dispatch(dev, mei_hdr); if (ret) { - dev_dbg(dev->dev, "mei_hbm_dispatch failed ret = %d\n", - ret); + dev_dbg(&dev->dev, "mei_hbm_dispatch failed ret = %d\n", ret); goto end; } goto reset_slots; @@ -474,7 +473,7 @@ int mei_irq_read_handler(struct mei_device *dev, ret = 0; goto reset_slots; } - dev_err(dev->dev, "no destination client found 0x%08X\n", dev->rd_msg_hdr[0]); + dev_err(&dev->dev, "no destination client found 0x%08X\n", dev->rd_msg_hdr[0]); ret = -EBADMSG; goto end; @@ -485,7 +484,7 @@ reset_slots: *slots = mei_count_full_read_slots(dev); if (*slots == -EOVERFLOW) { /* overflow - reset */ - dev_err(dev->dev, "resetting due to slots overflow.\n"); + dev_err(&dev->dev, "resetting due to slots overflow.\n"); /* set the event since message has been read */ ret = -ERANGE; goto end; @@ -525,7 +524,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) return -EMSGSIZE; /* complete all waiting for write CB */ - dev_dbg(dev->dev, "complete all waiting for write cb.\n"); + dev_dbg(&dev->dev, "complete all waiting for write cb.\n"); list_for_each_entry_safe(cb, next, &dev->write_waiting_list, list) { cl = cb->cl; @@ -537,7 +536,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) } /* complete control write list CB */ - dev_dbg(dev->dev, "complete control write list cb.\n"); + dev_dbg(&dev->dev, "complete control write list cb.\n"); list_for_each_entry_safe(cb, next, &dev->ctrl_wr_list, list) { cl = cb->cl; switch (cb->fop_type) { @@ -591,7 +590,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) } /* complete write list CB */ - dev_dbg(dev->dev, "complete write list cb.\n"); + dev_dbg(&dev->dev, "complete write list cb.\n"); list_for_each_entry_safe(cb, next, &dev->write_list, list) { cl = cb->cl; ret = mei_cl_irq_write(cl, cb, cmpl_list); @@ -656,7 +655,7 @@ void mei_timer(struct work_struct *work) if (dev->init_clients_timer) { if (--dev->init_clients_timer == 0) { - dev_err(dev->dev, "timer: init clients timeout hbm_state = %d.\n", + dev_err(&dev->dev, "timer: init clients timeout hbm_state = %d.\n", dev->hbm_state); mei_reset(dev); goto out; @@ -672,7 +671,7 @@ void mei_timer(struct work_struct *work) list_for_each_entry(cl, &dev->file_list, link) { if (cl->timer_count) { if (--cl->timer_count == 0) { - dev_err(dev->dev, "timer: connect/disconnect timeout.\n"); + dev_err(&dev->dev, "timer: connect/disconnect timeout.\n"); mei_connect_timeout(cl); goto out; } diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 8a149a15b861..86a73684a373 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -51,12 +51,15 @@ static int mei_open(struct inode *inode, struct file *file) int err; - dev = container_of(inode->i_cdev, struct mei_device, cdev); + dev = idr_find(&mei_idr, iminor(inode)); + if (!dev) + return -ENODEV; + get_device(&dev->dev); mutex_lock(&dev->device_lock); if (dev->dev_state != MEI_DEV_ENABLED) { - dev_dbg(dev->dev, "dev_state != MEI_ENABLED dev_state = %s\n", + dev_dbg(&dev->dev, "dev_state != MEI_ENABLED dev_state = %s\n", mei_dev_state_str(dev->dev_state)); err = -ENODEV; goto err_unlock; @@ -77,6 +80,7 @@ static int mei_open(struct inode *inode, struct file *file) err_unlock: mutex_unlock(&dev->device_lock); + put_device(&dev->dev); return err; } @@ -152,6 +156,7 @@ out: file->private_data = NULL; mutex_unlock(&dev->device_lock); + put_device(&dev->dev); return rets; } @@ -418,6 +423,7 @@ static int mei_ioctl_connect_client(struct file *file, cl->state != MEI_FILE_DISCONNECTED) return -EBUSY; +retry: /* find ME client we're trying to connect to */ me_cl = mei_me_cl_by_uuid(dev, in_client_uuid); if (!me_cl) { @@ -449,6 +455,28 @@ static int mei_ioctl_connect_client(struct file *file, rets = mei_cl_connect(cl, me_cl, file); + if (rets && cl->status == -EFAULT && + (dev->dev_state == MEI_DEV_RESETTING || + dev->dev_state == MEI_DEV_INIT_CLIENTS)) { + /* in link reset, wait for it completion */ + mutex_unlock(&dev->device_lock); + rets = wait_event_interruptible_timeout(dev->wait_dev_state, + dev->dev_state == MEI_DEV_ENABLED, + dev->timeouts.link_reset_wait); + mutex_lock(&dev->device_lock); + if (rets < 0) { + if (signal_pending(current)) + rets = -EINTR; + goto end; + } + if (dev->dev_state != MEI_DEV_ENABLED) { + rets = -ETIME; + goto end; + } + mei_me_cl_put(me_cl); + goto retry; + } + end: mei_me_cl_put(me_cl); return rets; @@ -477,7 +505,7 @@ static int mei_vt_support_check(struct mei_device *dev, const uuid_le *uuid) me_cl = mei_me_cl_by_uuid(dev, uuid); if (!me_cl) { - dev_dbg(dev->dev, "Cannot connect to FW Client UUID = %pUl\n", + dev_dbg(&dev->dev, "Cannot connect to FW Client UUID = %pUl\n", uuid); return -ENOTTY; } @@ -641,7 +669,7 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) struct mei_cl *cl = file->private_data; struct mei_connect_client_data conn; struct mei_connect_client_data_vtag conn_vtag; - const uuid_le *cl_uuid; + uuid_le cl_uuid; struct mei_client *props; u8 vtag; u32 notify_get, notify_req; @@ -669,18 +697,18 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) rets = -EFAULT; goto out; } - cl_uuid = &conn.in_client_uuid; + cl_uuid = conn.in_client_uuid; props = &conn.out_client_properties; vtag = 0; - rets = mei_vt_support_check(dev, cl_uuid); + rets = mei_vt_support_check(dev, &cl_uuid); if (rets == -ENOTTY) goto out; if (!rets) - rets = mei_ioctl_connect_vtag(file, cl_uuid, props, + rets = mei_ioctl_connect_vtag(file, &cl_uuid, props, vtag); else - rets = mei_ioctl_connect_client(file, cl_uuid, props); + rets = mei_ioctl_connect_client(file, &cl_uuid, props); if (rets) goto out; @@ -702,14 +730,14 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) goto out; } - cl_uuid = &conn_vtag.connect.in_client_uuid; + cl_uuid = conn_vtag.connect.in_client_uuid; props = &conn_vtag.out_client_properties; vtag = conn_vtag.connect.vtag; - rets = mei_vt_support_check(dev, cl_uuid); + rets = mei_vt_support_check(dev, &cl_uuid); if (rets == -EOPNOTSUPP) cl_dbg(dev, cl, "FW Client %pUl does not support vtags\n", - cl_uuid); + &cl_uuid); if (rets) goto out; @@ -719,7 +747,7 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) goto out; } - rets = mei_ioctl_connect_vtag(file, cl_uuid, props, vtag); + rets = mei_ioctl_connect_vtag(file, &cl_uuid, props, vtag); if (rets) goto out; @@ -1115,7 +1143,12 @@ void mei_set_devstate(struct mei_device *dev, enum mei_dev_state state) dev->dev_state = state; - clsdev = class_find_device_by_devt(&mei_class, dev->cdev.dev); + wake_up_interruptible_all(&dev->wait_dev_state); + + if (!dev->cdev) + return; + + clsdev = class_find_device_by_devt(&mei_class, dev->cdev->dev); if (clsdev) { sysfs_notify(&clsdev->kobj, NULL, "dev_state"); put_device(clsdev); @@ -1191,7 +1224,7 @@ static int mei_minor_get(struct mei_device *dev) if (ret >= 0) dev->minor = ret; else if (ret == -ENOSPC) - dev_err(dev->dev, "too many mei devices\n"); + dev_err(&dev->dev, "too many mei devices\n"); mutex_unlock(&mei_minor_lock); return ret; @@ -1200,56 +1233,81 @@ static int mei_minor_get(struct mei_device *dev) /** * mei_minor_free - mark device minor number as free * - * @dev: device pointer + * @minor: minor number to free */ -static void mei_minor_free(struct mei_device *dev) +static void mei_minor_free(int minor) { mutex_lock(&mei_minor_lock); - idr_remove(&mei_idr, dev->minor); + idr_remove(&mei_idr, minor); mutex_unlock(&mei_minor_lock); } +static void mei_device_release(struct device *dev) +{ + kfree(dev_get_drvdata(dev)); +} + int mei_register(struct mei_device *dev, struct device *parent) { - struct device *clsdev; /* class device */ int ret, devno; + int minor; ret = mei_minor_get(dev); if (ret < 0) return ret; + minor = dev->minor; + /* Fill in the data structures */ devno = MKDEV(MAJOR(mei_devt), dev->minor); - cdev_init(&dev->cdev, &mei_fops); - dev->cdev.owner = parent->driver->owner; + + device_initialize(&dev->dev); + dev->dev.devt = devno; + dev->dev.class = &mei_class; + dev->dev.parent = parent; + dev->dev.groups = mei_groups; + dev->dev.release = mei_device_release; + dev_set_drvdata(&dev->dev, dev); + + dev->cdev = cdev_alloc(); + if (!dev->cdev) { + ret = -ENOMEM; + goto err; + } + dev->cdev->ops = &mei_fops; + dev->cdev->owner = parent->driver->owner; + cdev_set_parent(dev->cdev, &dev->dev.kobj); /* Add the device */ - ret = cdev_add(&dev->cdev, devno, 1); + ret = cdev_add(dev->cdev, devno, 1); if (ret) { - dev_err(parent, "unable to add device %d:%d\n", + dev_err(parent, "unable to add cdev for device %d:%d\n", MAJOR(mei_devt), dev->minor); - goto err_dev_add; + goto err_del_cdev; } - clsdev = device_create_with_groups(&mei_class, parent, devno, - dev, mei_groups, - "mei%d", dev->minor); + ret = dev_set_name(&dev->dev, "mei%d", dev->minor); + if (ret) { + dev_err(parent, "unable to set name to device %d:%d ret = %d\n", + MAJOR(mei_devt), dev->minor, ret); + goto err_del_cdev; + } - if (IS_ERR(clsdev)) { - dev_err(parent, "unable to create device %d:%d\n", - MAJOR(mei_devt), dev->minor); - ret = PTR_ERR(clsdev); - goto err_dev_create; + ret = device_add(&dev->dev); + if (ret) { + dev_err(parent, "unable to add device %d:%d ret = %d\n", + MAJOR(mei_devt), dev->minor, ret); + goto err_del_cdev; } - mei_dbgfs_register(dev, dev_name(clsdev)); + mei_dbgfs_register(dev, dev_name(&dev->dev)); return 0; -err_dev_create: - cdev_del(&dev->cdev); -err_dev_add: - mei_minor_free(dev); +err_del_cdev: + cdev_del(dev->cdev); +err: + mei_minor_free(minor); return ret; } EXPORT_SYMBOL_GPL(mei_register); @@ -1257,15 +1315,16 @@ EXPORT_SYMBOL_GPL(mei_register); void mei_deregister(struct mei_device *dev) { int devno; + int minor = dev->minor; - devno = dev->cdev.dev; - cdev_del(&dev->cdev); + devno = dev->cdev->dev; + cdev_del(dev->cdev); mei_dbgfs_deregister(dev); device_destroy(&mei_class, devno); - mei_minor_free(dev); + mei_minor_free(minor); } EXPORT_SYMBOL_GPL(mei_deregister); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 37d7fb15cad7..0bf8d552c3ea 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -57,7 +57,8 @@ enum file_state { /* MEI device states */ enum mei_dev_state { - MEI_DEV_INITIALIZING = 0, + MEI_DEV_UNINITIALIZED = 0, + MEI_DEV_INITIALIZING, MEI_DEV_INIT_CLIENTS, MEI_DEV_ENABLED, MEI_DEV_RESETTING, @@ -465,13 +466,15 @@ struct mei_dev_timeouts { unsigned int d0i3; /* D0i3 set/unset max response time, in jiffies */ unsigned long hbm; /* HBM operation timeout, in jiffies */ unsigned long mkhi_recv; /* receive timeout, in jiffies */ + unsigned long link_reset_wait; /* link reset wait timeout, in jiffies */ }; /** * struct mei_device - MEI private device struct * - * @dev : device on a bus - * @cdev : character device + * @parent : device on a bus + * @dev : device object + * @cdev : character device pointer * @minor : minor number allocated for device * * @write_list : write pending list @@ -494,6 +497,7 @@ struct mei_dev_timeouts { * * @reset_count : number of consecutive resets * @dev_state : device state + * @wait_dev_state: wait queue for device state change * @hbm_state : state of host bus message protocol * @pxp_mode : PXP device mode * @init_clients_timer : HBM init handshake timeout @@ -547,17 +551,15 @@ struct mei_dev_timeouts { * * @dbgfs_dir : debugfs mei root directory * - * @saved_fw_status : saved firmware status - * @saved_dev_state : saved device state - * @saved_fw_status_flag : flag indicating that firmware status was saved * @gsc_reset_to_pxp : state of reset to the PXP mode * * @ops: : hw specific operations * @hw : hw specific data */ struct mei_device { - struct device *dev; - struct cdev cdev; + struct device *parent; + struct device dev; + struct cdev *cdev; int minor; struct list_head write_list; @@ -585,6 +587,7 @@ struct mei_device { */ unsigned long reset_count; enum mei_dev_state dev_state; + wait_queue_head_t wait_dev_state; enum mei_hbm_state hbm_state; enum mei_dev_pxp_mode pxp_mode; u16 init_clients_timer; @@ -648,9 +651,6 @@ struct mei_device { struct dentry *dbgfs_dir; #endif /* CONFIG_DEBUG_FS */ - struct mei_fw_status saved_fw_status; - enum mei_dev_state saved_dev_state; - bool saved_fw_status_flag; enum mei_dev_reset_to_pxp gsc_reset_to_pxp; const struct mei_hw_ops *ops; @@ -703,7 +703,7 @@ static inline u32 mei_slots2data(int slots) * mei init function prototypes */ void mei_device_init(struct mei_device *dev, - struct device *device, + struct device *parent, bool slow_fw, const struct mei_hw_ops *hw_ops); int mei_reset(struct mei_device *dev); diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 3f9c60b579ae..b108a7c22388 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -143,7 +143,7 @@ static inline void mei_me_unset_pm_domain(struct mei_device *dev) {} static int mei_me_read_fws(const struct mei_device *dev, int where, u32 *val) { - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); return pci_read_config_dword(pdev, where, val); } @@ -238,19 +238,19 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto end; } + err = mei_register(dev, &pdev->dev); + if (err) + goto release_irq; + if (mei_start(dev)) { dev_err(&pdev->dev, "init hw failure.\n"); err = -ENODEV; - goto release_irq; + goto deregister; } pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_ME_RPM_TIMEOUT); pm_runtime_use_autosuspend(&pdev->dev); - err = mei_register(dev, &pdev->dev); - if (err) - goto stop; - pci_set_drvdata(pdev, dev); /* @@ -280,8 +280,8 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) return 0; -stop: - mei_stop(dev); +deregister: + mei_deregister(dev); release_irq: mei_cancel_work(dev); mei_disable_interrupts(dev); @@ -475,7 +475,7 @@ static int mei_me_pm_runtime_resume(struct device *device) */ static inline void mei_me_set_pm_domain(struct mei_device *dev) { - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); if (pdev->dev.bus && pdev->dev.bus->pm) { dev->pg_domain.ops = *pdev->dev.bus->pm; @@ -496,7 +496,7 @@ static inline void mei_me_set_pm_domain(struct mei_device *dev) static inline void mei_me_unset_pm_domain(struct mei_device *dev) { /* stop using pm callbacks if any */ - dev_pm_domain_set(dev->dev, NULL); + dev_pm_domain_set(dev->parent, NULL); } static const struct dev_pm_ops mei_me_pm_ops = { diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index 2a584104ba38..c9eb5c5393e4 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -321,7 +321,7 @@ static int mei_txe_pm_runtime_resume(struct device *device) */ static inline void mei_txe_set_pm_domain(struct mei_device *dev) { - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); if (pdev->dev.bus && pdev->dev.bus->pm) { dev->pg_domain.ops = *pdev->dev.bus->pm; @@ -342,7 +342,7 @@ static inline void mei_txe_set_pm_domain(struct mei_device *dev) static inline void mei_txe_unset_pm_domain(struct mei_device *dev) { /* stop using pm callbacks if any */ - dev_pm_domain_set(dev->dev, NULL); + dev_pm_domain_set(dev->parent, NULL); } static const struct dev_pm_ops mei_txe_pm_ops = { diff --git a/drivers/misc/mei/platform-vsc.c b/drivers/misc/mei/platform-vsc.c index b2b5a20ae3fa..288e7b72e942 100644 --- a/drivers/misc/mei/platform-vsc.c +++ b/drivers/misc/mei/platform-vsc.c @@ -152,7 +152,7 @@ static int mei_vsc_hw_start(struct mei_device *mei_dev) MEI_VSC_POLL_TIMEOUT_US, true, hw, &buf, sizeof(buf)); if (ret) { - dev_err(mei_dev->dev, "wait fw ready failed: %d\n", ret); + dev_err(&mei_dev->dev, "wait fw ready failed: %d\n", ret); return ret; } @@ -259,7 +259,7 @@ static int mei_vsc_hw_reset(struct mei_device *mei_dev, bool intr_enable) if (!intr_enable) return 0; - return vsc_tp_init(hw->tp, mei_dev->dev); + return vsc_tp_init(hw->tp, mei_dev->parent); } static const struct mei_hw_ops mei_vsc_hw_ops = { @@ -325,7 +325,7 @@ static void mei_vsc_event_cb(void *context) mei_dev->hbuf_is_ready = mei_hbuf_is_ready(mei_dev); ret = mei_irq_write_handler(mei_dev, &cmpl_list); if (ret) - dev_err(mei_dev->dev, "dispatch write request failed: %d\n", ret); + dev_err(&mei_dev->dev, "dispatch write request failed: %d\n", ret); mei_dev->hbuf_is_ready = mei_hbuf_is_ready(mei_dev); mei_irq_compl_handler(mei_dev, &cmpl_list); @@ -343,12 +343,12 @@ static int mei_vsc_probe(struct platform_device *pdev) if (!tp) return dev_err_probe(dev, -ENODEV, "no platform data\n"); - mei_dev = devm_kzalloc(dev, size_add(sizeof(*mei_dev), sizeof(*hw)), - GFP_KERNEL); + mei_dev = kzalloc(size_add(sizeof(*mei_dev), sizeof(*hw)), GFP_KERNEL); if (!mei_dev) return -ENOMEM; mei_device_init(mei_dev, dev, false, &mei_vsc_hw_ops); + mei_dev->fw_f_fw_ver_supported = 0; mei_dev->kind = "ivsc"; @@ -360,22 +360,22 @@ static int mei_vsc_probe(struct platform_device *pdev) vsc_tp_register_event_cb(tp, mei_vsc_event_cb, mei_dev); + ret = mei_register(mei_dev, dev); + if (ret) + goto err_dereg; + ret = mei_start(mei_dev); if (ret) { dev_err_probe(dev, ret, "init hw failed\n"); goto err_cancel; } - ret = mei_register(mei_dev, dev); - if (ret) - goto err_stop; - - pm_runtime_enable(mei_dev->dev); + pm_runtime_enable(mei_dev->parent); return 0; -err_stop: - mei_stop(mei_dev); +err_dereg: + mei_deregister(mei_dev); err_cancel: mei_cancel_work(mei_dev); @@ -392,7 +392,7 @@ static void mei_vsc_remove(struct platform_device *pdev) struct mei_device *mei_dev = platform_get_drvdata(pdev); struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); - pm_runtime_disable(mei_dev->dev); + pm_runtime_disable(mei_dev->parent); mei_stop(mei_dev); diff --git a/drivers/mtd/chips/cfi_probe.c b/drivers/mtd/chips/cfi_probe.c index a04b6174181c..e254f9cd2796 100644 --- a/drivers/mtd/chips/cfi_probe.c +++ b/drivers/mtd/chips/cfi_probe.c @@ -208,7 +208,7 @@ static int __xipram cfi_chip_setup(struct map_info *map, if (!num_erase_regions) return 0; - cfi->cfiq = kmalloc(sizeof(struct cfi_ident) + num_erase_regions * 4, GFP_KERNEL); + cfi->cfiq = kmalloc(struct_size(cfi->cfiq, EraseRegionInfo, num_erase_regions), GFP_KERNEL); if (!cfi->cfiq) return 0; diff --git a/drivers/mtd/chips/jedec_probe.c b/drivers/mtd/chips/jedec_probe.c index 23c32fe584b7..b285962eee2a 100644 --- a/drivers/mtd/chips/jedec_probe.c +++ b/drivers/mtd/chips/jedec_probe.c @@ -1953,7 +1953,7 @@ static void jedec_reset(u32 base, struct map_info *map, struct cfi_private *cfi) * as they will ignore the writes and don't care what address * the F0 is written to */ if (cfi->addr_unlock1) { - pr_debug( "reset unlock called %x %x \n", + pr_debug("reset unlock called %x %x\n", cfi->addr_unlock1,cfi->addr_unlock2); cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL); cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL); @@ -1985,7 +1985,7 @@ static int cfi_jedec_setup(struct map_info *map, struct cfi_private *cfi, int in num_erase_regions = jedec_table[index].nr_regions; - cfi->cfiq = kmalloc(sizeof(struct cfi_ident) + num_erase_regions * 4, GFP_KERNEL); + cfi->cfiq = kmalloc(struct_size(cfi->cfiq, EraseRegionInfo, num_erase_regions), GFP_KERNEL); if (!cfi->cfiq) { //xx printk(KERN_WARNING "%s: kmalloc failed for CFI ident structure\n", map->name); return 0; diff --git a/drivers/mtd/ftl.c b/drivers/mtd/ftl.c index f2bd1984609c..59a901549257 100644 --- a/drivers/mtd/ftl.c +++ b/drivers/mtd/ftl.c @@ -263,7 +263,7 @@ static int build_maps(partition_t *part) /* Set up virtual page map */ blocks = le32_to_cpu(header.FormattedSize) >> header.BlockSize; - part->VirtualBlockMap = vmalloc(array_size(blocks, sizeof(uint32_t))); + part->VirtualBlockMap = vmalloc_array(blocks, sizeof(uint32_t)); if (!part->VirtualBlockMap) goto out_XferInfo; diff --git a/drivers/mtd/hyperbus/hbmc-am654.c b/drivers/mtd/hyperbus/hbmc-am654.c index 82a1e7b7e4d8..9d31464046b2 100644 --- a/drivers/mtd/hyperbus/hbmc-am654.c +++ b/drivers/mtd/hyperbus/hbmc-am654.c @@ -272,5 +272,4 @@ module_platform_driver(am654_hbmc_platform_driver); MODULE_DESCRIPTION("HBMC driver for AM654 SoC"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:hbmc-am654"); MODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>"); diff --git a/drivers/mtd/lpddr/lpddr_cmds.c b/drivers/mtd/lpddr/lpddr_cmds.c index 14e36ae71958..290fd0119e98 100644 --- a/drivers/mtd/lpddr/lpddr_cmds.c +++ b/drivers/mtd/lpddr/lpddr_cmds.c @@ -142,7 +142,7 @@ static int wait_for_ready(struct map_info *map, struct flchip *chip, if (dsr & DSR_READY_STATUS) break; if (!timeo) { - printk(KERN_ERR "%s: Flash timeout error state %d \n", + printk(KERN_ERR "%s: Flash timeout error state %d\n", map->name, chip_state); ret = -ETIME; break; @@ -186,7 +186,7 @@ static int wait_for_ready(struct map_info *map, struct flchip *chip, if (dsr & DSR_ERR) { /* Clear DSR*/ map_write(map, CMD(~(DSR_ERR)), map->pfow_base + PFOW_DSR); - printk(KERN_WARNING"%s: Bad status on wait: 0x%x \n", + printk(KERN_WARNING"%s: Bad status on wait: 0x%x\n", map->name, dsr); print_drs_error(dsr); ret = -EIO; @@ -321,7 +321,7 @@ static int chip_ready(struct map_info *map, struct flchip *chip, int mode) /* Resume and pretend we weren't here. */ put_chip(map, chip); printk(KERN_ERR "%s: suspend operation failed." - "State may be wrong \n", map->name); + "State may be wrong\n", map->name); return -EIO; } chip->erase_suspended = 1; @@ -468,7 +468,7 @@ static int do_write_buffer(struct map_info *map, struct flchip *chip, chip->state = FL_WRITING; ret = wait_for_ready(map, chip, (1<<lpddr->qinfo->ProgBufferTime)); if (ret) { - printk(KERN_WARNING"%s Buffer program error: %d at %lx; \n", + printk(KERN_WARNING"%s Buffer program error: %d at %lx\n", map->name, ret, adr); goto out; } @@ -736,7 +736,7 @@ static int do_xxlock(struct mtd_info *mtd, loff_t adr, uint32_t len, int thunk) ret = wait_for_ready(map, chip, 1); if (ret) { - printk(KERN_ERR "%s: block unlock error status %d \n", + printk(KERN_ERR "%s: block unlock error status %d\n", map->name, ret); goto out; } diff --git a/drivers/mtd/lpddr/qinfo_probe.c b/drivers/mtd/lpddr/qinfo_probe.c index 137ae5f0a19b..42281e460c62 100644 --- a/drivers/mtd/lpddr/qinfo_probe.c +++ b/drivers/mtd/lpddr/qinfo_probe.c @@ -55,7 +55,7 @@ static long lpddr_get_qinforec_pos(struct map_info *map, char *id_str) return minor | (major << bankwidth); } } - printk(KERN_ERR"%s qinfo id string is wrong! \n", map->name); + printk(KERN_ERR"%s qinfo id string is wrong!\n", map->name); BUG(); return -1; } @@ -112,7 +112,7 @@ static int lpddr_pfow_present(struct map_info *map, struct lpddr_private *lpddr) return 1; /* "PFOW" is found */ out: - printk(KERN_WARNING"%s: PFOW string at 0x%lx is not found \n", + printk(KERN_WARNING"%s: PFOW string at 0x%lx is not found\n", map->name, map->pfow_base); return 0; } diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 5ba9a741f5ac..64808493b4f5 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -384,14 +384,64 @@ EXPORT_SYMBOL_GPL(mtd_check_expert_analysis_mode); static struct dentry *dfs_dir_mtd; +static int mtd_ooblayout_show(struct seq_file *s, void *p, + int (*iter)(struct mtd_info *, int section, + struct mtd_oob_region *region)) +{ + struct mtd_info *mtd = s->private; + int section; + + for (section = 0;; section++) { + struct mtd_oob_region region; + int err; + + err = iter(mtd, section, ®ion); + if (err) { + if (err == -ERANGE) + break; + + return err; + } + + seq_printf(s, "%-3d %4u %4u\n", section, region.offset, + region.length); + } + + return 0; +} + +static int mtd_ooblayout_ecc_show(struct seq_file *s, void *p) +{ + return mtd_ooblayout_show(s, p, mtd_ooblayout_ecc); +} +DEFINE_SHOW_ATTRIBUTE(mtd_ooblayout_ecc); + +static int mtd_ooblayout_free_show(struct seq_file *s, void *p) +{ + return mtd_ooblayout_show(s, p, mtd_ooblayout_free); +} +DEFINE_SHOW_ATTRIBUTE(mtd_ooblayout_free); + static void mtd_debugfs_populate(struct mtd_info *mtd) { struct device *dev = &mtd->dev; + struct mtd_oob_region region; if (IS_ERR_OR_NULL(dfs_dir_mtd)) return; mtd->dbg.dfs_dir = debugfs_create_dir(dev_name(dev), dfs_dir_mtd); + if (IS_ERR_OR_NULL(mtd->dbg.dfs_dir)) + return; + + /* Create ooblayout files only if at least one region is present. */ + if (mtd_ooblayout_ecc(mtd, 0, ®ion) == 0) + debugfs_create_file("ooblayout_ecc", 0444, mtd->dbg.dfs_dir, + mtd, &mtd_ooblayout_ecc_fops); + + if (mtd_ooblayout_free(mtd, 0, ®ion) == 0) + debugfs_create_file("ooblayout_free", 0444, mtd->dbg.dfs_dir, + mtd, &mtd_ooblayout_free_fops); } #ifndef CONFIG_MMU @@ -2339,6 +2389,7 @@ EXPORT_SYMBOL_GPL(mtd_block_isbad); int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs) { struct mtd_info *master = mtd_get_master(mtd); + loff_t moffs; int ret; if (!master->_block_markbad) @@ -2351,7 +2402,15 @@ int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs) if (mtd->flags & MTD_SLC_ON_MLC_EMULATION) ofs = (loff_t)mtd_div_by_eb(ofs, mtd) * master->erasesize; - ret = master->_block_markbad(master, mtd_get_master_ofs(mtd, ofs)); + moffs = mtd_get_master_ofs(mtd, ofs); + + if (master->_block_isbad) { + ret = master->_block_isbad(master, moffs); + if (ret > 0) + return 0; + } + + ret = master->_block_markbad(master, moffs); if (ret) return ret; diff --git a/drivers/mtd/mtdoops.c b/drivers/mtd/mtdoops.c index 7bf3777e1f13..b88083751a0c 100644 --- a/drivers/mtd/mtdoops.c +++ b/drivers/mtd/mtdoops.c @@ -356,9 +356,8 @@ static void mtdoops_notify_add(struct mtd_info *mtd) /* oops_page_used is a bit field */ cxt->oops_page_used = - vmalloc(array_size(sizeof(unsigned long), - DIV_ROUND_UP(mtdoops_pages, - BITS_PER_LONG))); + vmalloc_array(DIV_ROUND_UP(mtdoops_pages, BITS_PER_LONG), + sizeof(unsigned long)); if (!cxt->oops_page_used) { pr_err("could not allocate page array\n"); return; diff --git a/drivers/mtd/mtdswap.c b/drivers/mtd/mtdswap.c index 680366616da2..d8f2e5be2d31 100644 --- a/drivers/mtd/mtdswap.c +++ b/drivers/mtd/mtdswap.c @@ -1285,11 +1285,11 @@ static int mtdswap_init(struct mtdswap_dev *d, unsigned int eblocks, for (i = 0; i < MTDSWAP_TREE_CNT; i++) d->trees[i].root = RB_ROOT; - d->page_data = vmalloc(array_size(pages, sizeof(int))); + d->page_data = vmalloc_array(pages, sizeof(int)); if (!d->page_data) goto page_data_fail; - d->revmap = vmalloc(array_size(blocks, sizeof(int))); + d->revmap = vmalloc_array(blocks, sizeof(int)); if (!d->revmap) goto revmap_fail; diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 5b0c2c95f10c..4a17271076bc 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -61,6 +61,14 @@ config MTD_NAND_ECC_MEDIATEK help This enables support for the hardware ECC engine from Mediatek. +config MTD_NAND_ECC_REALTEK + tristate "Realtek RTL93xx hardware ECC engine" + depends on HAS_IOMEM + depends on MACH_REALTEK_RTL || COMPILE_TEST + select MTD_NAND_ECC + help + This enables support for the hardware ECC engine from Realtek. + endmenu endmenu diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 44913ff1bf12..2e0e56267718 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -3,6 +3,7 @@ nandcore-objs := core.o bbt.o obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o +obj-$(CONFIG_MTD_NAND_ECC_REALTEK) += ecc-realtek.o obj-$(CONFIG_SPI_QPIC_SNAND) += qpic_common.o obj-$(CONFIG_MTD_NAND_QCOM) += qpic_common.o obj-y += onenand/ diff --git a/drivers/mtd/nand/core.c b/drivers/mtd/nand/core.c index 7737b1a4a177..3e76d127715f 100644 --- a/drivers/mtd/nand/core.c +++ b/drivers/mtd/nand/core.c @@ -13,6 +13,137 @@ #include <linux/mtd/nand.h> /** + * nand_check_erased_buf - check if a buffer contains (almost) only 0xff data + * @buf: buffer to test + * @len: buffer length + * @bitflips_threshold: maximum number of bitflips + * + * Check if a buffer contains only 0xff, which means the underlying region + * has been erased and is ready to be programmed. + * The bitflips_threshold specify the maximum number of bitflips before + * considering the region is not erased. + * Note: The logic of this function has been extracted from the memweight + * implementation, except that nand_check_erased_buf function exit before + * testing the whole buffer if the number of bitflips exceed the + * bitflips_threshold value. + * + * Returns a positive number of bitflips less than or equal to + * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the + * threshold. + */ +static int nand_check_erased_buf(void *buf, int len, int bitflips_threshold) +{ + const unsigned char *bitmap = buf; + int bitflips = 0; + int weight; + + for (; len && ((uintptr_t)bitmap) % sizeof(long); + len--, bitmap++) { + weight = hweight8(*bitmap); + bitflips += BITS_PER_BYTE - weight; + if (unlikely(bitflips > bitflips_threshold)) + return -EBADMSG; + } + + for (; len >= sizeof(long); + len -= sizeof(long), bitmap += sizeof(long)) { + unsigned long d = *((unsigned long *)bitmap); + if (d == ~0UL) + continue; + weight = hweight_long(d); + bitflips += BITS_PER_LONG - weight; + if (unlikely(bitflips > bitflips_threshold)) + return -EBADMSG; + } + + for (; len > 0; len--, bitmap++) { + weight = hweight8(*bitmap); + bitflips += BITS_PER_BYTE - weight; + if (unlikely(bitflips > bitflips_threshold)) + return -EBADMSG; + } + + return bitflips; +} + +/** + * nand_check_erased_ecc_chunk - check if an ECC chunk contains (almost) only + * 0xff data + * @data: data buffer to test + * @datalen: data length + * @ecc: ECC buffer + * @ecclen: ECC length + * @extraoob: extra OOB buffer + * @extraooblen: extra OOB length + * @bitflips_threshold: maximum number of bitflips + * + * Check if a data buffer and its associated ECC and OOB data contains only + * 0xff pattern, which means the underlying region has been erased and is + * ready to be programmed. + * The bitflips_threshold specify the maximum number of bitflips before + * considering the region as not erased. + * + * Note: + * 1/ ECC algorithms are working on pre-defined block sizes which are usually + * different from the NAND page size. When fixing bitflips, ECC engines will + * report the number of errors per chunk, and the NAND core infrastructure + * expect you to return the maximum number of bitflips for the whole page. + * This is why you should always use this function on a single chunk and + * not on the whole page. After checking each chunk you should update your + * max_bitflips value accordingly. + * 2/ When checking for bitflips in erased pages you should not only check + * the payload data but also their associated ECC data, because a user might + * have programmed almost all bits to 1 but a few. In this case, we + * shouldn't consider the chunk as erased, and checking ECC bytes prevent + * this case. + * 3/ The extraoob argument is optional, and should be used if some of your OOB + * data are protected by the ECC engine. + * It could also be used if you support subpages and want to attach some + * extra OOB data to an ECC chunk. + * + * Returns a positive number of bitflips less than or equal to + * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the + * threshold. In case of success, the passed buffers are filled with 0xff. + */ +int nand_check_erased_ecc_chunk(void *data, int datalen, + void *ecc, int ecclen, + void *extraoob, int extraooblen, + int bitflips_threshold) +{ + int data_bitflips = 0, ecc_bitflips = 0, extraoob_bitflips = 0; + + data_bitflips = nand_check_erased_buf(data, datalen, + bitflips_threshold); + if (data_bitflips < 0) + return data_bitflips; + + bitflips_threshold -= data_bitflips; + + ecc_bitflips = nand_check_erased_buf(ecc, ecclen, bitflips_threshold); + if (ecc_bitflips < 0) + return ecc_bitflips; + + bitflips_threshold -= ecc_bitflips; + + extraoob_bitflips = nand_check_erased_buf(extraoob, extraooblen, + bitflips_threshold); + if (extraoob_bitflips < 0) + return extraoob_bitflips; + + if (data_bitflips) + memset(data, 0xff, datalen); + + if (ecc_bitflips) + memset(ecc, 0xff, ecclen); + + if (extraoob_bitflips) + memset(extraoob, 0xff, extraooblen); + + return data_bitflips + ecc_bitflips + extraoob_bitflips; +} +EXPORT_SYMBOL(nand_check_erased_ecc_chunk); + +/** * nanddev_isbad() - Check if a block is bad * @nand: NAND device * @pos: position pointing to the block we want to check diff --git a/drivers/mtd/nand/ecc-mxic.c b/drivers/mtd/nand/ecc-mxic.c index 1bf9a5a64b87..60cdcb4175ef 100644 --- a/drivers/mtd/nand/ecc-mxic.c +++ b/drivers/mtd/nand/ecc-mxic.c @@ -322,14 +322,14 @@ static int mxic_ecc_init_ctx(struct nand_device *nand, struct device *dev) sg_init_table(ctx->sg, 2); /* Configuration dump and sanity checks */ - dev_err(dev, "DPE version number: %d\n", + dev_dbg(dev, "DPE version number: %d\n", readl(mxic->regs + DP_VER) >> DP_VER_OFFSET); - dev_err(dev, "Chunk size: %d\n", readl(mxic->regs + CHUNK_SIZE)); - dev_err(dev, "Main size: %d\n", readl(mxic->regs + MAIN_SIZE)); - dev_err(dev, "Spare size: %d\n", SPARE_SZ(spare_reg)); - dev_err(dev, "Rsv size: %ld\n", RSV_SZ(spare_reg)); - dev_err(dev, "Parity size: %d\n", ctx->parity_sz); - dev_err(dev, "Meta size: %d\n", ctx->meta_sz); + dev_dbg(dev, "Chunk size: %d\n", readl(mxic->regs + CHUNK_SIZE)); + dev_dbg(dev, "Main size: %d\n", readl(mxic->regs + MAIN_SIZE)); + dev_dbg(dev, "Spare size: %d\n", SPARE_SZ(spare_reg)); + dev_dbg(dev, "Rsv size: %ld\n", RSV_SZ(spare_reg)); + dev_dbg(dev, "Parity size: %d\n", ctx->parity_sz); + dev_dbg(dev, "Meta size: %d\n", ctx->meta_sz); if ((ctx->meta_sz + ctx->parity_sz + RSV_SZ(spare_reg)) != SPARE_SZ(spare_reg)) { diff --git a/drivers/mtd/nand/ecc-realtek.c b/drivers/mtd/nand/ecc-realtek.c new file mode 100644 index 000000000000..7d718934c909 --- /dev/null +++ b/drivers/mtd/nand/ecc-realtek.c @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support for Realtek hardware ECC engine in RTL93xx SoCs + */ + +#include <linux/bitfield.h> +#include <linux/dma-mapping.h> +#include <linux/mtd/nand.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +/* + * The Realtek ECC engine has two operation modes. + * + * - BCH6 : Generate 10 ECC bytes from 512 data bytes plus 6 free bytes + * - BCH12 : Generate 20 ECC bytes from 512 data bytes plus 6 free bytes + * + * It can run for arbitrary NAND flash chips with different block and OOB sizes. Currently there + * are only two known devices in the wild that have NAND flash and make use of this ECC engine + * (Linksys LGS328C & LGS352C). To keep compatibility with vendor firmware, new modes can only + * be added when new data layouts have been analyzed. For now allow BCH6 on flash with 2048 byte + * blocks and 64 bytes oob. + * + * This driver aligns with kernel ECC naming conventions. Neverthless a short notice on the + * Realtek naming conventions for the different structures in the OOB area. + * + * - BBI : Bad block indicator. The first two bytes of OOB. Protected by ECC! + * - tag : 6 User/free bytes. First tag "contains" 2 bytes BBI. Protected by ECC! + * - syndrome : ECC/parity bytes + * + * Altogether this gives currently the following block layout. + * + * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+ + * | 512 | 512 | 512 | 512 | 2 | 4 | 6 | 6 | 6 | 10 | 10 | 10 | 10 | + * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+ + * | data | data | data | data | BBI | free | free | free | free | ECC | ECC | ECC | ECC | + * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+ + */ + +#define RTL_ECC_ALLOWED_PAGE_SIZE 2048 +#define RTL_ECC_ALLOWED_OOB_SIZE 64 +#define RTL_ECC_ALLOWED_STRENGTH 6 + +#define RTL_ECC_BLOCK_SIZE 512 +#define RTL_ECC_FREE_SIZE 6 +#define RTL_ECC_PARITY_SIZE_BCH6 10 +#define RTL_ECC_PARITY_SIZE_BCH12 20 + +/* + * The engine is fed with two DMA regions. One for data (always 512 bytes) and one for free bytes + * and parity (either 16 bytes for BCH6 or 26 bytes for BCH12). Start and length of each must be + * aligned to a multiple of 4. + */ + +#define RTL_ECC_DMA_FREE_PARITY_SIZE ALIGN(RTL_ECC_FREE_SIZE + RTL_ECC_PARITY_SIZE_BCH12, 4) +#define RTL_ECC_DMA_SIZE (RTL_ECC_BLOCK_SIZE + RTL_ECC_DMA_FREE_PARITY_SIZE) + +#define RTL_ECC_CFG 0x00 +#define RTL_ECC_BCH6 0 +#define RTL_ECC_BCH12 BIT(28) +#define RTL_ECC_DMA_PRECISE BIT(12) +#define RTL_ECC_BURST_128 GENMASK(1, 0) +#define RTL_ECC_DMA_TRIGGER 0x08 +#define RTL_ECC_OP_DECODE 0 +#define RTL_ECC_OP_ENCODE BIT(0) +#define RTL_ECC_DMA_START 0x0c +#define RTL_ECC_DMA_TAG 0x10 +#define RTL_ECC_STATUS 0x14 +#define RTL_ECC_CORR_COUNT GENMASK(19, 12) +#define RTL_ECC_RESULT BIT(8) +#define RTL_ECC_ALL_ONE BIT(4) +#define RTL_ECC_OP_STATUS BIT(0) + +struct rtl_ecc_engine { + struct device *dev; + struct nand_ecc_engine engine; + struct mutex lock; + char *buf; + dma_addr_t buf_dma; + struct regmap *regmap; +}; + +struct rtl_ecc_ctx { + struct rtl_ecc_engine * rtlc; + struct nand_ecc_req_tweak_ctx req_ctx; + int steps; + int bch_mode; + int strength; + int parity_size; +}; + +static const struct regmap_config rtl_ecc_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static inline void *nand_to_ctx(struct nand_device *nand) +{ + return nand->ecc.ctx.priv; +} + +static inline struct rtl_ecc_engine *nand_to_rtlc(struct nand_device *nand) +{ + struct nand_ecc_engine *eng = nand->ecc.engine; + + return container_of(eng, struct rtl_ecc_engine, engine); +} + +static int rtl_ecc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct rtl_ecc_ctx *ctx = nand_to_ctx(nand); + + if (section < 0 || section >= ctx->steps) + return -ERANGE; + + oobregion->offset = ctx->steps * RTL_ECC_FREE_SIZE + section * ctx->parity_size; + oobregion->length = ctx->parity_size; + + return 0; +} + +static int rtl_ecc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct rtl_ecc_ctx *ctx = nand_to_ctx(nand); + int bbm; + + if (section < 0 || section >= ctx->steps) + return -ERANGE; + + /* reserve 2 BBM bytes in first block */ + bbm = section ? 0 : 2; + oobregion->offset = section * RTL_ECC_FREE_SIZE + bbm; + oobregion->length = RTL_ECC_FREE_SIZE - bbm; + + return 0; +} + +static const struct mtd_ooblayout_ops rtl_ecc_ooblayout_ops = { + .ecc = rtl_ecc_ooblayout_ecc, + .free = rtl_ecc_ooblayout_free, +}; + +static void rtl_ecc_kick_engine(struct rtl_ecc_ctx *ctx, int operation) +{ + struct rtl_ecc_engine *rtlc = ctx->rtlc; + + regmap_write(rtlc->regmap, RTL_ECC_CFG, + ctx->bch_mode | RTL_ECC_BURST_128 | RTL_ECC_DMA_PRECISE); + + regmap_write(rtlc->regmap, RTL_ECC_DMA_START, rtlc->buf_dma); + regmap_write(rtlc->regmap, RTL_ECC_DMA_TAG, rtlc->buf_dma + RTL_ECC_BLOCK_SIZE); + regmap_write(rtlc->regmap, RTL_ECC_DMA_TRIGGER, operation); +} + +static int rtl_ecc_wait_for_engine(struct rtl_ecc_ctx *ctx) +{ + struct rtl_ecc_engine *rtlc = ctx->rtlc; + int ret, status, bitflips; + bool all_one; + + /* + * The ECC engine needs 6-8 us to encode/decode a BCH6 syndrome for 512 bytes of data + * and 6 free bytes. In case the NAND area has been erased and all data and oob is + * set to 0xff, decoding takes 30us (reason unknown). Although the engine can trigger + * interrupts when finished, use active polling for now. 12 us maximum wait time has + * proven to be a good tradeoff between performance and overhead. + */ + + ret = regmap_read_poll_timeout(rtlc->regmap, RTL_ECC_STATUS, status, + !(status & RTL_ECC_OP_STATUS), 12, 1000000); + if (ret) + return ret; + + ret = FIELD_GET(RTL_ECC_RESULT, status); + all_one = FIELD_GET(RTL_ECC_ALL_ONE, status); + bitflips = FIELD_GET(RTL_ECC_CORR_COUNT, status); + + /* For erased blocks (all bits one) error status can be ignored */ + if (all_one) + ret = 0; + + return ret ? -EBADMSG : bitflips; +} + +static int rtl_ecc_run_engine(struct rtl_ecc_ctx *ctx, char *data, char *free, + char *parity, int operation) +{ + struct rtl_ecc_engine *rtlc = ctx->rtlc; + char *buf_parity = rtlc->buf + RTL_ECC_BLOCK_SIZE + RTL_ECC_FREE_SIZE; + char *buf_free = rtlc->buf + RTL_ECC_BLOCK_SIZE; + char *buf_data = rtlc->buf; + int ret; + + mutex_lock(&rtlc->lock); + + memcpy(buf_data, data, RTL_ECC_BLOCK_SIZE); + memcpy(buf_free, free, RTL_ECC_FREE_SIZE); + memcpy(buf_parity, parity, ctx->parity_size); + + dma_sync_single_for_device(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_TO_DEVICE); + rtl_ecc_kick_engine(ctx, operation); + ret = rtl_ecc_wait_for_engine(ctx); + dma_sync_single_for_cpu(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_FROM_DEVICE); + + if (ret >= 0) { + memcpy(data, buf_data, RTL_ECC_BLOCK_SIZE); + memcpy(free, buf_free, RTL_ECC_FREE_SIZE); + memcpy(parity, buf_parity, ctx->parity_size); + } + + mutex_unlock(&rtlc->lock); + + return ret; +} + +static int rtl_ecc_prepare_io_req(struct nand_device *nand, struct nand_page_io_req *req) +{ + struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand); + struct rtl_ecc_ctx *ctx = nand_to_ctx(nand); + char *data, *free, *parity; + int ret = 0; + + if (req->mode == MTD_OPS_RAW) + return 0; + + nand_ecc_tweak_req(&ctx->req_ctx, req); + + if (req->type == NAND_PAGE_READ) + return 0; + + free = req->oobbuf.in; + data = req->databuf.in; + parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE; + + for (int i = 0; i < ctx->steps; i++) { + ret |= rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_ENCODE); + + free += RTL_ECC_FREE_SIZE; + data += RTL_ECC_BLOCK_SIZE; + parity += ctx->parity_size; + } + + if (unlikely(ret)) + dev_dbg(rtlc->dev, "ECC calculation failed\n"); + + return ret ? -EBADMSG : 0; +} + +static int rtl_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req) +{ + struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand); + struct rtl_ecc_ctx *ctx = nand_to_ctx(nand); + struct mtd_info *mtd = nanddev_to_mtd(nand); + char *data, *free, *parity; + bool failure = false; + int bitflips = 0; + + if (req->mode == MTD_OPS_RAW) + return 0; + + if (req->type == NAND_PAGE_WRITE) { + nand_ecc_restore_req(&ctx->req_ctx, req); + return 0; + } + + free = req->oobbuf.in; + data = req->databuf.in; + parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE; + + for (int i = 0 ; i < ctx->steps; i++) { + int ret = rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_DECODE); + + if (unlikely(ret < 0)) + /* ECC totally fails for bitflips in erased blocks */ + ret = nand_check_erased_ecc_chunk(data, RTL_ECC_BLOCK_SIZE, + parity, ctx->parity_size, + free, RTL_ECC_FREE_SIZE, + ctx->strength); + if (unlikely(ret < 0)) { + failure = true; + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += ret; + bitflips = max_t(unsigned int, bitflips, ret); + } + + free += RTL_ECC_FREE_SIZE; + data += RTL_ECC_BLOCK_SIZE; + parity += ctx->parity_size; + } + + nand_ecc_restore_req(&ctx->req_ctx, req); + + if (unlikely(failure)) + dev_dbg(rtlc->dev, "ECC correction failed\n"); + else if (unlikely(bitflips > 2)) + dev_dbg(rtlc->dev, "%d bitflips detected\n", bitflips); + + return failure ? -EBADMSG : bitflips; +} + +static int rtl_ecc_check_support(struct nand_device *nand) +{ + struct mtd_info *mtd = nanddev_to_mtd(nand); + struct device *dev = nand->ecc.engine->dev; + + if (mtd->oobsize != RTL_ECC_ALLOWED_OOB_SIZE || + mtd->writesize != RTL_ECC_ALLOWED_PAGE_SIZE) { + dev_err(dev, "only flash geometry data=%d, oob=%d supported\n", + RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_OOB_SIZE); + return -EINVAL; + } + + if (nand->ecc.user_conf.algo != NAND_ECC_ALGO_BCH || + nand->ecc.user_conf.strength != RTL_ECC_ALLOWED_STRENGTH || + nand->ecc.user_conf.placement != NAND_ECC_PLACEMENT_OOB || + nand->ecc.user_conf.step_size != RTL_ECC_BLOCK_SIZE) { + dev_err(dev, "only algo=bch, strength=%d, placement=oob, step=%d supported\n", + RTL_ECC_ALLOWED_STRENGTH, RTL_ECC_BLOCK_SIZE); + return -EINVAL; + } + + return 0; +} + +static int rtl_ecc_init_ctx(struct nand_device *nand) +{ + struct nand_ecc_props *conf = &nand->ecc.ctx.conf; + struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand); + struct mtd_info *mtd = nanddev_to_mtd(nand); + int strength = nand->ecc.user_conf.strength; + struct device *dev = nand->ecc.engine->dev; + struct rtl_ecc_ctx *ctx; + int ret; + + ret = rtl_ecc_check_support(nand); + if (ret) + return ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + nand->ecc.ctx.priv = ctx; + mtd_set_ooblayout(mtd, &rtl_ecc_ooblayout_ops); + + conf->algo = NAND_ECC_ALGO_BCH; + conf->strength = strength; + conf->step_size = RTL_ECC_BLOCK_SIZE; + conf->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + + ctx->rtlc = rtlc; + ctx->steps = mtd->writesize / RTL_ECC_BLOCK_SIZE; + ctx->strength = strength; + ctx->bch_mode = strength == 6 ? RTL_ECC_BCH6 : RTL_ECC_BCH12; + ctx->parity_size = strength == 6 ? RTL_ECC_PARITY_SIZE_BCH6 : RTL_ECC_PARITY_SIZE_BCH12; + + ret = nand_ecc_init_req_tweaking(&ctx->req_ctx, nand); + if (ret) + return ret; + + dev_dbg(dev, "using bch%d with geometry data=%dx%d, free=%dx6, parity=%dx%d", + conf->strength, ctx->steps, conf->step_size, + ctx->steps, ctx->steps, ctx->parity_size); + + return 0; +} + +static void rtl_ecc_cleanup_ctx(struct nand_device *nand) +{ + struct rtl_ecc_ctx *ctx = nand_to_ctx(nand); + + if (ctx) + nand_ecc_cleanup_req_tweaking(&ctx->req_ctx); +} + +static struct nand_ecc_engine_ops rtl_ecc_engine_ops = { + .init_ctx = rtl_ecc_init_ctx, + .cleanup_ctx = rtl_ecc_cleanup_ctx, + .prepare_io_req = rtl_ecc_prepare_io_req, + .finish_io_req = rtl_ecc_finish_io_req, +}; + +static int rtl_ecc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rtl_ecc_engine *rtlc; + void __iomem *base; + int ret; + + rtlc = devm_kzalloc(dev, sizeof(*rtlc), GFP_KERNEL); + if (!rtlc) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + ret = devm_mutex_init(dev, &rtlc->lock); + if (ret) + return ret; + + rtlc->regmap = devm_regmap_init_mmio(dev, base, &rtl_ecc_regmap_config); + if (IS_ERR(rtlc->regmap)) + return PTR_ERR(rtlc->regmap); + + /* + * Focus on simplicity and use a preallocated DMA buffer for data exchange with the + * engine. For now make it a noncoherent memory model as invalidating/flushing caches + * is faster than reading/writing uncached memory on the known architectures. + */ + + rtlc->buf = dma_alloc_noncoherent(dev, RTL_ECC_DMA_SIZE, &rtlc->buf_dma, + DMA_BIDIRECTIONAL, GFP_KERNEL); + if (IS_ERR(rtlc->buf)) + return PTR_ERR(rtlc->buf); + + rtlc->dev = dev; + rtlc->engine.dev = dev; + rtlc->engine.ops = &rtl_ecc_engine_ops; + rtlc->engine.integration = NAND_ECC_ENGINE_INTEGRATION_EXTERNAL; + + nand_ecc_register_on_host_hw_engine(&rtlc->engine); + + platform_set_drvdata(pdev, rtlc); + + return 0; +} + +static void rtl_ecc_remove(struct platform_device *pdev) +{ + struct rtl_ecc_engine *rtlc = platform_get_drvdata(pdev); + + nand_ecc_unregister_on_host_hw_engine(&rtlc->engine); + dma_free_noncoherent(rtlc->dev, RTL_ECC_DMA_SIZE, rtlc->buf, rtlc->buf_dma, + DMA_BIDIRECTIONAL); +} + +static const struct of_device_id rtl_ecc_of_ids[] = { + { + .compatible = "realtek,rtl9301-ecc", + }, + { /* sentinel */ }, +}; + +static struct platform_driver rtl_ecc_driver = { + .driver = { + .name = "rtl-nand-ecc-engine", + .of_match_table = rtl_ecc_of_ids, + }, + .probe = rtl_ecc_probe, + .remove = rtl_ecc_remove, +}; +module_platform_driver(rtl_ecc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen@gmx.de>"); +MODULE_DESCRIPTION("Realtek NAND hardware ECC controller"); diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c index 8f996e8d61b8..6ccdff3fc913 100644 --- a/drivers/mtd/nand/ecc.c +++ b/drivers/mtd/nand/ecc.c @@ -552,7 +552,7 @@ void nand_ecc_tweak_req(struct nand_ecc_req_tweak_ctx *ctx, memset(tweak->oobbuf.in, 0xFF, ctx->oob_buffer_size); } - /* Copy the data that must be writen in the bounce buffers, if needed */ + /* Copy the data that must be written in the bounce buffers, if needed */ if (orig->type == NAND_PAGE_WRITE) { if (ctx->bounce_data) memcpy((void *)tweak->databuf.out + orig->dataoffs, diff --git a/drivers/mtd/nand/onenand/onenand_omap2.c b/drivers/mtd/nand/onenand/onenand_omap2.c index f9a386b69050..0793251ada3b 100644 --- a/drivers/mtd/nand/onenand/onenand_omap2.c +++ b/drivers/mtd/nand/onenand/onenand_omap2.c @@ -603,7 +603,6 @@ static struct platform_driver omap2_onenand_driver = { module_platform_driver(omap2_onenand_driver); -MODULE_ALIAS("platform:" DRIVER_NAME); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jarkko Lavinen <jarkko.lavinen@nokia.com>"); MODULE_DESCRIPTION("Glue layer for OneNAND flash on OMAP2 / OMAP3"); diff --git a/drivers/mtd/nand/qpic_common.c b/drivers/mtd/nand/qpic_common.c index 8e604cc22ca3..db6c46a6fe01 100644 --- a/drivers/mtd/nand/qpic_common.c +++ b/drivers/mtd/nand/qpic_common.c @@ -89,10 +89,8 @@ void qcom_clear_bam_transaction(struct qcom_nand_controller *nandc) memset(&bam_txn->bam_positions, 0, sizeof(bam_txn->bam_positions)); bam_txn->last_data_desc = NULL; - sg_init_table(bam_txn->cmd_sgl, nandc->max_cwperpage * - QPIC_PER_CW_CMD_SGL); - sg_init_table(bam_txn->data_sgl, nandc->max_cwperpage * - QPIC_PER_CW_DATA_SGL); + sg_init_table(bam_txn->cmd_sgl, bam_txn->cmd_sgl_nitems); + sg_init_table(bam_txn->data_sgl, bam_txn->data_sgl_nitems); reinit_completion(&bam_txn->txn_done); } diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index 4b99d9c422c3..7408f34f0c68 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -77,32 +77,6 @@ config MTD_NAND_NDFC help NDFC Nand Flash Controllers are integrated in IBM/AMCC's 4xx SoCs -config MTD_NAND_S3C2410 - tristate "Samsung S3C NAND controller" - depends on ARCH_S3C64XX - help - This enables the NAND flash controller on the S3C24xx and S3C64xx - SoCs - - No board specific support is done by this driver, each board - must advertise a platform_device for the driver to attach. - -config MTD_NAND_S3C2410_DEBUG - bool "Samsung S3C NAND controller debug" - depends on MTD_NAND_S3C2410 - help - Enable debugging of the S3C NAND driver - -config MTD_NAND_S3C2410_CLKSTOP - bool "Samsung S3C NAND IDLE clock stop" - depends on MTD_NAND_S3C2410 - default n - help - Stop the clock to the NAND controller when there is no chip - selected to save power. This will mean there is a small delay - when the is NAND chip selected or released, but will save - approximately 5mA of power when there is nothing happening. - config MTD_NAND_SHARPSL tristate "Sharp SL Series (C7xx + others) NAND controller" depends on ARCH_PXA || COMPILE_TEST @@ -462,12 +436,12 @@ config MTD_NAND_NUVOTON_MA35 Enables support for the NAND controller found on the Nuvoton MA35 series SoCs. -config MTD_NAND_LOONGSON1 - tristate "Loongson1 NAND controller" - depends on LOONGSON1_APB_DMA || COMPILE_TEST +config MTD_NAND_LOONGSON + tristate "Loongson NAND controller" + depends on LOONGSON1_APB_DMA || LOONGSON2_APB_DMA || COMPILE_TEST select REGMAP_MMIO help - Enables support for NAND controller on Loongson1 SoCs. + Enables support for NAND controller on Loongson family chips. comment "Misc" diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile index 711d043ad4f8..619760138d32 100644 --- a/drivers/mtd/nand/raw/Makefile +++ b/drivers/mtd/nand/raw/Makefile @@ -9,7 +9,6 @@ obj-$(CONFIG_MTD_NAND_DENALI) += denali.o obj-$(CONFIG_MTD_NAND_DENALI_PCI) += denali_pci.o obj-$(CONFIG_MTD_NAND_DENALI_DT) += denali_dt.o obj-$(CONFIG_MTD_NAND_AU1550) += au1550nd.o -obj-$(CONFIG_MTD_NAND_S3C2410) += s3c2410.o obj-$(CONFIG_MTD_NAND_DAVINCI) += davinci_nand.o obj-$(CONFIG_MTD_NAND_DISKONCHIP) += diskonchip.o obj-$(CONFIG_MTD_NAND_FSMC) += fsmc_nand.o @@ -59,7 +58,7 @@ obj-$(CONFIG_MTD_NAND_ROCKCHIP) += rockchip-nand-controller.o obj-$(CONFIG_MTD_NAND_PL35X) += pl35x-nand-controller.o obj-$(CONFIG_MTD_NAND_RENESAS) += renesas-nand-controller.o obj-$(CONFIG_MTD_NAND_NUVOTON_MA35) += nuvoton-ma35d1-nand-controller.o -obj-$(CONFIG_MTD_NAND_LOONGSON1) += loongson1-nand-controller.o +obj-$(CONFIG_MTD_NAND_LOONGSON) += loongson-nand-controller.o nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o nand-objs += nand_onfi.o diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c index db94d14a3807..83ba4ebd02d4 100644 --- a/drivers/mtd/nand/raw/atmel/nand-controller.c +++ b/drivers/mtd/nand/raw/atmel/nand-controller.c @@ -1240,7 +1240,7 @@ static int atmel_smc_nand_prepare_smcconf(struct atmel_nand *nand, const struct nand_interface_config *conf, struct atmel_smc_cs_conf *smcconf) { - u32 ncycles, totalcycles, timeps, mckperiodps; + u32 ncycles, totalcycles, timeps, mckperiodps, pulse; struct atmel_nand_controller *nc; int ret; @@ -1366,11 +1366,16 @@ static int atmel_smc_nand_prepare_smcconf(struct atmel_nand *nand, ATMEL_SMC_MODE_TDFMODE_OPTIMIZED; /* - * Read pulse timing directly matches tRP: + * Read pulse timing would directly match tRP, + * but some NAND flash chips (S34ML01G2 and W29N02KVxxAF) + * do not work properly in timing mode 3. + * The workaround is to extend the SMC NRD pulse to meet tREA + * timing. * - * NRD_PULSE = tRP + * NRD_PULSE = max(tRP, tREA) */ - ncycles = DIV_ROUND_UP(conf->timings.sdr.tRP_min, mckperiodps); + pulse = max(conf->timings.sdr.tRP_min, conf->timings.sdr.tREA_max); + ncycles = DIV_ROUND_UP(pulse, mckperiodps); totalcycles += ncycles; ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NRD_SHIFT, ncycles); @@ -1858,7 +1863,7 @@ atmel_nand_controller_legacy_add_nands(struct atmel_nand_controller *nc) static int atmel_nand_controller_add_nands(struct atmel_nand_controller *nc) { - struct device_node *np, *nand_np; + struct device_node *np; struct device *dev = nc->dev; int ret, reg_cells; u32 val; @@ -1885,7 +1890,7 @@ static int atmel_nand_controller_add_nands(struct atmel_nand_controller *nc) reg_cells += val; - for_each_child_of_node(np, nand_np) { + for_each_child_of_node_scoped(np, nand_np) { struct atmel_nand *nand; nand = atmel_nand_create(nc, nand_np, reg_cells); diff --git a/drivers/mtd/nand/raw/atmel/pmecc.c b/drivers/mtd/nand/raw/atmel/pmecc.c index 0b402823b619..1d0e93e4edb1 100644 --- a/drivers/mtd/nand/raw/atmel/pmecc.c +++ b/drivers/mtd/nand/raw/atmel/pmecc.c @@ -1010,4 +1010,3 @@ module_platform_driver(atmel_pmecc_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); MODULE_DESCRIPTION("PMECC engine driver"); -MODULE_ALIAS("platform:atmel_pmecc"); diff --git a/drivers/mtd/nand/raw/fsmc_nand.c b/drivers/mtd/nand/raw/fsmc_nand.c index df61db8ce466..b13b2b0c3f30 100644 --- a/drivers/mtd/nand/raw/fsmc_nand.c +++ b/drivers/mtd/nand/raw/fsmc_nand.c @@ -876,10 +876,14 @@ static int fsmc_nand_probe_config_dt(struct platform_device *pdev, if (!of_property_read_u32(np, "bank-width", &val)) { if (val == 2) { nand->options |= NAND_BUSWIDTH_16; - } else if (val != 1) { + } else if (val == 1) { + nand->options |= NAND_BUSWIDTH_AUTO; + } else { dev_err(&pdev->dev, "invalid bank-width %u\n", val); return -EINVAL; } + } else { + nand->options |= NAND_BUSWIDTH_AUTO; } if (of_property_read_bool(np, "nand-skip-bbtscan")) diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c index f4e68008ea03..a750f5839e34 100644 --- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c +++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c @@ -145,6 +145,9 @@ err_clk: return ret; } +#define gpmi_enable_clk(x) __gpmi_enable_clk(x, true) +#define gpmi_disable_clk(x) __gpmi_enable_clk(x, false) + static int gpmi_init(struct gpmi_nand_data *this) { struct resources *r = &this->resources; @@ -2765,6 +2768,11 @@ static int gpmi_nand_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); pm_runtime_set_autosuspend_delay(&pdev->dev, 500); pm_runtime_use_autosuspend(&pdev->dev); +#ifndef CONFIG_PM + ret = gpmi_enable_clk(this); + if (ret) + goto exit_acquire_resources; +#endif ret = gpmi_init(this); if (ret) @@ -2800,6 +2808,9 @@ static void gpmi_nand_remove(struct platform_device *pdev) release_resources(this); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_disable(&pdev->dev); +#ifndef CONFIG_PM + gpmi_disable_clk(this); +#endif } static int gpmi_pm_suspend(struct device *dev) @@ -2846,9 +2857,6 @@ static int gpmi_pm_resume(struct device *dev) return 0; } -#define gpmi_enable_clk(x) __gpmi_enable_clk(x, true) -#define gpmi_disable_clk(x) __gpmi_enable_clk(x, false) - static int gpmi_runtime_suspend(struct device *dev) { struct gpmi_nand_data *this = dev_get_drvdata(dev); diff --git a/drivers/mtd/nand/raw/loongson-nand-controller.c b/drivers/mtd/nand/raw/loongson-nand-controller.c new file mode 100644 index 000000000000..8490412d5be1 --- /dev/null +++ b/drivers/mtd/nand/raw/loongson-nand-controller.c @@ -0,0 +1,1024 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NAND Controller Driver for Loongson family chips + * + * Copyright (C) 2015-2025 Keguang Zhang <keguang.zhang@gmail.com> + * Copyright (C) 2025 Binbin Zhou <zhoubinbin@loongson.cn> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/iopoll.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/rawnand.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/sizes.h> + +/* Loongson NAND Controller Registers */ +#define LOONGSON_NAND_CMD 0x0 +#define LOONGSON_NAND_ADDR1 0x4 +#define LOONGSON_NAND_ADDR2 0x8 +#define LOONGSON_NAND_TIMING 0xc +#define LOONGSON_NAND_IDL 0x10 +#define LOONGSON_NAND_IDH_STATUS 0x14 +#define LOONGSON_NAND_PARAM 0x18 +#define LOONGSON_NAND_OP_NUM 0x1c +#define LOONGSON_NAND_CS_RDY_MAP 0x20 + +/* Bitfields of nand command register */ +#define LOONGSON_NAND_CMD_OP_DONE BIT(10) +#define LOONGSON_NAND_CMD_OP_SPARE BIT(9) +#define LOONGSON_NAND_CMD_OP_MAIN BIT(8) +#define LOONGSON_NAND_CMD_STATUS BIT(7) +#define LOONGSON_NAND_CMD_RESET BIT(6) +#define LOONGSON_NAND_CMD_READID BIT(5) +#define LOONGSON_NAND_CMD_BLOCKS_ERASE BIT(4) +#define LOONGSON_NAND_CMD_ERASE BIT(3) +#define LOONGSON_NAND_CMD_WRITE BIT(2) +#define LOONGSON_NAND_CMD_READ BIT(1) +#define LOONGSON_NAND_CMD_VALID BIT(0) + +/* Bitfields of nand cs/rdy map register */ +#define LOONGSON_NAND_MAP_CS1_SEL GENMASK(11, 8) +#define LOONGSON_NAND_MAP_RDY1_SEL GENMASK(15, 12) +#define LOONGSON_NAND_MAP_CS2_SEL GENMASK(19, 16) +#define LOONGSON_NAND_MAP_RDY2_SEL GENMASK(23, 20) +#define LOONGSON_NAND_MAP_CS3_SEL GENMASK(27, 24) +#define LOONGSON_NAND_MAP_RDY3_SEL GENMASK(31, 28) + +#define LOONGSON_NAND_CS_SEL0 BIT(0) +#define LOONGSON_NAND_CS_SEL1 BIT(1) +#define LOONGSON_NAND_CS_SEL2 BIT(2) +#define LOONGSON_NAND_CS_SEL3 BIT(3) +#define LOONGSON_NAND_CS_RDY0 BIT(0) +#define LOONGSON_NAND_CS_RDY1 BIT(1) +#define LOONGSON_NAND_CS_RDY2 BIT(2) +#define LOONGSON_NAND_CS_RDY3 BIT(3) + +/* Bitfields of nand timing register */ +#define LOONGSON_NAND_WAIT_CYCLE_MASK GENMASK(7, 0) +#define LOONGSON_NAND_HOLD_CYCLE_MASK GENMASK(15, 8) + +/* Bitfields of nand parameter register */ +#define LOONGSON_NAND_CELL_SIZE_MASK GENMASK(11, 8) + +#define LOONGSON_NAND_COL_ADDR_CYC 2U +#define LOONGSON_NAND_MAX_ADDR_CYC 5U + +#define LOONGSON_NAND_READ_ID_SLEEP_US 1000 +#define LOONGSON_NAND_READ_ID_TIMEOUT_US 5000 + +#define BITS_PER_WORD (4 * BITS_PER_BYTE) + +/* Loongson-2K1000 NAND DMA routing register */ +#define LS2K1000_NAND_DMA_MASK GENMASK(2, 0) +#define LS2K1000_DMA0_CONF 0x0 +#define LS2K1000_DMA1_CONF 0x1 +#define LS2K1000_DMA2_CONF 0x2 +#define LS2K1000_DMA3_CONF 0x3 +#define LS2K1000_DMA4_CONF 0x4 + +struct loongson_nand_host; + +struct loongson_nand_op { + char addrs[LOONGSON_NAND_MAX_ADDR_CYC]; + unsigned int naddrs; + unsigned int addrs_offset; + unsigned int aligned_offset; + unsigned int cmd_reg; + unsigned int row_start; + unsigned int rdy_timeout_ms; + unsigned int orig_len; + bool is_readid; + bool is_erase; + bool is_write; + bool is_read; + bool is_change_column; + size_t len; + char *buf; +}; + +struct loongson_nand_data { + unsigned int max_id_cycle; + unsigned int id_cycle_field; + unsigned int status_field; + unsigned int op_scope_field; + unsigned int hold_cycle; + unsigned int wait_cycle; + unsigned int nand_cs; + unsigned int dma_bits; + int (*dma_config)(struct device *dev); + void (*set_addr)(struct loongson_nand_host *host, struct loongson_nand_op *op); +}; + +struct loongson_nand_host { + struct device *dev; + struct nand_chip chip; + struct nand_controller controller; + const struct loongson_nand_data *data; + unsigned int addr_cs_field; + void __iomem *reg_base; + struct regmap *regmap; + /* DMA Engine stuff */ + dma_addr_t dma_base; + struct dma_chan *dma_chan; + dma_cookie_t dma_cookie; + struct completion dma_complete; +}; + +static const struct regmap_config loongson_nand_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int loongson_nand_op_cmd_mapping(struct nand_chip *chip, struct loongson_nand_op *op, + u8 opcode) +{ + struct loongson_nand_host *host = nand_get_controller_data(chip); + + op->row_start = chip->page_shift + 1; + + /* The controller abstracts the following NAND operations. */ + switch (opcode) { + case NAND_CMD_STATUS: + op->cmd_reg = LOONGSON_NAND_CMD_STATUS; + break; + case NAND_CMD_RESET: + op->cmd_reg = LOONGSON_NAND_CMD_RESET; + break; + case NAND_CMD_READID: + op->is_readid = true; + op->cmd_reg = LOONGSON_NAND_CMD_READID; + break; + case NAND_CMD_ERASE1: + op->is_erase = true; + op->addrs_offset = LOONGSON_NAND_COL_ADDR_CYC; + break; + case NAND_CMD_ERASE2: + if (!op->is_erase) + return -EOPNOTSUPP; + /* During erasing, row_start differs from the default value. */ + op->row_start = chip->page_shift; + op->cmd_reg = LOONGSON_NAND_CMD_ERASE; + break; + case NAND_CMD_SEQIN: + op->is_write = true; + break; + case NAND_CMD_PAGEPROG: + if (!op->is_write) + return -EOPNOTSUPP; + op->cmd_reg = LOONGSON_NAND_CMD_WRITE; + break; + case NAND_CMD_READ0: + op->is_read = true; + break; + case NAND_CMD_READSTART: + if (!op->is_read) + return -EOPNOTSUPP; + op->cmd_reg = LOONGSON_NAND_CMD_READ; + break; + case NAND_CMD_RNDOUT: + op->is_change_column = true; + break; + case NAND_CMD_RNDOUTSTART: + if (!op->is_change_column) + return -EOPNOTSUPP; + op->cmd_reg = LOONGSON_NAND_CMD_READ; + break; + default: + dev_dbg(host->dev, "unsupported opcode: %u\n", opcode); + return -EOPNOTSUPP; + } + + return 0; +} + +static int loongson_nand_parse_instructions(struct nand_chip *chip, const struct nand_subop *subop, + struct loongson_nand_op *op) +{ + unsigned int op_id; + int ret; + + for (op_id = 0; op_id < subop->ninstrs; op_id++) { + const struct nand_op_instr *instr = &subop->instrs[op_id]; + unsigned int offset, naddrs; + const u8 *addrs; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + ret = loongson_nand_op_cmd_mapping(chip, op, instr->ctx.cmd.opcode); + if (ret < 0) + return ret; + + break; + case NAND_OP_ADDR_INSTR: + naddrs = nand_subop_get_num_addr_cyc(subop, op_id); + if (naddrs > LOONGSON_NAND_MAX_ADDR_CYC) + return -EOPNOTSUPP; + op->naddrs = naddrs; + offset = nand_subop_get_addr_start_off(subop, op_id); + addrs = &instr->ctx.addr.addrs[offset]; + memcpy(op->addrs + op->addrs_offset, addrs, naddrs); + break; + case NAND_OP_DATA_IN_INSTR: + case NAND_OP_DATA_OUT_INSTR: + offset = nand_subop_get_data_start_off(subop, op_id); + op->orig_len = nand_subop_get_data_len(subop, op_id); + if (instr->type == NAND_OP_DATA_IN_INSTR) + op->buf = instr->ctx.data.buf.in + offset; + else if (instr->type == NAND_OP_DATA_OUT_INSTR) + op->buf = (void *)instr->ctx.data.buf.out + offset; + + break; + case NAND_OP_WAITRDY_INSTR: + op->rdy_timeout_ms = instr->ctx.waitrdy.timeout_ms; + break; + default: + break; + } + } + + return 0; +} + +static void loongson_nand_set_addr_cs(struct loongson_nand_host *host) +{ + struct nand_chip *chip = &host->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + + if (!host->data->nand_cs) + return; + + /* + * The Manufacturer/Chip ID read operation precedes attach_chip, at which point + * information such as NAND chip selection and capacity is unknown. As a + * workaround, we use 128MB cellsize (2KB pagesize) as a fallback. + */ + if (!mtd->writesize) + host->addr_cs_field = GENMASK(17, 16); + + regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR2, host->addr_cs_field, + host->data->nand_cs << __ffs(host->addr_cs_field)); +} + +static void ls1b_nand_set_addr(struct loongson_nand_host *host, struct loongson_nand_op *op) +{ + struct nand_chip *chip = &host->chip; + int i; + + for (i = 0; i < LOONGSON_NAND_MAX_ADDR_CYC; i++) { + int shift, mask, val; + + if (i < LOONGSON_NAND_COL_ADDR_CYC) { + shift = i * BITS_PER_BYTE; + mask = (u32)0xff << shift; + mask &= GENMASK(chip->page_shift, 0); + val = (u32)op->addrs[i] << shift; + regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR1, mask, val); + } else if (!op->is_change_column) { + shift = op->row_start + (i - LOONGSON_NAND_COL_ADDR_CYC) * BITS_PER_BYTE; + mask = (u32)0xff << shift; + val = (u32)op->addrs[i] << shift; + regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR1, mask, val); + + if (i == 4) { + mask = (u32)0xff >> (BITS_PER_WORD - shift); + val = (u32)op->addrs[i] >> (BITS_PER_WORD - shift); + regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR2, mask, val); + } + } + } +} + +static void ls1c_nand_set_addr(struct loongson_nand_host *host, struct loongson_nand_op *op) +{ + int i; + + for (i = 0; i < LOONGSON_NAND_MAX_ADDR_CYC; i++) { + int shift, mask, val; + + if (i < LOONGSON_NAND_COL_ADDR_CYC) { + shift = i * BITS_PER_BYTE; + mask = (u32)0xff << shift; + val = (u32)op->addrs[i] << shift; + regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR1, mask, val); + } else if (!op->is_change_column) { + shift = (i - LOONGSON_NAND_COL_ADDR_CYC) * BITS_PER_BYTE; + mask = (u32)0xff << shift; + val = (u32)op->addrs[i] << shift; + regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR2, mask, val); + } + } + + loongson_nand_set_addr_cs(host); +} + +static void loongson_nand_trigger_op(struct loongson_nand_host *host, struct loongson_nand_op *op) +{ + struct nand_chip *chip = &host->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + int col0 = op->addrs[0]; + short col; + + if (!IS_ALIGNED(col0, chip->buf_align)) { + col0 = ALIGN_DOWN(op->addrs[0], chip->buf_align); + op->aligned_offset = op->addrs[0] - col0; + op->addrs[0] = col0; + } + + if (host->data->set_addr) + host->data->set_addr(host, op); + + /* set operation length */ + if (op->is_write || op->is_read || op->is_change_column) + op->len = ALIGN(op->orig_len + op->aligned_offset, chip->buf_align); + else if (op->is_erase) + op->len = 1; + else + op->len = op->orig_len; + + writel(op->len, host->reg_base + LOONGSON_NAND_OP_NUM); + + /* set operation area and scope */ + col = op->addrs[1] << BITS_PER_BYTE | op->addrs[0]; + if (op->orig_len && !op->is_readid) { + unsigned int op_scope = 0; + + if (col < mtd->writesize) { + op->cmd_reg |= LOONGSON_NAND_CMD_OP_MAIN; + op_scope = mtd->writesize; + } + + op->cmd_reg |= LOONGSON_NAND_CMD_OP_SPARE; + op_scope += mtd->oobsize; + + op_scope <<= __ffs(host->data->op_scope_field); + regmap_update_bits(host->regmap, LOONGSON_NAND_PARAM, + host->data->op_scope_field, op_scope); + } + + /* set command */ + writel(op->cmd_reg, host->reg_base + LOONGSON_NAND_CMD); + + /* trigger operation */ + regmap_write_bits(host->regmap, LOONGSON_NAND_CMD, LOONGSON_NAND_CMD_VALID, + LOONGSON_NAND_CMD_VALID); +} + +static int loongson_nand_wait_for_op_done(struct loongson_nand_host *host, + struct loongson_nand_op *op) +{ + unsigned int val; + int ret = 0; + + if (op->rdy_timeout_ms) { + ret = regmap_read_poll_timeout(host->regmap, LOONGSON_NAND_CMD, + val, val & LOONGSON_NAND_CMD_OP_DONE, + 0, op->rdy_timeout_ms * MSEC_PER_SEC); + if (ret) + dev_err(host->dev, "operation failed\n"); + } + + return ret; +} + +static void loongson_nand_dma_callback(void *data) +{ + struct loongson_nand_host *host = (struct loongson_nand_host *)data; + struct dma_chan *chan = host->dma_chan; + struct device *dev = chan->device->dev; + enum dma_status status; + + status = dmaengine_tx_status(chan, host->dma_cookie, NULL); + if (likely(status == DMA_COMPLETE)) { + dev_dbg(dev, "DMA complete with cookie=%d\n", host->dma_cookie); + complete(&host->dma_complete); + } else { + dev_err(dev, "DMA error with cookie=%d\n", host->dma_cookie); + } +} + +static int loongson_nand_dma_transfer(struct loongson_nand_host *host, struct loongson_nand_op *op) +{ + struct nand_chip *chip = &host->chip; + struct dma_chan *chan = host->dma_chan; + struct device *dev = chan->device->dev; + struct dma_async_tx_descriptor *desc; + enum dma_data_direction data_dir = op->is_write ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + enum dma_transfer_direction xfer_dir = op->is_write ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; + void *buf = op->buf; + char *dma_buf = NULL; + dma_addr_t dma_addr; + int ret; + + if (IS_ALIGNED((uintptr_t)buf, chip->buf_align) && + IS_ALIGNED(op->orig_len, chip->buf_align)) { + dma_addr = dma_map_single(dev, buf, op->orig_len, data_dir); + if (dma_mapping_error(dev, dma_addr)) { + dev_err(dev, "failed to map DMA buffer\n"); + return -ENXIO; + } + } else if (!op->is_write) { + dma_buf = dma_alloc_coherent(dev, op->len, &dma_addr, GFP_KERNEL); + if (!dma_buf) + return -ENOMEM; + } else { + dev_err(dev, "subpage writing not supported\n"); + return -EOPNOTSUPP; + } + + desc = dmaengine_prep_slave_single(chan, dma_addr, op->len, xfer_dir, DMA_PREP_INTERRUPT); + if (!desc) { + dev_err(dev, "failed to prepare DMA descriptor\n"); + ret = -ENOMEM; + goto err; + } + desc->callback = loongson_nand_dma_callback; + desc->callback_param = host; + + host->dma_cookie = dmaengine_submit(desc); + ret = dma_submit_error(host->dma_cookie); + if (ret) { + dev_err(dev, "failed to submit DMA descriptor\n"); + goto err; + } + + dev_dbg(dev, "issue DMA with cookie=%d\n", host->dma_cookie); + dma_async_issue_pending(chan); + + if (!wait_for_completion_timeout(&host->dma_complete, msecs_to_jiffies(1000))) { + dmaengine_terminate_sync(chan); + reinit_completion(&host->dma_complete); + ret = -ETIMEDOUT; + goto err; + } + + if (dma_buf) + memcpy(buf, dma_buf + op->aligned_offset, op->orig_len); +err: + if (dma_buf) + dma_free_coherent(dev, op->len, dma_buf, dma_addr); + else + dma_unmap_single(dev, dma_addr, op->orig_len, data_dir); + + return ret; +} + +static int loongson_nand_data_type_exec(struct nand_chip *chip, const struct nand_subop *subop) +{ + struct loongson_nand_host *host = nand_get_controller_data(chip); + struct loongson_nand_op op = {}; + int ret; + + ret = loongson_nand_parse_instructions(chip, subop, &op); + if (ret) + return ret; + + loongson_nand_trigger_op(host, &op); + + ret = loongson_nand_dma_transfer(host, &op); + if (ret) + return ret; + + return loongson_nand_wait_for_op_done(host, &op); +} + +static int loongson_nand_misc_type_exec(struct nand_chip *chip, const struct nand_subop *subop, + struct loongson_nand_op *op) +{ + struct loongson_nand_host *host = nand_get_controller_data(chip); + int ret; + + ret = loongson_nand_parse_instructions(chip, subop, op); + if (ret) + return ret; + + loongson_nand_trigger_op(host, op); + + return loongson_nand_wait_for_op_done(host, op); +} + +static int loongson_nand_zerolen_type_exec(struct nand_chip *chip, const struct nand_subop *subop) +{ + struct loongson_nand_op op = {}; + + return loongson_nand_misc_type_exec(chip, subop, &op); +} + +static int loongson_nand_read_id_type_exec(struct nand_chip *chip, const struct nand_subop *subop) +{ + struct loongson_nand_host *host = nand_get_controller_data(chip); + struct loongson_nand_op op = {}; + int i, ret; + union { + char ids[6]; + struct { + int idl; + u16 idh; + }; + } nand_id; + + ret = loongson_nand_misc_type_exec(chip, subop, &op); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(host->regmap, LOONGSON_NAND_IDL, nand_id.idl, nand_id.idl, + LOONGSON_NAND_READ_ID_SLEEP_US, + LOONGSON_NAND_READ_ID_TIMEOUT_US); + if (ret) + return ret; + + nand_id.idh = readw(host->reg_base + LOONGSON_NAND_IDH_STATUS); + + for (i = 0; i < min(host->data->max_id_cycle, op.orig_len); i++) + op.buf[i] = nand_id.ids[host->data->max_id_cycle - 1 - i]; + + return ret; +} + +static int loongson_nand_read_status_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct loongson_nand_host *host = nand_get_controller_data(chip); + struct loongson_nand_op op = {}; + int val, ret; + + ret = loongson_nand_misc_type_exec(chip, subop, &op); + if (ret) + return ret; + + val = readl(host->reg_base + LOONGSON_NAND_IDH_STATUS); + val &= ~host->data->status_field; + op.buf[0] = val << ffs(host->data->status_field); + + return ret; +} + +static const struct nand_op_parser loongson_nand_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN( + loongson_nand_read_id_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, LOONGSON_NAND_MAX_ADDR_CYC), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 8)), + NAND_OP_PARSER_PATTERN( + loongson_nand_read_status_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 1)), + NAND_OP_PARSER_PATTERN( + loongson_nand_zerolen_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + NAND_OP_PARSER_PATTERN( + loongson_nand_zerolen_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, LOONGSON_NAND_MAX_ADDR_CYC), + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + NAND_OP_PARSER_PATTERN( + loongson_nand_data_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, LOONGSON_NAND_MAX_ADDR_CYC), + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 0)), + NAND_OP_PARSER_PATTERN( + loongson_nand_data_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, LOONGSON_NAND_MAX_ADDR_CYC), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 0), + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), + ); + +static int loongson_nand_is_valid_cmd(u8 opcode) +{ + if (opcode == NAND_CMD_STATUS || opcode == NAND_CMD_RESET || opcode == NAND_CMD_READID) + return 0; + + return -EOPNOTSUPP; +} + +static int loongson_nand_is_valid_cmd_seq(u8 opcode1, u8 opcode2) +{ + if (opcode1 == NAND_CMD_RNDOUT && opcode2 == NAND_CMD_RNDOUTSTART) + return 0; + + if (opcode1 == NAND_CMD_READ0 && opcode2 == NAND_CMD_READSTART) + return 0; + + if (opcode1 == NAND_CMD_ERASE1 && opcode2 == NAND_CMD_ERASE2) + return 0; + + if (opcode1 == NAND_CMD_SEQIN && opcode2 == NAND_CMD_PAGEPROG) + return 0; + + return -EOPNOTSUPP; +} + +static int loongson_nand_check_op(struct nand_chip *chip, const struct nand_operation *op) +{ + const struct nand_op_instr *instr1 = NULL, *instr2 = NULL; + int op_id; + + for (op_id = 0; op_id < op->ninstrs; op_id++) { + const struct nand_op_instr *instr = &op->instrs[op_id]; + + if (instr->type == NAND_OP_CMD_INSTR) { + if (!instr1) + instr1 = instr; + else if (!instr2) + instr2 = instr; + else + break; + } + } + + if (!instr1) + return -EOPNOTSUPP; + + if (!instr2) + return loongson_nand_is_valid_cmd(instr1->ctx.cmd.opcode); + + return loongson_nand_is_valid_cmd_seq(instr1->ctx.cmd.opcode, instr2->ctx.cmd.opcode); +} + +static int loongson_nand_exec_op(struct nand_chip *chip, const struct nand_operation *op, + bool check_only) +{ + if (check_only) + return loongson_nand_check_op(chip, op); + + return nand_op_parser_exec_op(chip, &loongson_nand_op_parser, op, check_only); +} + +static int loongson_nand_get_chip_capacity(struct nand_chip *chip) +{ + struct loongson_nand_host *host = nand_get_controller_data(chip); + u64 chipsize = nanddev_target_size(&chip->base); + struct mtd_info *mtd = nand_to_mtd(chip); + + switch (mtd->writesize) { + case SZ_512: + switch (chipsize) { + case SZ_8M: + host->addr_cs_field = GENMASK(15, 14); + return 0x9; + case SZ_16M: + host->addr_cs_field = GENMASK(16, 15); + return 0xa; + case SZ_32M: + host->addr_cs_field = GENMASK(17, 16); + return 0xb; + case SZ_64M: + host->addr_cs_field = GENMASK(18, 17); + return 0xc; + case SZ_128M: + host->addr_cs_field = GENMASK(19, 18); + return 0xd; + } + break; + case SZ_2K: + switch (chipsize) { + case SZ_128M: + host->addr_cs_field = GENMASK(17, 16); + return 0x0; + case SZ_256M: + host->addr_cs_field = GENMASK(18, 17); + return 0x1; + case SZ_512M: + host->addr_cs_field = GENMASK(19, 18); + return 0x2; + case SZ_1G: + host->addr_cs_field = GENMASK(20, 19); + return 0x3; + } + break; + case SZ_4K: + if (chipsize == SZ_2G) { + host->addr_cs_field = GENMASK(20, 19); + return 0x4; + } + break; + case SZ_8K: + switch (chipsize) { + case SZ_4G: + host->addr_cs_field = GENMASK(20, 19); + return 0x5; + case SZ_8G: + host->addr_cs_field = GENMASK(21, 20); + return 0x6; + case SZ_16G: + host->addr_cs_field = GENMASK(22, 21); + return 0x7; + } + break; + } + + dev_err(host->dev, "Unsupported chip size: %llu MB with page size %u B\n", + chipsize, mtd->writesize); + return -EINVAL; +} + +static int loongson_nand_attach_chip(struct nand_chip *chip) +{ + struct loongson_nand_host *host = nand_get_controller_data(chip); + int cell_size = loongson_nand_get_chip_capacity(chip); + + if (cell_size < 0) + return cell_size; + + switch (chip->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_NONE: + break; + case NAND_ECC_ENGINE_TYPE_SOFT: + break; + default: + return -EINVAL; + } + + /* set cell size */ + regmap_update_bits(host->regmap, LOONGSON_NAND_PARAM, LOONGSON_NAND_CELL_SIZE_MASK, + FIELD_PREP(LOONGSON_NAND_CELL_SIZE_MASK, cell_size)); + + regmap_update_bits(host->regmap, LOONGSON_NAND_TIMING, LOONGSON_NAND_HOLD_CYCLE_MASK, + FIELD_PREP(LOONGSON_NAND_HOLD_CYCLE_MASK, host->data->hold_cycle)); + + regmap_update_bits(host->regmap, LOONGSON_NAND_TIMING, LOONGSON_NAND_WAIT_CYCLE_MASK, + FIELD_PREP(LOONGSON_NAND_WAIT_CYCLE_MASK, host->data->wait_cycle)); + + chip->ecc.read_page_raw = nand_monolithic_read_page_raw; + chip->ecc.write_page_raw = nand_monolithic_write_page_raw; + + return 0; +} + +static const struct nand_controller_ops loongson_nand_controller_ops = { + .exec_op = loongson_nand_exec_op, + .attach_chip = loongson_nand_attach_chip, +}; + +static void loongson_nand_controller_cleanup(struct loongson_nand_host *host) +{ + if (host->dma_chan) + dma_release_channel(host->dma_chan); +} + +static int ls2k1000_nand_apbdma_config(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + void __iomem *regs; + int val; + + regs = devm_platform_ioremap_resource_byname(pdev, "dma-config"); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + val = readl(regs); + val |= FIELD_PREP(LS2K1000_NAND_DMA_MASK, LS2K1000_DMA0_CONF); + writel(val, regs); + + return 0; +} + +static int loongson_nand_controller_init(struct loongson_nand_host *host) +{ + struct device *dev = host->dev; + struct dma_chan *chan; + struct dma_slave_config cfg = {}; + int ret, val; + + host->regmap = devm_regmap_init_mmio(dev, host->reg_base, &loongson_nand_regmap_config); + if (IS_ERR(host->regmap)) + return dev_err_probe(dev, PTR_ERR(host->regmap), "failed to init regmap\n"); + + if (host->data->id_cycle_field) + regmap_update_bits(host->regmap, LOONGSON_NAND_PARAM, host->data->id_cycle_field, + host->data->max_id_cycle << __ffs(host->data->id_cycle_field)); + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(host->data->dma_bits)); + if (ret) + return dev_err_probe(dev, ret, "failed to set DMA mask\n"); + + val = FIELD_PREP(LOONGSON_NAND_MAP_CS1_SEL, LOONGSON_NAND_CS_SEL1) | + FIELD_PREP(LOONGSON_NAND_MAP_RDY1_SEL, LOONGSON_NAND_CS_RDY1) | + FIELD_PREP(LOONGSON_NAND_MAP_CS2_SEL, LOONGSON_NAND_CS_SEL2) | + FIELD_PREP(LOONGSON_NAND_MAP_RDY2_SEL, LOONGSON_NAND_CS_RDY2) | + FIELD_PREP(LOONGSON_NAND_MAP_CS3_SEL, LOONGSON_NAND_CS_SEL3) | + FIELD_PREP(LOONGSON_NAND_MAP_RDY3_SEL, LOONGSON_NAND_CS_RDY3); + + regmap_write(host->regmap, LOONGSON_NAND_CS_RDY_MAP, val); + + if (host->data->dma_config) { + ret = host->data->dma_config(dev); + if (ret) + return dev_err_probe(dev, ret, "failed to config DMA routing\n"); + } + + chan = dma_request_chan(dev, "rxtx"); + if (IS_ERR(chan)) + return dev_err_probe(dev, PTR_ERR(chan), "failed to request DMA channel\n"); + host->dma_chan = chan; + + cfg.src_addr = host->dma_base; + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.dst_addr = host->dma_base; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + ret = dmaengine_slave_config(host->dma_chan, &cfg); + if (ret) + return dev_err_probe(dev, ret, "failed to config DMA channel\n"); + + init_completion(&host->dma_complete); + + return 0; +} + +static int loongson_nand_chip_init(struct loongson_nand_host *host) +{ + struct device *dev = host->dev; + int nchips = of_get_child_count(dev->of_node); + struct device_node *chip_np; + struct nand_chip *chip = &host->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + if (nchips != 1) + return dev_err_probe(dev, -EINVAL, "Currently one NAND chip supported\n"); + + chip_np = of_get_next_child(dev->of_node, NULL); + if (!chip_np) + return dev_err_probe(dev, -ENODEV, "failed to get child node for NAND chip\n"); + + nand_set_flash_node(chip, chip_np); + of_node_put(chip_np); + if (!mtd->name) + return dev_err_probe(dev, -EINVAL, "Missing MTD label\n"); + + nand_set_controller_data(chip, host); + chip->controller = &host->controller; + chip->options = NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA | NAND_BROKEN_XD; + chip->buf_align = 16; + mtd->dev.parent = dev; + mtd->owner = THIS_MODULE; + + ret = nand_scan(chip, 1); + if (ret) + return dev_err_probe(dev, ret, "failed to scan NAND chip\n"); + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + nand_cleanup(chip); + return dev_err_probe(dev, ret, "failed to register MTD device\n"); + } + + return 0; +} + +static int loongson_nand_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct loongson_nand_data *data; + struct loongson_nand_host *host; + struct resource *res; + int ret; + + data = of_device_get_match_data(dev); + if (!data) + return -ENODEV; + + host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + host->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(host->reg_base)) + return PTR_ERR(host->reg_base); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand-dma"); + if (!res) + return dev_err_probe(dev, -EINVAL, "Missing 'nand-dma' in reg-names property\n"); + + host->dma_base = dma_map_resource(dev, res->start, resource_size(res), + DMA_BIDIRECTIONAL, 0); + if (dma_mapping_error(dev, host->dma_base)) + return -ENXIO; + + host->dev = dev; + host->data = data; + host->controller.ops = &loongson_nand_controller_ops; + + nand_controller_init(&host->controller); + + ret = loongson_nand_controller_init(host); + if (ret) + goto err; + + ret = loongson_nand_chip_init(host); + if (ret) + goto err; + + platform_set_drvdata(pdev, host); + + return 0; +err: + loongson_nand_controller_cleanup(host); + + return ret; +} + +static void loongson_nand_remove(struct platform_device *pdev) +{ + struct loongson_nand_host *host = platform_get_drvdata(pdev); + struct nand_chip *chip = &host->chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + loongson_nand_controller_cleanup(host); +} + +static const struct loongson_nand_data ls1b_nand_data = { + .max_id_cycle = 5, + .status_field = GENMASK(15, 8), + .hold_cycle = 0x2, + .wait_cycle = 0xc, + .dma_bits = 32, + .set_addr = ls1b_nand_set_addr, +}; + +static const struct loongson_nand_data ls1c_nand_data = { + .max_id_cycle = 6, + .id_cycle_field = GENMASK(14, 12), + .status_field = GENMASK(23, 16), + .op_scope_field = GENMASK(29, 16), + .hold_cycle = 0x2, + .wait_cycle = 0xc, + .dma_bits = 32, + .set_addr = ls1c_nand_set_addr, +}; + +static const struct loongson_nand_data ls2k0500_nand_data = { + .max_id_cycle = 6, + .id_cycle_field = GENMASK(14, 12), + .status_field = GENMASK(23, 16), + .op_scope_field = GENMASK(29, 16), + .hold_cycle = 0x4, + .wait_cycle = 0x12, + .dma_bits = 64, + .set_addr = ls1c_nand_set_addr, +}; + +static const struct loongson_nand_data ls2k1000_nand_data = { + .max_id_cycle = 6, + .id_cycle_field = GENMASK(14, 12), + .status_field = GENMASK(23, 16), + .op_scope_field = GENMASK(29, 16), + .hold_cycle = 0x4, + .wait_cycle = 0x12, + .nand_cs = 0x2, + .dma_bits = 64, + .dma_config = ls2k1000_nand_apbdma_config, + .set_addr = ls1c_nand_set_addr, +}; + +static const struct of_device_id loongson_nand_match[] = { + { + .compatible = "loongson,ls1b-nand-controller", + .data = &ls1b_nand_data, + }, + { + .compatible = "loongson,ls1c-nand-controller", + .data = &ls1c_nand_data, + }, + { + .compatible = "loongson,ls2k0500-nand-controller", + .data = &ls2k0500_nand_data, + }, + { + .compatible = "loongson,ls2k1000-nand-controller", + .data = &ls2k1000_nand_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, loongson_nand_match); + +static struct platform_driver loongson_nand_driver = { + .probe = loongson_nand_probe, + .remove = loongson_nand_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = loongson_nand_match, + }, +}; + +module_platform_driver(loongson_nand_driver); + +MODULE_AUTHOR("Keguang Zhang <keguang.zhang@gmail.com>"); +MODULE_AUTHOR("Binbin Zhou <zhoubinbin@loongson.cn>"); +MODULE_DESCRIPTION("Loongson NAND Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/raw/loongson1-nand-controller.c b/drivers/mtd/nand/raw/loongson1-nand-controller.c deleted file mode 100644 index ef8e4f9ce287..000000000000 --- a/drivers/mtd/nand/raw/loongson1-nand-controller.c +++ /dev/null @@ -1,836 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * NAND Controller Driver for Loongson-1 SoC - * - * Copyright (C) 2015-2025 Keguang Zhang <keguang.zhang@gmail.com> - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/dmaengine.h> -#include <linux/dma-mapping.h> -#include <linux/iopoll.h> -#include <linux/mtd/mtd.h> -#include <linux/mtd/rawnand.h> -#include <linux/of.h> -#include <linux/platform_device.h> -#include <linux/regmap.h> -#include <linux/sizes.h> - -/* Loongson-1 NAND Controller Registers */ -#define LS1X_NAND_CMD 0x0 -#define LS1X_NAND_ADDR1 0x4 -#define LS1X_NAND_ADDR2 0x8 -#define LS1X_NAND_TIMING 0xc -#define LS1X_NAND_IDL 0x10 -#define LS1X_NAND_IDH_STATUS 0x14 -#define LS1X_NAND_PARAM 0x18 -#define LS1X_NAND_OP_NUM 0x1c - -/* NAND Command Register Bits */ -#define LS1X_NAND_CMD_OP_DONE BIT(10) -#define LS1X_NAND_CMD_OP_SPARE BIT(9) -#define LS1X_NAND_CMD_OP_MAIN BIT(8) -#define LS1X_NAND_CMD_STATUS BIT(7) -#define LS1X_NAND_CMD_RESET BIT(6) -#define LS1X_NAND_CMD_READID BIT(5) -#define LS1X_NAND_CMD_BLOCKS_ERASE BIT(4) -#define LS1X_NAND_CMD_ERASE BIT(3) -#define LS1X_NAND_CMD_WRITE BIT(2) -#define LS1X_NAND_CMD_READ BIT(1) -#define LS1X_NAND_CMD_VALID BIT(0) - -#define LS1X_NAND_WAIT_CYCLE_MASK GENMASK(7, 0) -#define LS1X_NAND_HOLD_CYCLE_MASK GENMASK(15, 8) -#define LS1X_NAND_CELL_SIZE_MASK GENMASK(11, 8) - -#define LS1X_NAND_COL_ADDR_CYC 2U -#define LS1X_NAND_MAX_ADDR_CYC 5U - -#define BITS_PER_WORD (4 * BITS_PER_BYTE) - -struct ls1x_nand_host; - -struct ls1x_nand_op { - char addrs[LS1X_NAND_MAX_ADDR_CYC]; - unsigned int naddrs; - unsigned int addrs_offset; - unsigned int aligned_offset; - unsigned int cmd_reg; - unsigned int row_start; - unsigned int rdy_timeout_ms; - unsigned int orig_len; - bool is_readid; - bool is_erase; - bool is_write; - bool is_read; - bool is_change_column; - size_t len; - char *buf; -}; - -struct ls1x_nand_data { - unsigned int status_field; - unsigned int op_scope_field; - unsigned int hold_cycle; - unsigned int wait_cycle; - void (*set_addr)(struct ls1x_nand_host *host, struct ls1x_nand_op *op); -}; - -struct ls1x_nand_host { - struct device *dev; - struct nand_chip chip; - struct nand_controller controller; - const struct ls1x_nand_data *data; - void __iomem *reg_base; - struct regmap *regmap; - /* DMA Engine stuff */ - dma_addr_t dma_base; - struct dma_chan *dma_chan; - dma_cookie_t dma_cookie; - struct completion dma_complete; -}; - -static const struct regmap_config ls1x_nand_regmap_config = { - .reg_bits = 32, - .val_bits = 32, - .reg_stride = 4, -}; - -static int ls1x_nand_op_cmd_mapping(struct nand_chip *chip, struct ls1x_nand_op *op, u8 opcode) -{ - struct ls1x_nand_host *host = nand_get_controller_data(chip); - - op->row_start = chip->page_shift + 1; - - /* The controller abstracts the following NAND operations. */ - switch (opcode) { - case NAND_CMD_STATUS: - op->cmd_reg = LS1X_NAND_CMD_STATUS; - break; - case NAND_CMD_RESET: - op->cmd_reg = LS1X_NAND_CMD_RESET; - break; - case NAND_CMD_READID: - op->is_readid = true; - op->cmd_reg = LS1X_NAND_CMD_READID; - break; - case NAND_CMD_ERASE1: - op->is_erase = true; - op->addrs_offset = LS1X_NAND_COL_ADDR_CYC; - break; - case NAND_CMD_ERASE2: - if (!op->is_erase) - return -EOPNOTSUPP; - /* During erasing, row_start differs from the default value. */ - op->row_start = chip->page_shift; - op->cmd_reg = LS1X_NAND_CMD_ERASE; - break; - case NAND_CMD_SEQIN: - op->is_write = true; - break; - case NAND_CMD_PAGEPROG: - if (!op->is_write) - return -EOPNOTSUPP; - op->cmd_reg = LS1X_NAND_CMD_WRITE; - break; - case NAND_CMD_READ0: - op->is_read = true; - break; - case NAND_CMD_READSTART: - if (!op->is_read) - return -EOPNOTSUPP; - op->cmd_reg = LS1X_NAND_CMD_READ; - break; - case NAND_CMD_RNDOUT: - op->is_change_column = true; - break; - case NAND_CMD_RNDOUTSTART: - if (!op->is_change_column) - return -EOPNOTSUPP; - op->cmd_reg = LS1X_NAND_CMD_READ; - break; - default: - dev_dbg(host->dev, "unsupported opcode: %u\n", opcode); - return -EOPNOTSUPP; - } - - return 0; -} - -static int ls1x_nand_parse_instructions(struct nand_chip *chip, - const struct nand_subop *subop, struct ls1x_nand_op *op) -{ - unsigned int op_id; - int ret; - - for (op_id = 0; op_id < subop->ninstrs; op_id++) { - const struct nand_op_instr *instr = &subop->instrs[op_id]; - unsigned int offset, naddrs; - const u8 *addrs; - - switch (instr->type) { - case NAND_OP_CMD_INSTR: - ret = ls1x_nand_op_cmd_mapping(chip, op, instr->ctx.cmd.opcode); - if (ret < 0) - return ret; - - break; - case NAND_OP_ADDR_INSTR: - naddrs = nand_subop_get_num_addr_cyc(subop, op_id); - if (naddrs > LS1X_NAND_MAX_ADDR_CYC) - return -EOPNOTSUPP; - op->naddrs = naddrs; - offset = nand_subop_get_addr_start_off(subop, op_id); - addrs = &instr->ctx.addr.addrs[offset]; - memcpy(op->addrs + op->addrs_offset, addrs, naddrs); - break; - case NAND_OP_DATA_IN_INSTR: - case NAND_OP_DATA_OUT_INSTR: - offset = nand_subop_get_data_start_off(subop, op_id); - op->orig_len = nand_subop_get_data_len(subop, op_id); - if (instr->type == NAND_OP_DATA_IN_INSTR) - op->buf = instr->ctx.data.buf.in + offset; - else if (instr->type == NAND_OP_DATA_OUT_INSTR) - op->buf = (void *)instr->ctx.data.buf.out + offset; - - break; - case NAND_OP_WAITRDY_INSTR: - op->rdy_timeout_ms = instr->ctx.waitrdy.timeout_ms; - break; - default: - break; - } - } - - return 0; -} - -static void ls1b_nand_set_addr(struct ls1x_nand_host *host, struct ls1x_nand_op *op) -{ - struct nand_chip *chip = &host->chip; - int i; - - for (i = 0; i < LS1X_NAND_MAX_ADDR_CYC; i++) { - int shift, mask, val; - - if (i < LS1X_NAND_COL_ADDR_CYC) { - shift = i * BITS_PER_BYTE; - mask = (u32)0xff << shift; - mask &= GENMASK(chip->page_shift, 0); - val = (u32)op->addrs[i] << shift; - regmap_update_bits(host->regmap, LS1X_NAND_ADDR1, mask, val); - } else if (!op->is_change_column) { - shift = op->row_start + (i - LS1X_NAND_COL_ADDR_CYC) * BITS_PER_BYTE; - mask = (u32)0xff << shift; - val = (u32)op->addrs[i] << shift; - regmap_update_bits(host->regmap, LS1X_NAND_ADDR1, mask, val); - - if (i == 4) { - mask = (u32)0xff >> (BITS_PER_WORD - shift); - val = (u32)op->addrs[i] >> (BITS_PER_WORD - shift); - regmap_update_bits(host->regmap, LS1X_NAND_ADDR2, mask, val); - } - } - } -} - -static void ls1c_nand_set_addr(struct ls1x_nand_host *host, struct ls1x_nand_op *op) -{ - int i; - - for (i = 0; i < LS1X_NAND_MAX_ADDR_CYC; i++) { - int shift, mask, val; - - if (i < LS1X_NAND_COL_ADDR_CYC) { - shift = i * BITS_PER_BYTE; - mask = (u32)0xff << shift; - val = (u32)op->addrs[i] << shift; - regmap_update_bits(host->regmap, LS1X_NAND_ADDR1, mask, val); - } else if (!op->is_change_column) { - shift = (i - LS1X_NAND_COL_ADDR_CYC) * BITS_PER_BYTE; - mask = (u32)0xff << shift; - val = (u32)op->addrs[i] << shift; - regmap_update_bits(host->regmap, LS1X_NAND_ADDR2, mask, val); - } - } -} - -static void ls1x_nand_trigger_op(struct ls1x_nand_host *host, struct ls1x_nand_op *op) -{ - struct nand_chip *chip = &host->chip; - struct mtd_info *mtd = nand_to_mtd(chip); - int col0 = op->addrs[0]; - short col; - - if (!IS_ALIGNED(col0, chip->buf_align)) { - col0 = ALIGN_DOWN(op->addrs[0], chip->buf_align); - op->aligned_offset = op->addrs[0] - col0; - op->addrs[0] = col0; - } - - if (host->data->set_addr) - host->data->set_addr(host, op); - - /* set operation length */ - if (op->is_write || op->is_read || op->is_change_column) - op->len = ALIGN(op->orig_len + op->aligned_offset, chip->buf_align); - else if (op->is_erase) - op->len = 1; - else - op->len = op->orig_len; - - writel(op->len, host->reg_base + LS1X_NAND_OP_NUM); - - /* set operation area and scope */ - col = op->addrs[1] << BITS_PER_BYTE | op->addrs[0]; - if (op->orig_len && !op->is_readid) { - unsigned int op_scope = 0; - - if (col < mtd->writesize) { - op->cmd_reg |= LS1X_NAND_CMD_OP_MAIN; - op_scope = mtd->writesize; - } - - op->cmd_reg |= LS1X_NAND_CMD_OP_SPARE; - op_scope += mtd->oobsize; - - op_scope <<= __ffs(host->data->op_scope_field); - regmap_update_bits(host->regmap, LS1X_NAND_PARAM, - host->data->op_scope_field, op_scope); - } - - /* set command */ - writel(op->cmd_reg, host->reg_base + LS1X_NAND_CMD); - - /* trigger operation */ - regmap_write_bits(host->regmap, LS1X_NAND_CMD, LS1X_NAND_CMD_VALID, LS1X_NAND_CMD_VALID); -} - -static int ls1x_nand_wait_for_op_done(struct ls1x_nand_host *host, struct ls1x_nand_op *op) -{ - unsigned int val; - int ret = 0; - - if (op->rdy_timeout_ms) { - ret = regmap_read_poll_timeout(host->regmap, LS1X_NAND_CMD, - val, val & LS1X_NAND_CMD_OP_DONE, - 0, op->rdy_timeout_ms * MSEC_PER_SEC); - if (ret) - dev_err(host->dev, "operation failed\n"); - } - - return ret; -} - -static void ls1x_nand_dma_callback(void *data) -{ - struct ls1x_nand_host *host = (struct ls1x_nand_host *)data; - struct dma_chan *chan = host->dma_chan; - struct device *dev = chan->device->dev; - enum dma_status status; - - status = dmaengine_tx_status(chan, host->dma_cookie, NULL); - if (likely(status == DMA_COMPLETE)) { - dev_dbg(dev, "DMA complete with cookie=%d\n", host->dma_cookie); - complete(&host->dma_complete); - } else { - dev_err(dev, "DMA error with cookie=%d\n", host->dma_cookie); - } -} - -static int ls1x_nand_dma_transfer(struct ls1x_nand_host *host, struct ls1x_nand_op *op) -{ - struct nand_chip *chip = &host->chip; - struct dma_chan *chan = host->dma_chan; - struct device *dev = chan->device->dev; - struct dma_async_tx_descriptor *desc; - enum dma_data_direction data_dir = op->is_write ? DMA_TO_DEVICE : DMA_FROM_DEVICE; - enum dma_transfer_direction xfer_dir = op->is_write ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; - void *buf = op->buf; - char *dma_buf = NULL; - dma_addr_t dma_addr; - int ret; - - if (IS_ALIGNED((uintptr_t)buf, chip->buf_align) && - IS_ALIGNED(op->orig_len, chip->buf_align)) { - dma_addr = dma_map_single(dev, buf, op->orig_len, data_dir); - if (dma_mapping_error(dev, dma_addr)) { - dev_err(dev, "failed to map DMA buffer\n"); - return -ENXIO; - } - } else if (!op->is_write) { - dma_buf = dma_alloc_coherent(dev, op->len, &dma_addr, GFP_KERNEL); - if (!dma_buf) - return -ENOMEM; - } else { - dev_err(dev, "subpage writing not supported\n"); - return -EOPNOTSUPP; - } - - desc = dmaengine_prep_slave_single(chan, dma_addr, op->len, xfer_dir, DMA_PREP_INTERRUPT); - if (!desc) { - dev_err(dev, "failed to prepare DMA descriptor\n"); - ret = -ENOMEM; - goto err; - } - desc->callback = ls1x_nand_dma_callback; - desc->callback_param = host; - - host->dma_cookie = dmaengine_submit(desc); - ret = dma_submit_error(host->dma_cookie); - if (ret) { - dev_err(dev, "failed to submit DMA descriptor\n"); - goto err; - } - - dev_dbg(dev, "issue DMA with cookie=%d\n", host->dma_cookie); - dma_async_issue_pending(chan); - - if (!wait_for_completion_timeout(&host->dma_complete, msecs_to_jiffies(1000))) { - dmaengine_terminate_sync(chan); - reinit_completion(&host->dma_complete); - ret = -ETIMEDOUT; - goto err; - } - - if (dma_buf) - memcpy(buf, dma_buf + op->aligned_offset, op->orig_len); -err: - if (dma_buf) - dma_free_coherent(dev, op->len, dma_buf, dma_addr); - else - dma_unmap_single(dev, dma_addr, op->orig_len, data_dir); - - return ret; -} - -static int ls1x_nand_data_type_exec(struct nand_chip *chip, const struct nand_subop *subop) -{ - struct ls1x_nand_host *host = nand_get_controller_data(chip); - struct ls1x_nand_op op = {}; - int ret; - - ret = ls1x_nand_parse_instructions(chip, subop, &op); - if (ret) - return ret; - - ls1x_nand_trigger_op(host, &op); - - ret = ls1x_nand_dma_transfer(host, &op); - if (ret) - return ret; - - return ls1x_nand_wait_for_op_done(host, &op); -} - -static int ls1x_nand_misc_type_exec(struct nand_chip *chip, - const struct nand_subop *subop, struct ls1x_nand_op *op) -{ - struct ls1x_nand_host *host = nand_get_controller_data(chip); - int ret; - - ret = ls1x_nand_parse_instructions(chip, subop, op); - if (ret) - return ret; - - ls1x_nand_trigger_op(host, op); - - return ls1x_nand_wait_for_op_done(host, op); -} - -static int ls1x_nand_zerolen_type_exec(struct nand_chip *chip, const struct nand_subop *subop) -{ - struct ls1x_nand_op op = {}; - - return ls1x_nand_misc_type_exec(chip, subop, &op); -} - -static int ls1x_nand_read_id_type_exec(struct nand_chip *chip, const struct nand_subop *subop) -{ - struct ls1x_nand_host *host = nand_get_controller_data(chip); - struct ls1x_nand_op op = {}; - int i, ret; - union { - char ids[5]; - struct { - int idl; - char idh; - }; - } nand_id; - - ret = ls1x_nand_misc_type_exec(chip, subop, &op); - if (ret) - return ret; - - nand_id.idl = readl(host->reg_base + LS1X_NAND_IDL); - nand_id.idh = readb(host->reg_base + LS1X_NAND_IDH_STATUS); - - for (i = 0; i < min(sizeof(nand_id.ids), op.orig_len); i++) - op.buf[i] = nand_id.ids[sizeof(nand_id.ids) - 1 - i]; - - return ret; -} - -static int ls1x_nand_read_status_type_exec(struct nand_chip *chip, const struct nand_subop *subop) -{ - struct ls1x_nand_host *host = nand_get_controller_data(chip); - struct ls1x_nand_op op = {}; - int val, ret; - - ret = ls1x_nand_misc_type_exec(chip, subop, &op); - if (ret) - return ret; - - val = readl(host->reg_base + LS1X_NAND_IDH_STATUS); - val &= ~host->data->status_field; - op.buf[0] = val << ffs(host->data->status_field); - - return ret; -} - -static const struct nand_op_parser ls1x_nand_op_parser = NAND_OP_PARSER( - NAND_OP_PARSER_PATTERN( - ls1x_nand_read_id_type_exec, - NAND_OP_PARSER_PAT_CMD_ELEM(false), - NAND_OP_PARSER_PAT_ADDR_ELEM(false, LS1X_NAND_MAX_ADDR_CYC), - NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 8)), - NAND_OP_PARSER_PATTERN( - ls1x_nand_read_status_type_exec, - NAND_OP_PARSER_PAT_CMD_ELEM(false), - NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 1)), - NAND_OP_PARSER_PATTERN( - ls1x_nand_zerolen_type_exec, - NAND_OP_PARSER_PAT_CMD_ELEM(false), - NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), - NAND_OP_PARSER_PATTERN( - ls1x_nand_zerolen_type_exec, - NAND_OP_PARSER_PAT_CMD_ELEM(false), - NAND_OP_PARSER_PAT_ADDR_ELEM(false, LS1X_NAND_MAX_ADDR_CYC), - NAND_OP_PARSER_PAT_CMD_ELEM(false), - NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), - NAND_OP_PARSER_PATTERN( - ls1x_nand_data_type_exec, - NAND_OP_PARSER_PAT_CMD_ELEM(false), - NAND_OP_PARSER_PAT_ADDR_ELEM(false, LS1X_NAND_MAX_ADDR_CYC), - NAND_OP_PARSER_PAT_CMD_ELEM(false), - NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), - NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 0)), - NAND_OP_PARSER_PATTERN( - ls1x_nand_data_type_exec, - NAND_OP_PARSER_PAT_CMD_ELEM(false), - NAND_OP_PARSER_PAT_ADDR_ELEM(false, LS1X_NAND_MAX_ADDR_CYC), - NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 0), - NAND_OP_PARSER_PAT_CMD_ELEM(false), - NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), - ); - -static int ls1x_nand_is_valid_cmd(u8 opcode) -{ - if (opcode == NAND_CMD_STATUS || opcode == NAND_CMD_RESET || opcode == NAND_CMD_READID) - return 0; - - return -EOPNOTSUPP; -} - -static int ls1x_nand_is_valid_cmd_seq(u8 opcode1, u8 opcode2) -{ - if (opcode1 == NAND_CMD_RNDOUT && opcode2 == NAND_CMD_RNDOUTSTART) - return 0; - - if (opcode1 == NAND_CMD_READ0 && opcode2 == NAND_CMD_READSTART) - return 0; - - if (opcode1 == NAND_CMD_ERASE1 && opcode2 == NAND_CMD_ERASE2) - return 0; - - if (opcode1 == NAND_CMD_SEQIN && opcode2 == NAND_CMD_PAGEPROG) - return 0; - - return -EOPNOTSUPP; -} - -static int ls1x_nand_check_op(struct nand_chip *chip, const struct nand_operation *op) -{ - const struct nand_op_instr *instr1 = NULL, *instr2 = NULL; - int op_id; - - for (op_id = 0; op_id < op->ninstrs; op_id++) { - const struct nand_op_instr *instr = &op->instrs[op_id]; - - if (instr->type == NAND_OP_CMD_INSTR) { - if (!instr1) - instr1 = instr; - else if (!instr2) - instr2 = instr; - else - break; - } - } - - if (!instr1) - return -EOPNOTSUPP; - - if (!instr2) - return ls1x_nand_is_valid_cmd(instr1->ctx.cmd.opcode); - - return ls1x_nand_is_valid_cmd_seq(instr1->ctx.cmd.opcode, instr2->ctx.cmd.opcode); -} - -static int ls1x_nand_exec_op(struct nand_chip *chip, - const struct nand_operation *op, bool check_only) -{ - if (check_only) - return ls1x_nand_check_op(chip, op); - - return nand_op_parser_exec_op(chip, &ls1x_nand_op_parser, op, check_only); -} - -static int ls1x_nand_attach_chip(struct nand_chip *chip) -{ - struct ls1x_nand_host *host = nand_get_controller_data(chip); - u64 chipsize = nanddev_target_size(&chip->base); - int cell_size = 0; - - switch (chipsize) { - case SZ_128M: - cell_size = 0x0; - break; - case SZ_256M: - cell_size = 0x1; - break; - case SZ_512M: - cell_size = 0x2; - break; - case SZ_1G: - cell_size = 0x3; - break; - case SZ_2G: - cell_size = 0x4; - break; - case SZ_4G: - cell_size = 0x5; - break; - case SZ_8G: - cell_size = 0x6; - break; - case SZ_16G: - cell_size = 0x7; - break; - default: - dev_err(host->dev, "unsupported chip size: %llu MB\n", chipsize); - return -EINVAL; - } - - switch (chip->ecc.engine_type) { - case NAND_ECC_ENGINE_TYPE_NONE: - break; - case NAND_ECC_ENGINE_TYPE_SOFT: - break; - default: - return -EINVAL; - } - - /* set cell size */ - regmap_update_bits(host->regmap, LS1X_NAND_PARAM, LS1X_NAND_CELL_SIZE_MASK, - FIELD_PREP(LS1X_NAND_CELL_SIZE_MASK, cell_size)); - - regmap_update_bits(host->regmap, LS1X_NAND_TIMING, LS1X_NAND_HOLD_CYCLE_MASK, - FIELD_PREP(LS1X_NAND_HOLD_CYCLE_MASK, host->data->hold_cycle)); - - regmap_update_bits(host->regmap, LS1X_NAND_TIMING, LS1X_NAND_WAIT_CYCLE_MASK, - FIELD_PREP(LS1X_NAND_WAIT_CYCLE_MASK, host->data->wait_cycle)); - - chip->ecc.read_page_raw = nand_monolithic_read_page_raw; - chip->ecc.write_page_raw = nand_monolithic_write_page_raw; - - return 0; -} - -static const struct nand_controller_ops ls1x_nand_controller_ops = { - .exec_op = ls1x_nand_exec_op, - .attach_chip = ls1x_nand_attach_chip, -}; - -static void ls1x_nand_controller_cleanup(struct ls1x_nand_host *host) -{ - if (host->dma_chan) - dma_release_channel(host->dma_chan); -} - -static int ls1x_nand_controller_init(struct ls1x_nand_host *host) -{ - struct device *dev = host->dev; - struct dma_chan *chan; - struct dma_slave_config cfg = {}; - int ret; - - host->regmap = devm_regmap_init_mmio(dev, host->reg_base, &ls1x_nand_regmap_config); - if (IS_ERR(host->regmap)) - return dev_err_probe(dev, PTR_ERR(host->regmap), "failed to init regmap\n"); - - chan = dma_request_chan(dev, "rxtx"); - if (IS_ERR(chan)) - return dev_err_probe(dev, PTR_ERR(chan), "failed to request DMA channel\n"); - host->dma_chan = chan; - - cfg.src_addr = host->dma_base; - cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - cfg.dst_addr = host->dma_base; - cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; - ret = dmaengine_slave_config(host->dma_chan, &cfg); - if (ret) - return dev_err_probe(dev, ret, "failed to config DMA channel\n"); - - init_completion(&host->dma_complete); - - return 0; -} - -static int ls1x_nand_chip_init(struct ls1x_nand_host *host) -{ - struct device *dev = host->dev; - int nchips = of_get_child_count(dev->of_node); - struct device_node *chip_np; - struct nand_chip *chip = &host->chip; - struct mtd_info *mtd = nand_to_mtd(chip); - int ret; - - if (nchips != 1) - return dev_err_probe(dev, -EINVAL, "Currently one NAND chip supported\n"); - - chip_np = of_get_next_child(dev->of_node, NULL); - if (!chip_np) - return dev_err_probe(dev, -ENODEV, "failed to get child node for NAND chip\n"); - - nand_set_flash_node(chip, chip_np); - of_node_put(chip_np); - if (!mtd->name) - return dev_err_probe(dev, -EINVAL, "Missing MTD label\n"); - - nand_set_controller_data(chip, host); - chip->controller = &host->controller; - chip->options = NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA | NAND_BROKEN_XD; - chip->buf_align = 16; - mtd->dev.parent = dev; - mtd->owner = THIS_MODULE; - - ret = nand_scan(chip, 1); - if (ret) - return dev_err_probe(dev, ret, "failed to scan NAND chip\n"); - - ret = mtd_device_register(mtd, NULL, 0); - if (ret) { - nand_cleanup(chip); - return dev_err_probe(dev, ret, "failed to register MTD device\n"); - } - - return 0; -} - -static int ls1x_nand_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - const struct ls1x_nand_data *data; - struct ls1x_nand_host *host; - struct resource *res; - int ret; - - data = of_device_get_match_data(dev); - if (!data) - return -ENODEV; - - host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); - if (!host) - return -ENOMEM; - - host->reg_base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(host->reg_base)) - return PTR_ERR(host->reg_base); - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand-dma"); - if (!res) - return dev_err_probe(dev, -EINVAL, "Missing 'nand-dma' in reg-names property\n"); - - host->dma_base = dma_map_resource(dev, res->start, resource_size(res), - DMA_BIDIRECTIONAL, 0); - if (dma_mapping_error(dev, host->dma_base)) - return -ENXIO; - - host->dev = dev; - host->data = data; - host->controller.ops = &ls1x_nand_controller_ops; - - nand_controller_init(&host->controller); - - ret = ls1x_nand_controller_init(host); - if (ret) - goto err; - - ret = ls1x_nand_chip_init(host); - if (ret) - goto err; - - platform_set_drvdata(pdev, host); - - return 0; -err: - ls1x_nand_controller_cleanup(host); - - return ret; -} - -static void ls1x_nand_remove(struct platform_device *pdev) -{ - struct ls1x_nand_host *host = platform_get_drvdata(pdev); - struct nand_chip *chip = &host->chip; - int ret; - - ret = mtd_device_unregister(nand_to_mtd(chip)); - WARN_ON(ret); - nand_cleanup(chip); - ls1x_nand_controller_cleanup(host); -} - -static const struct ls1x_nand_data ls1b_nand_data = { - .status_field = GENMASK(15, 8), - .hold_cycle = 0x2, - .wait_cycle = 0xc, - .set_addr = ls1b_nand_set_addr, -}; - -static const struct ls1x_nand_data ls1c_nand_data = { - .status_field = GENMASK(23, 16), - .op_scope_field = GENMASK(29, 16), - .hold_cycle = 0x2, - .wait_cycle = 0xc, - .set_addr = ls1c_nand_set_addr, -}; - -static const struct of_device_id ls1x_nand_match[] = { - { - .compatible = "loongson,ls1b-nand-controller", - .data = &ls1b_nand_data, - }, - { - .compatible = "loongson,ls1c-nand-controller", - .data = &ls1c_nand_data, - }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, ls1x_nand_match); - -static struct platform_driver ls1x_nand_driver = { - .probe = ls1x_nand_probe, - .remove = ls1x_nand_remove, - .driver = { - .name = KBUILD_MODNAME, - .of_match_table = ls1x_nand_match, - }, -}; - -module_platform_driver(ls1x_nand_driver); - -MODULE_AUTHOR("Keguang Zhang <keguang.zhang@gmail.com>"); -MODULE_DESCRIPTION("Loongson-1 NAND Controller Driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index 13e4060bd1b6..c7d9501f646b 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -2784,137 +2784,6 @@ int nand_set_features(struct nand_chip *chip, int addr, } /** - * nand_check_erased_buf - check if a buffer contains (almost) only 0xff data - * @buf: buffer to test - * @len: buffer length - * @bitflips_threshold: maximum number of bitflips - * - * Check if a buffer contains only 0xff, which means the underlying region - * has been erased and is ready to be programmed. - * The bitflips_threshold specify the maximum number of bitflips before - * considering the region is not erased. - * Note: The logic of this function has been extracted from the memweight - * implementation, except that nand_check_erased_buf function exit before - * testing the whole buffer if the number of bitflips exceed the - * bitflips_threshold value. - * - * Returns a positive number of bitflips less than or equal to - * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the - * threshold. - */ -static int nand_check_erased_buf(void *buf, int len, int bitflips_threshold) -{ - const unsigned char *bitmap = buf; - int bitflips = 0; - int weight; - - for (; len && ((uintptr_t)bitmap) % sizeof(long); - len--, bitmap++) { - weight = hweight8(*bitmap); - bitflips += BITS_PER_BYTE - weight; - if (unlikely(bitflips > bitflips_threshold)) - return -EBADMSG; - } - - for (; len >= sizeof(long); - len -= sizeof(long), bitmap += sizeof(long)) { - unsigned long d = *((unsigned long *)bitmap); - if (d == ~0UL) - continue; - weight = hweight_long(d); - bitflips += BITS_PER_LONG - weight; - if (unlikely(bitflips > bitflips_threshold)) - return -EBADMSG; - } - - for (; len > 0; len--, bitmap++) { - weight = hweight8(*bitmap); - bitflips += BITS_PER_BYTE - weight; - if (unlikely(bitflips > bitflips_threshold)) - return -EBADMSG; - } - - return bitflips; -} - -/** - * nand_check_erased_ecc_chunk - check if an ECC chunk contains (almost) only - * 0xff data - * @data: data buffer to test - * @datalen: data length - * @ecc: ECC buffer - * @ecclen: ECC length - * @extraoob: extra OOB buffer - * @extraooblen: extra OOB length - * @bitflips_threshold: maximum number of bitflips - * - * Check if a data buffer and its associated ECC and OOB data contains only - * 0xff pattern, which means the underlying region has been erased and is - * ready to be programmed. - * The bitflips_threshold specify the maximum number of bitflips before - * considering the region as not erased. - * - * Note: - * 1/ ECC algorithms are working on pre-defined block sizes which are usually - * different from the NAND page size. When fixing bitflips, ECC engines will - * report the number of errors per chunk, and the NAND core infrastructure - * expect you to return the maximum number of bitflips for the whole page. - * This is why you should always use this function on a single chunk and - * not on the whole page. After checking each chunk you should update your - * max_bitflips value accordingly. - * 2/ When checking for bitflips in erased pages you should not only check - * the payload data but also their associated ECC data, because a user might - * have programmed almost all bits to 1 but a few. In this case, we - * shouldn't consider the chunk as erased, and checking ECC bytes prevent - * this case. - * 3/ The extraoob argument is optional, and should be used if some of your OOB - * data are protected by the ECC engine. - * It could also be used if you support subpages and want to attach some - * extra OOB data to an ECC chunk. - * - * Returns a positive number of bitflips less than or equal to - * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the - * threshold. In case of success, the passed buffers are filled with 0xff. - */ -int nand_check_erased_ecc_chunk(void *data, int datalen, - void *ecc, int ecclen, - void *extraoob, int extraooblen, - int bitflips_threshold) -{ - int data_bitflips = 0, ecc_bitflips = 0, extraoob_bitflips = 0; - - data_bitflips = nand_check_erased_buf(data, datalen, - bitflips_threshold); - if (data_bitflips < 0) - return data_bitflips; - - bitflips_threshold -= data_bitflips; - - ecc_bitflips = nand_check_erased_buf(ecc, ecclen, bitflips_threshold); - if (ecc_bitflips < 0) - return ecc_bitflips; - - bitflips_threshold -= ecc_bitflips; - - extraoob_bitflips = nand_check_erased_buf(extraoob, extraooblen, - bitflips_threshold); - if (extraoob_bitflips < 0) - return extraoob_bitflips; - - if (data_bitflips) - memset(data, 0xff, datalen); - - if (ecc_bitflips) - memset(ecc, 0xff, ecclen); - - if (extraoob_bitflips) - memset(extraoob, 0xff, extraooblen); - - return data_bitflips + ecc_bitflips + extraoob_bitflips; -} -EXPORT_SYMBOL(nand_check_erased_ecc_chunk); - -/** * nand_read_page_raw_notsupp - dummy read raw page function * @chip: nand chip info structure * @buf: buffer to store read data diff --git a/drivers/mtd/nand/raw/nandsim.c b/drivers/mtd/nand/raw/nandsim.c index df48b7d01d16..84942e7e528f 100644 --- a/drivers/mtd/nand/raw/nandsim.c +++ b/drivers/mtd/nand/raw/nandsim.c @@ -552,9 +552,8 @@ static int __init ns_alloc_device(struct nandsim *ns) err = -EINVAL; goto err_close_filp; } - ns->pages_written = - vzalloc(array_size(sizeof(unsigned long), - BITS_TO_LONGS(ns->geom.pgnum))); + ns->pages_written = vcalloc(BITS_TO_LONGS(ns->geom.pgnum), + sizeof(unsigned long)); if (!ns->pages_written) { NS_ERR("alloc_device: unable to allocate pages written array\n"); err = -ENOMEM; @@ -578,7 +577,7 @@ err_close_filp: return err; } - ns->pages = vmalloc(array_size(sizeof(union ns_mem), ns->geom.pgnum)); + ns->pages = vmalloc_array(ns->geom.pgnum, sizeof(union ns_mem)); if (!ns->pages) { NS_ERR("alloc_device: unable to allocate page array\n"); return -ENOMEM; diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c index b8af3a3533fc..39e297486721 100644 --- a/drivers/mtd/nand/raw/omap2.c +++ b/drivers/mtd/nand/raw/omap2.c @@ -1979,7 +1979,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip) err = rawnand_sw_bch_init(chip); if (err) { dev_err(dev, "Unable to use BCH library\n"); - return err; + goto err_put_elm_dev; } break; @@ -2016,7 +2016,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip) err = rawnand_sw_bch_init(chip); if (err) { dev_err(dev, "unable to use BCH library\n"); - return err; + goto err_put_elm_dev; } break; @@ -2054,7 +2054,8 @@ static int omap_nand_attach_chip(struct nand_chip *chip) break; default: dev_err(dev, "Invalid or unsupported ECC scheme\n"); - return -EINVAL; + err = -EINVAL; + goto err_put_elm_dev; } if (elm_bch_strength >= 0) { @@ -2073,7 +2074,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip) info->nsteps_per_eccpg, chip->ecc.size, chip->ecc.bytes); if (err < 0) - return err; + goto err_put_elm_dev; } /* Check if NAND device's OOB is enough to store ECC signatures */ @@ -2083,10 +2084,24 @@ static int omap_nand_attach_chip(struct nand_chip *chip) dev_err(dev, "Not enough OOB bytes: required = %d, available=%d\n", min_oobbytes, mtd->oobsize); - return -EINVAL; + err = -EINVAL; + goto err_put_elm_dev; } return 0; + +err_put_elm_dev: + put_device(info->elm_dev); + + return err; +} + +static void omap_nand_detach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct omap_nand_info *info = mtd_to_omap(mtd); + + put_device(info->elm_dev); } static void omap_nand_data_in(struct nand_chip *chip, void *buf, @@ -2187,6 +2202,7 @@ static int omap_nand_exec_op(struct nand_chip *chip, static const struct nand_controller_ops omap_nand_controller_ops = { .attach_chip = omap_nand_attach_chip, + .detach_chip = omap_nand_detach_chip, .exec_op = omap_nand_exec_op, }; @@ -2316,6 +2332,5 @@ static struct platform_driver omap_nand_driver = { module_platform_driver(omap_nand_driver); -MODULE_ALIAS("platform:" DRIVER_NAME); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Glue layer for NAND flash on TI OMAP boards"); diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c index 09440ed4652e..11bd90e3f18c 100644 --- a/drivers/mtd/nand/raw/pl35x-nand-controller.c +++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c @@ -1137,7 +1137,7 @@ static int pl35x_nand_probe(struct platform_device *pdev) struct device *smc_dev = pdev->dev.parent; struct amba_device *smc_amba = to_amba_device(smc_dev); struct pl35x_nandc *nfc; - u32 ret; + int ret; nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL); if (!nfc) @@ -1193,6 +1193,5 @@ static struct platform_driver pl35x_nandc_driver = { module_platform_driver(pl35x_nandc_driver); MODULE_AUTHOR("Xilinx, Inc."); -MODULE_ALIAS("platform:" PL35X_NANDC_DRIVER_NAME); MODULE_DESCRIPTION("ARM PL35X NAND controller driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c index c5d7cd8a6cab..9444ba02696d 100644 --- a/drivers/mtd/nand/raw/rockchip-nand-controller.c +++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c @@ -1505,4 +1505,3 @@ module_platform_driver(rk_nfc_driver); MODULE_LICENSE("Dual MIT/GPL"); MODULE_AUTHOR("Yifeng Zhao <yifeng.zhao@rock-chips.com>"); MODULE_DESCRIPTION("Rockchip Nand Flash Controller Driver"); -MODULE_ALIAS("platform:rockchip-nand-controller"); diff --git a/drivers/mtd/nand/raw/s3c2410.c b/drivers/mtd/nand/raw/s3c2410.c deleted file mode 100644 index 229f2e87d56e..000000000000 --- a/drivers/mtd/nand/raw/s3c2410.c +++ /dev/null @@ -1,1230 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright © 2004-2008 Simtec Electronics - * http://armlinux.simtec.co.uk/ - * Ben Dooks <ben@simtec.co.uk> - * - * Samsung S3C2410/S3C2440/S3C2412 NAND driver -*/ - -#define pr_fmt(fmt) "nand-s3c2410: " fmt - -#ifdef CONFIG_MTD_NAND_S3C2410_DEBUG -#define DEBUG -#endif - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <linux/string.h> -#include <linux/io.h> -#include <linux/ioport.h> -#include <linux/platform_device.h> -#include <linux/delay.h> -#include <linux/err.h> -#include <linux/slab.h> -#include <linux/clk.h> -#include <linux/cpufreq.h> -#include <linux/of.h> - -#include <linux/mtd/mtd.h> -#include <linux/mtd/rawnand.h> -#include <linux/mtd/partitions.h> - -#include <linux/platform_data/mtd-nand-s3c2410.h> - -#define S3C2410_NFREG(x) (x) - -#define S3C2410_NFCONF S3C2410_NFREG(0x00) -#define S3C2410_NFCMD S3C2410_NFREG(0x04) -#define S3C2410_NFADDR S3C2410_NFREG(0x08) -#define S3C2410_NFDATA S3C2410_NFREG(0x0C) -#define S3C2410_NFSTAT S3C2410_NFREG(0x10) -#define S3C2410_NFECC S3C2410_NFREG(0x14) -#define S3C2440_NFCONT S3C2410_NFREG(0x04) -#define S3C2440_NFCMD S3C2410_NFREG(0x08) -#define S3C2440_NFADDR S3C2410_NFREG(0x0C) -#define S3C2440_NFDATA S3C2410_NFREG(0x10) -#define S3C2440_NFSTAT S3C2410_NFREG(0x20) -#define S3C2440_NFMECC0 S3C2410_NFREG(0x2C) -#define S3C2412_NFSTAT S3C2410_NFREG(0x28) -#define S3C2412_NFMECC0 S3C2410_NFREG(0x34) -#define S3C2410_NFCONF_EN (1<<15) -#define S3C2410_NFCONF_INITECC (1<<12) -#define S3C2410_NFCONF_nFCE (1<<11) -#define S3C2410_NFCONF_TACLS(x) ((x)<<8) -#define S3C2410_NFCONF_TWRPH0(x) ((x)<<4) -#define S3C2410_NFCONF_TWRPH1(x) ((x)<<0) -#define S3C2410_NFSTAT_BUSY (1<<0) -#define S3C2440_NFCONF_TACLS(x) ((x)<<12) -#define S3C2440_NFCONF_TWRPH0(x) ((x)<<8) -#define S3C2440_NFCONF_TWRPH1(x) ((x)<<4) -#define S3C2440_NFCONT_INITECC (1<<4) -#define S3C2440_NFCONT_nFCE (1<<1) -#define S3C2440_NFCONT_ENABLE (1<<0) -#define S3C2440_NFSTAT_READY (1<<0) -#define S3C2412_NFCONF_NANDBOOT (1<<31) -#define S3C2412_NFCONT_INIT_MAIN_ECC (1<<5) -#define S3C2412_NFCONT_nFCE0 (1<<1) -#define S3C2412_NFSTAT_READY (1<<0) - -/* new oob placement block for use with hardware ecc generation - */ -static int s3c2410_ooblayout_ecc(struct mtd_info *mtd, int section, - struct mtd_oob_region *oobregion) -{ - if (section) - return -ERANGE; - - oobregion->offset = 0; - oobregion->length = 3; - - return 0; -} - -static int s3c2410_ooblayout_free(struct mtd_info *mtd, int section, - struct mtd_oob_region *oobregion) -{ - if (section) - return -ERANGE; - - oobregion->offset = 8; - oobregion->length = 8; - - return 0; -} - -static const struct mtd_ooblayout_ops s3c2410_ooblayout_ops = { - .ecc = s3c2410_ooblayout_ecc, - .free = s3c2410_ooblayout_free, -}; - -/* controller and mtd information */ - -struct s3c2410_nand_info; - -/** - * struct s3c2410_nand_mtd - driver MTD structure - * @chip: The NAND chip information. - * @set: The platform information supplied for this set of NAND chips. - * @info: Link back to the hardware information. -*/ -struct s3c2410_nand_mtd { - struct nand_chip chip; - struct s3c2410_nand_set *set; - struct s3c2410_nand_info *info; -}; - -enum s3c_cpu_type { - TYPE_S3C2410, - TYPE_S3C2412, - TYPE_S3C2440, -}; - -enum s3c_nand_clk_state { - CLOCK_DISABLE = 0, - CLOCK_ENABLE, - CLOCK_SUSPEND, -}; - -/* overview of the s3c2410 nand state */ - -/** - * struct s3c2410_nand_info - NAND controller state. - * @controller: Base controller structure. - * @mtds: An array of MTD instances on this controller. - * @platform: The platform data for this board. - * @device: The platform device we bound to. - * @clk: The clock resource for this controller. - * @regs: The area mapped for the hardware registers. - * @sel_reg: Pointer to the register controlling the NAND selection. - * @sel_bit: The bit in @sel_reg to select the NAND chip. - * @mtd_count: The number of MTDs created from this controller. - * @save_sel: The contents of @sel_reg to be saved over suspend. - * @clk_rate: The clock rate from @clk. - * @clk_state: The current clock state. - * @cpu_type: The exact type of this controller. - */ -struct s3c2410_nand_info { - /* mtd info */ - struct nand_controller controller; - struct s3c2410_nand_mtd *mtds; - struct s3c2410_platform_nand *platform; - - /* device info */ - struct device *device; - struct clk *clk; - void __iomem *regs; - void __iomem *sel_reg; - int sel_bit; - int mtd_count; - unsigned long save_sel; - unsigned long clk_rate; - enum s3c_nand_clk_state clk_state; - - enum s3c_cpu_type cpu_type; -}; - -struct s3c24XX_nand_devtype_data { - enum s3c_cpu_type type; -}; - -static const struct s3c24XX_nand_devtype_data s3c2410_nand_devtype_data = { - .type = TYPE_S3C2410, -}; - -static const struct s3c24XX_nand_devtype_data s3c2412_nand_devtype_data = { - .type = TYPE_S3C2412, -}; - -static const struct s3c24XX_nand_devtype_data s3c2440_nand_devtype_data = { - .type = TYPE_S3C2440, -}; - -/* conversion functions */ - -static struct s3c2410_nand_mtd *s3c2410_nand_mtd_toours(struct mtd_info *mtd) -{ - return container_of(mtd_to_nand(mtd), struct s3c2410_nand_mtd, - chip); -} - -static struct s3c2410_nand_info *s3c2410_nand_mtd_toinfo(struct mtd_info *mtd) -{ - return s3c2410_nand_mtd_toours(mtd)->info; -} - -static struct s3c2410_nand_info *to_nand_info(struct platform_device *dev) -{ - return platform_get_drvdata(dev); -} - -static struct s3c2410_platform_nand *to_nand_plat(struct platform_device *dev) -{ - return dev_get_platdata(&dev->dev); -} - -static inline int allow_clk_suspend(struct s3c2410_nand_info *info) -{ -#ifdef CONFIG_MTD_NAND_S3C2410_CLKSTOP - return 1; -#else - return 0; -#endif -} - -/** - * s3c2410_nand_clk_set_state - Enable, disable or suspend NAND clock. - * @info: The controller instance. - * @new_state: State to which clock should be set. - */ -static void s3c2410_nand_clk_set_state(struct s3c2410_nand_info *info, - enum s3c_nand_clk_state new_state) -{ - if (!allow_clk_suspend(info) && new_state == CLOCK_SUSPEND) - return; - - if (info->clk_state == CLOCK_ENABLE) { - if (new_state != CLOCK_ENABLE) - clk_disable_unprepare(info->clk); - } else { - if (new_state == CLOCK_ENABLE) - clk_prepare_enable(info->clk); - } - - info->clk_state = new_state; -} - -/* timing calculations */ - -#define NS_IN_KHZ 1000000 - -/** - * s3c_nand_calc_rate - calculate timing data. - * @wanted: The cycle time in nanoseconds. - * @clk: The clock rate in kHz. - * @max: The maximum divider value. - * - * Calculate the timing value from the given parameters. - */ -static int s3c_nand_calc_rate(int wanted, unsigned long clk, int max) -{ - int result; - - result = DIV_ROUND_UP((wanted * clk), NS_IN_KHZ); - - pr_debug("result %d from %ld, %d\n", result, clk, wanted); - - if (result > max) { - pr_err("%d ns is too big for current clock rate %ld\n", - wanted, clk); - return -1; - } - - if (result < 1) - result = 1; - - return result; -} - -#define to_ns(ticks, clk) (((ticks) * NS_IN_KHZ) / (unsigned int)(clk)) - -/* controller setup */ - -/** - * s3c2410_nand_setrate - setup controller timing information. - * @info: The controller instance. - * - * Given the information supplied by the platform, calculate and set - * the necessary timing registers in the hardware to generate the - * necessary timing cycles to the hardware. - */ -static int s3c2410_nand_setrate(struct s3c2410_nand_info *info) -{ - struct s3c2410_platform_nand *plat = info->platform; - int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4; - int tacls, twrph0, twrph1; - unsigned long clkrate = clk_get_rate(info->clk); - unsigned long set, cfg, mask; - unsigned long flags; - - /* calculate the timing information for the controller */ - - info->clk_rate = clkrate; - clkrate /= 1000; /* turn clock into kHz for ease of use */ - - if (plat != NULL) { - tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max); - twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8); - twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8); - } else { - /* default timings */ - tacls = tacls_max; - twrph0 = 8; - twrph1 = 8; - } - - if (tacls < 0 || twrph0 < 0 || twrph1 < 0) { - dev_err(info->device, "cannot get suitable timings\n"); - return -EINVAL; - } - - dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n", - tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), - twrph1, to_ns(twrph1, clkrate)); - - switch (info->cpu_type) { - case TYPE_S3C2410: - mask = (S3C2410_NFCONF_TACLS(3) | - S3C2410_NFCONF_TWRPH0(7) | - S3C2410_NFCONF_TWRPH1(7)); - set = S3C2410_NFCONF_EN; - set |= S3C2410_NFCONF_TACLS(tacls - 1); - set |= S3C2410_NFCONF_TWRPH0(twrph0 - 1); - set |= S3C2410_NFCONF_TWRPH1(twrph1 - 1); - break; - - case TYPE_S3C2440: - case TYPE_S3C2412: - mask = (S3C2440_NFCONF_TACLS(tacls_max - 1) | - S3C2440_NFCONF_TWRPH0(7) | - S3C2440_NFCONF_TWRPH1(7)); - - set = S3C2440_NFCONF_TACLS(tacls - 1); - set |= S3C2440_NFCONF_TWRPH0(twrph0 - 1); - set |= S3C2440_NFCONF_TWRPH1(twrph1 - 1); - break; - - default: - BUG(); - } - - local_irq_save(flags); - - cfg = readl(info->regs + S3C2410_NFCONF); - cfg &= ~mask; - cfg |= set; - writel(cfg, info->regs + S3C2410_NFCONF); - - local_irq_restore(flags); - - dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg); - - return 0; -} - -/** - * s3c2410_nand_inithw - basic hardware initialisation - * @info: The hardware state. - * - * Do the basic initialisation of the hardware, using s3c2410_nand_setrate() - * to setup the hardware access speeds and set the controller to be enabled. -*/ -static int s3c2410_nand_inithw(struct s3c2410_nand_info *info) -{ - int ret; - - ret = s3c2410_nand_setrate(info); - if (ret < 0) - return ret; - - switch (info->cpu_type) { - case TYPE_S3C2410: - default: - break; - - case TYPE_S3C2440: - case TYPE_S3C2412: - /* enable the controller and de-assert nFCE */ - - writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT); - } - - return 0; -} - -/** - * s3c2410_nand_select_chip - select the given nand chip - * @this: NAND chip object. - * @chip: The chip number. - * - * This is called by the MTD layer to either select a given chip for the - * @mtd instance, or to indicate that the access has finished and the - * chip can be de-selected. - * - * The routine ensures that the nFCE line is correctly setup, and any - * platform specific selection code is called to route nFCE to the specific - * chip. - */ -static void s3c2410_nand_select_chip(struct nand_chip *this, int chip) -{ - struct s3c2410_nand_info *info; - struct s3c2410_nand_mtd *nmtd; - unsigned long cur; - - nmtd = nand_get_controller_data(this); - info = nmtd->info; - - if (chip != -1) - s3c2410_nand_clk_set_state(info, CLOCK_ENABLE); - - cur = readl(info->sel_reg); - - if (chip == -1) { - cur |= info->sel_bit; - } else { - if (nmtd->set != NULL && chip > nmtd->set->nr_chips) { - dev_err(info->device, "invalid chip %d\n", chip); - return; - } - - if (info->platform != NULL) { - if (info->platform->select_chip != NULL) - (info->platform->select_chip) (nmtd->set, chip); - } - - cur &= ~info->sel_bit; - } - - writel(cur, info->sel_reg); - - if (chip == -1) - s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND); -} - -/* s3c2410_nand_hwcontrol - * - * Issue command and address cycles to the chip -*/ - -static void s3c2410_nand_hwcontrol(struct nand_chip *chip, int cmd, - unsigned int ctrl) -{ - struct mtd_info *mtd = nand_to_mtd(chip); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - - if (cmd == NAND_CMD_NONE) - return; - - if (ctrl & NAND_CLE) - writeb(cmd, info->regs + S3C2410_NFCMD); - else - writeb(cmd, info->regs + S3C2410_NFADDR); -} - -/* command and control functions */ - -static void s3c2440_nand_hwcontrol(struct nand_chip *chip, int cmd, - unsigned int ctrl) -{ - struct mtd_info *mtd = nand_to_mtd(chip); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - - if (cmd == NAND_CMD_NONE) - return; - - if (ctrl & NAND_CLE) - writeb(cmd, info->regs + S3C2440_NFCMD); - else - writeb(cmd, info->regs + S3C2440_NFADDR); -} - -/* s3c2410_nand_devready() - * - * returns 0 if the nand is busy, 1 if it is ready -*/ - -static int s3c2410_nand_devready(struct nand_chip *chip) -{ - struct mtd_info *mtd = nand_to_mtd(chip); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - return readb(info->regs + S3C2410_NFSTAT) & S3C2410_NFSTAT_BUSY; -} - -static int s3c2440_nand_devready(struct nand_chip *chip) -{ - struct mtd_info *mtd = nand_to_mtd(chip); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY; -} - -static int s3c2412_nand_devready(struct nand_chip *chip) -{ - struct mtd_info *mtd = nand_to_mtd(chip); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - return readb(info->regs + S3C2412_NFSTAT) & S3C2412_NFSTAT_READY; -} - -/* ECC handling functions */ - -static int s3c2410_nand_correct_data(struct nand_chip *chip, u_char *dat, - u_char *read_ecc, u_char *calc_ecc) -{ - struct mtd_info *mtd = nand_to_mtd(chip); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - unsigned int diff0, diff1, diff2; - unsigned int bit, byte; - - pr_debug("%s(%p,%p,%p,%p)\n", __func__, mtd, dat, read_ecc, calc_ecc); - - diff0 = read_ecc[0] ^ calc_ecc[0]; - diff1 = read_ecc[1] ^ calc_ecc[1]; - diff2 = read_ecc[2] ^ calc_ecc[2]; - - pr_debug("%s: rd %*phN calc %*phN diff %02x%02x%02x\n", - __func__, 3, read_ecc, 3, calc_ecc, - diff0, diff1, diff2); - - if (diff0 == 0 && diff1 == 0 && diff2 == 0) - return 0; /* ECC is ok */ - - /* sometimes people do not think about using the ECC, so check - * to see if we have an 0xff,0xff,0xff read ECC and then ignore - * the error, on the assumption that this is an un-eccd page. - */ - if (read_ecc[0] == 0xff && read_ecc[1] == 0xff && read_ecc[2] == 0xff - && info->platform->ignore_unset_ecc) - return 0; - - /* Can we correct this ECC (ie, one row and column change). - * Note, this is similar to the 256 error code on smartmedia */ - - if (((diff0 ^ (diff0 >> 1)) & 0x55) == 0x55 && - ((diff1 ^ (diff1 >> 1)) & 0x55) == 0x55 && - ((diff2 ^ (diff2 >> 1)) & 0x55) == 0x55) { - /* calculate the bit position of the error */ - - bit = ((diff2 >> 3) & 1) | - ((diff2 >> 4) & 2) | - ((diff2 >> 5) & 4); - - /* calculate the byte position of the error */ - - byte = ((diff2 << 7) & 0x100) | - ((diff1 << 0) & 0x80) | - ((diff1 << 1) & 0x40) | - ((diff1 << 2) & 0x20) | - ((diff1 << 3) & 0x10) | - ((diff0 >> 4) & 0x08) | - ((diff0 >> 3) & 0x04) | - ((diff0 >> 2) & 0x02) | - ((diff0 >> 1) & 0x01); - - dev_dbg(info->device, "correcting error bit %d, byte %d\n", - bit, byte); - - dat[byte] ^= (1 << bit); - return 1; - } - - /* if there is only one bit difference in the ECC, then - * one of only a row or column parity has changed, which - * means the error is most probably in the ECC itself */ - - diff0 |= (diff1 << 8); - diff0 |= (diff2 << 16); - - /* equal to "(diff0 & ~(1 << __ffs(diff0)))" */ - if ((diff0 & (diff0 - 1)) == 0) - return 1; - - return -1; -} - -/* ECC functions - * - * These allow the s3c2410 and s3c2440 to use the controller's ECC - * generator block to ECC the data as it passes through] -*/ - -static void s3c2410_nand_enable_hwecc(struct nand_chip *chip, int mode) -{ - struct s3c2410_nand_info *info; - unsigned long ctrl; - - info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip)); - ctrl = readl(info->regs + S3C2410_NFCONF); - ctrl |= S3C2410_NFCONF_INITECC; - writel(ctrl, info->regs + S3C2410_NFCONF); -} - -static void s3c2412_nand_enable_hwecc(struct nand_chip *chip, int mode) -{ - struct s3c2410_nand_info *info; - unsigned long ctrl; - - info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip)); - ctrl = readl(info->regs + S3C2440_NFCONT); - writel(ctrl | S3C2412_NFCONT_INIT_MAIN_ECC, - info->regs + S3C2440_NFCONT); -} - -static void s3c2440_nand_enable_hwecc(struct nand_chip *chip, int mode) -{ - struct s3c2410_nand_info *info; - unsigned long ctrl; - - info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip)); - ctrl = readl(info->regs + S3C2440_NFCONT); - writel(ctrl | S3C2440_NFCONT_INITECC, info->regs + S3C2440_NFCONT); -} - -static int s3c2410_nand_calculate_ecc(struct nand_chip *chip, - const u_char *dat, u_char *ecc_code) -{ - struct mtd_info *mtd = nand_to_mtd(chip); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - - ecc_code[0] = readb(info->regs + S3C2410_NFECC + 0); - ecc_code[1] = readb(info->regs + S3C2410_NFECC + 1); - ecc_code[2] = readb(info->regs + S3C2410_NFECC + 2); - - pr_debug("%s: returning ecc %*phN\n", __func__, 3, ecc_code); - - return 0; -} - -static int s3c2412_nand_calculate_ecc(struct nand_chip *chip, - const u_char *dat, u_char *ecc_code) -{ - struct mtd_info *mtd = nand_to_mtd(chip); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - unsigned long ecc = readl(info->regs + S3C2412_NFMECC0); - - ecc_code[0] = ecc; - ecc_code[1] = ecc >> 8; - ecc_code[2] = ecc >> 16; - - pr_debug("%s: returning ecc %*phN\n", __func__, 3, ecc_code); - - return 0; -} - -static int s3c2440_nand_calculate_ecc(struct nand_chip *chip, - const u_char *dat, u_char *ecc_code) -{ - struct mtd_info *mtd = nand_to_mtd(chip); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - unsigned long ecc = readl(info->regs + S3C2440_NFMECC0); - - ecc_code[0] = ecc; - ecc_code[1] = ecc >> 8; - ecc_code[2] = ecc >> 16; - - pr_debug("%s: returning ecc %06lx\n", __func__, ecc & 0xffffff); - - return 0; -} - -/* over-ride the standard functions for a little more speed. We can - * use read/write block to move the data buffers to/from the controller -*/ - -static void s3c2410_nand_read_buf(struct nand_chip *this, u_char *buf, int len) -{ - readsb(this->legacy.IO_ADDR_R, buf, len); -} - -static void s3c2440_nand_read_buf(struct nand_chip *this, u_char *buf, int len) -{ - struct mtd_info *mtd = nand_to_mtd(this); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - - readsl(info->regs + S3C2440_NFDATA, buf, len >> 2); - - /* cleanup if we've got less than a word to do */ - if (len & 3) { - buf += len & ~3; - - for (; len & 3; len--) - *buf++ = readb(info->regs + S3C2440_NFDATA); - } -} - -static void s3c2410_nand_write_buf(struct nand_chip *this, const u_char *buf, - int len) -{ - writesb(this->legacy.IO_ADDR_W, buf, len); -} - -static void s3c2440_nand_write_buf(struct nand_chip *this, const u_char *buf, - int len) -{ - struct mtd_info *mtd = nand_to_mtd(this); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - - writesl(info->regs + S3C2440_NFDATA, buf, len >> 2); - - /* cleanup any fractional write */ - if (len & 3) { - buf += len & ~3; - - for (; len & 3; len--, buf++) - writeb(*buf, info->regs + S3C2440_NFDATA); - } -} - -/* device management functions */ - -static void s3c24xx_nand_remove(struct platform_device *pdev) -{ - struct s3c2410_nand_info *info = to_nand_info(pdev); - - if (info == NULL) - return; - - /* Release all our mtds and their partitions, then go through - * freeing the resources used - */ - - if (info->mtds != NULL) { - struct s3c2410_nand_mtd *ptr = info->mtds; - int mtdno; - - for (mtdno = 0; mtdno < info->mtd_count; mtdno++, ptr++) { - pr_debug("releasing mtd %d (%p)\n", mtdno, ptr); - WARN_ON(mtd_device_unregister(nand_to_mtd(&ptr->chip))); - nand_cleanup(&ptr->chip); - } - } - - /* free the common resources */ - - if (!IS_ERR(info->clk)) - s3c2410_nand_clk_set_state(info, CLOCK_DISABLE); -} - -static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info, - struct s3c2410_nand_mtd *mtd, - struct s3c2410_nand_set *set) -{ - if (set) { - struct mtd_info *mtdinfo = nand_to_mtd(&mtd->chip); - - mtdinfo->name = set->name; - - return mtd_device_register(mtdinfo, set->partitions, - set->nr_partitions); - } - - return -ENODEV; -} - -static int s3c2410_nand_setup_interface(struct nand_chip *chip, int csline, - const struct nand_interface_config *conf) -{ - struct mtd_info *mtd = nand_to_mtd(chip); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - struct s3c2410_platform_nand *pdata = info->platform; - const struct nand_sdr_timings *timings; - int tacls; - - timings = nand_get_sdr_timings(conf); - if (IS_ERR(timings)) - return -ENOTSUPP; - - tacls = timings->tCLS_min - timings->tWP_min; - if (tacls < 0) - tacls = 0; - - pdata->tacls = DIV_ROUND_UP(tacls, 1000); - pdata->twrph0 = DIV_ROUND_UP(timings->tWP_min, 1000); - pdata->twrph1 = DIV_ROUND_UP(timings->tCLH_min, 1000); - - return s3c2410_nand_setrate(info); -} - -/** - * s3c2410_nand_init_chip - initialise a single instance of an chip - * @info: The base NAND controller the chip is on. - * @nmtd: The new controller MTD instance to fill in. - * @set: The information passed from the board specific platform data. - * - * Initialise the given @nmtd from the information in @info and @set. This - * readies the structure for use with the MTD layer functions by ensuring - * all pointers are setup and the necessary control routines selected. - */ -static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, - struct s3c2410_nand_mtd *nmtd, - struct s3c2410_nand_set *set) -{ - struct device_node *np = info->device->of_node; - struct nand_chip *chip = &nmtd->chip; - void __iomem *regs = info->regs; - - nand_set_flash_node(chip, set->of_node); - - chip->legacy.write_buf = s3c2410_nand_write_buf; - chip->legacy.read_buf = s3c2410_nand_read_buf; - chip->legacy.select_chip = s3c2410_nand_select_chip; - chip->legacy.chip_delay = 50; - nand_set_controller_data(chip, nmtd); - chip->options = set->options; - chip->controller = &info->controller; - - /* - * let's keep behavior unchanged for legacy boards booting via pdata and - * auto-detect timings only when booting with a device tree. - */ - if (!np) - chip->options |= NAND_KEEP_TIMINGS; - - switch (info->cpu_type) { - case TYPE_S3C2410: - chip->legacy.IO_ADDR_W = regs + S3C2410_NFDATA; - info->sel_reg = regs + S3C2410_NFCONF; - info->sel_bit = S3C2410_NFCONF_nFCE; - chip->legacy.cmd_ctrl = s3c2410_nand_hwcontrol; - chip->legacy.dev_ready = s3c2410_nand_devready; - break; - - case TYPE_S3C2440: - chip->legacy.IO_ADDR_W = regs + S3C2440_NFDATA; - info->sel_reg = regs + S3C2440_NFCONT; - info->sel_bit = S3C2440_NFCONT_nFCE; - chip->legacy.cmd_ctrl = s3c2440_nand_hwcontrol; - chip->legacy.dev_ready = s3c2440_nand_devready; - chip->legacy.read_buf = s3c2440_nand_read_buf; - chip->legacy.write_buf = s3c2440_nand_write_buf; - break; - - case TYPE_S3C2412: - chip->legacy.IO_ADDR_W = regs + S3C2440_NFDATA; - info->sel_reg = regs + S3C2440_NFCONT; - info->sel_bit = S3C2412_NFCONT_nFCE0; - chip->legacy.cmd_ctrl = s3c2440_nand_hwcontrol; - chip->legacy.dev_ready = s3c2412_nand_devready; - - if (readl(regs + S3C2410_NFCONF) & S3C2412_NFCONF_NANDBOOT) - dev_info(info->device, "System booted from NAND\n"); - - break; - } - - chip->legacy.IO_ADDR_R = chip->legacy.IO_ADDR_W; - - nmtd->info = info; - nmtd->set = set; - - chip->ecc.engine_type = info->platform->engine_type; - - /* - * If you use u-boot BBT creation code, specifying this flag will - * let the kernel fish out the BBT from the NAND. - */ - if (set->flash_bbt) - chip->bbt_options |= NAND_BBT_USE_FLASH; -} - -/** - * s3c2410_nand_attach_chip - Init the ECC engine after NAND scan - * @chip: The NAND chip - * - * This hook is called by the core after the identification of the NAND chip, - * once the relevant per-chip information is up to date.. This call ensure that - * we update the internal state accordingly. - * - * The internal state is currently limited to the ECC state information. -*/ -static int s3c2410_nand_attach_chip(struct nand_chip *chip) -{ - struct mtd_info *mtd = nand_to_mtd(chip); - struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - - switch (chip->ecc.engine_type) { - - case NAND_ECC_ENGINE_TYPE_NONE: - dev_info(info->device, "ECC disabled\n"); - break; - - case NAND_ECC_ENGINE_TYPE_SOFT: - /* - * This driver expects Hamming based ECC when engine_type is set - * to NAND_ECC_ENGINE_TYPE_SOFT. Force ecc.algo to - * NAND_ECC_ALGO_HAMMING to avoid adding an extra ecc_algo field - * to s3c2410_platform_nand. - */ - chip->ecc.algo = NAND_ECC_ALGO_HAMMING; - dev_info(info->device, "soft ECC\n"); - break; - - case NAND_ECC_ENGINE_TYPE_ON_HOST: - chip->ecc.calculate = s3c2410_nand_calculate_ecc; - chip->ecc.correct = s3c2410_nand_correct_data; - chip->ecc.strength = 1; - - switch (info->cpu_type) { - case TYPE_S3C2410: - chip->ecc.hwctl = s3c2410_nand_enable_hwecc; - chip->ecc.calculate = s3c2410_nand_calculate_ecc; - break; - - case TYPE_S3C2412: - chip->ecc.hwctl = s3c2412_nand_enable_hwecc; - chip->ecc.calculate = s3c2412_nand_calculate_ecc; - break; - - case TYPE_S3C2440: - chip->ecc.hwctl = s3c2440_nand_enable_hwecc; - chip->ecc.calculate = s3c2440_nand_calculate_ecc; - break; - } - - dev_dbg(info->device, "chip %p => page shift %d\n", - chip, chip->page_shift); - - /* change the behaviour depending on whether we are using - * the large or small page nand device */ - if (chip->page_shift > 10) { - chip->ecc.size = 256; - chip->ecc.bytes = 3; - } else { - chip->ecc.size = 512; - chip->ecc.bytes = 3; - mtd_set_ooblayout(nand_to_mtd(chip), - &s3c2410_ooblayout_ops); - } - - dev_info(info->device, "hardware ECC\n"); - break; - - default: - dev_err(info->device, "invalid ECC mode!\n"); - return -EINVAL; - } - - if (chip->bbt_options & NAND_BBT_USE_FLASH) - chip->options |= NAND_SKIP_BBTSCAN; - - return 0; -} - -static const struct nand_controller_ops s3c24xx_nand_controller_ops = { - .attach_chip = s3c2410_nand_attach_chip, - .setup_interface = s3c2410_nand_setup_interface, -}; - -static const struct of_device_id s3c24xx_nand_dt_ids[] = { - { - .compatible = "samsung,s3c2410-nand", - .data = &s3c2410_nand_devtype_data, - }, { - /* also compatible with s3c6400 */ - .compatible = "samsung,s3c2412-nand", - .data = &s3c2412_nand_devtype_data, - }, { - .compatible = "samsung,s3c2440-nand", - .data = &s3c2440_nand_devtype_data, - }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, s3c24xx_nand_dt_ids); - -static int s3c24xx_nand_probe_dt(struct platform_device *pdev) -{ - const struct s3c24XX_nand_devtype_data *devtype_data; - struct s3c2410_platform_nand *pdata; - struct s3c2410_nand_info *info = platform_get_drvdata(pdev); - struct device_node *np = pdev->dev.of_node, *child; - struct s3c2410_nand_set *sets; - - devtype_data = of_device_get_match_data(&pdev->dev); - if (!devtype_data) - return -ENODEV; - - info->cpu_type = devtype_data->type; - - pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - return -ENOMEM; - - pdev->dev.platform_data = pdata; - - pdata->nr_sets = of_get_child_count(np); - if (!pdata->nr_sets) - return 0; - - sets = devm_kcalloc(&pdev->dev, pdata->nr_sets, sizeof(*sets), - GFP_KERNEL); - if (!sets) - return -ENOMEM; - - pdata->sets = sets; - - for_each_available_child_of_node(np, child) { - sets->name = (char *)child->name; - sets->of_node = child; - sets->nr_chips = 1; - - of_node_get(child); - - sets++; - } - - return 0; -} - -static int s3c24xx_nand_probe_pdata(struct platform_device *pdev) -{ - struct s3c2410_nand_info *info = platform_get_drvdata(pdev); - - info->cpu_type = platform_get_device_id(pdev)->driver_data; - - return 0; -} - -/* s3c24xx_nand_probe - * - * called by device layer when it finds a device matching - * one our driver can handled. This code checks to see if - * it can allocate all necessary resources then calls the - * nand layer to look for devices -*/ -static int s3c24xx_nand_probe(struct platform_device *pdev) -{ - struct s3c2410_platform_nand *plat; - struct s3c2410_nand_info *info; - struct s3c2410_nand_mtd *nmtd; - struct s3c2410_nand_set *sets; - struct resource *res; - int err = 0; - int size; - int nr_sets; - int setno; - - info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); - if (info == NULL) { - err = -ENOMEM; - goto exit_error; - } - - platform_set_drvdata(pdev, info); - - nand_controller_init(&info->controller); - info->controller.ops = &s3c24xx_nand_controller_ops; - - /* get the clock source and enable it */ - - info->clk = devm_clk_get(&pdev->dev, "nand"); - if (IS_ERR(info->clk)) { - dev_err(&pdev->dev, "failed to get clock\n"); - err = -ENOENT; - goto exit_error; - } - - s3c2410_nand_clk_set_state(info, CLOCK_ENABLE); - - if (pdev->dev.of_node) - err = s3c24xx_nand_probe_dt(pdev); - else - err = s3c24xx_nand_probe_pdata(pdev); - - if (err) - goto exit_error; - - plat = to_nand_plat(pdev); - - /* allocate and map the resource */ - - /* currently we assume we have the one resource */ - res = pdev->resource; - size = resource_size(res); - - info->device = &pdev->dev; - info->platform = plat; - - info->regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(info->regs)) { - err = PTR_ERR(info->regs); - goto exit_error; - } - - dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs); - - if (!plat->sets || plat->nr_sets < 1) { - err = -EINVAL; - goto exit_error; - } - - sets = plat->sets; - nr_sets = plat->nr_sets; - - info->mtd_count = nr_sets; - - /* allocate our information */ - - size = nr_sets * sizeof(*info->mtds); - info->mtds = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); - if (info->mtds == NULL) { - err = -ENOMEM; - goto exit_error; - } - - /* initialise all possible chips */ - - nmtd = info->mtds; - - for (setno = 0; setno < nr_sets; setno++, nmtd++, sets++) { - struct mtd_info *mtd = nand_to_mtd(&nmtd->chip); - - pr_debug("initialising set %d (%p, info %p)\n", - setno, nmtd, info); - - mtd->dev.parent = &pdev->dev; - s3c2410_nand_init_chip(info, nmtd, sets); - - err = nand_scan(&nmtd->chip, sets ? sets->nr_chips : 1); - if (err) - goto exit_error; - - s3c2410_nand_add_partition(info, nmtd, sets); - } - - /* initialise the hardware */ - err = s3c2410_nand_inithw(info); - if (err != 0) - goto exit_error; - - if (allow_clk_suspend(info)) { - dev_info(&pdev->dev, "clock idle support enabled\n"); - s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND); - } - - return 0; - - exit_error: - s3c24xx_nand_remove(pdev); - - if (err == 0) - err = -EINVAL; - return err; -} - -/* PM Support */ -#ifdef CONFIG_PM - -static int s3c24xx_nand_suspend(struct platform_device *dev, pm_message_t pm) -{ - struct s3c2410_nand_info *info = platform_get_drvdata(dev); - - if (info) { - info->save_sel = readl(info->sel_reg); - - /* For the moment, we must ensure nFCE is high during - * the time we are suspended. This really should be - * handled by suspending the MTDs we are using, but - * that is currently not the case. */ - - writel(info->save_sel | info->sel_bit, info->sel_reg); - - s3c2410_nand_clk_set_state(info, CLOCK_DISABLE); - } - - return 0; -} - -static int s3c24xx_nand_resume(struct platform_device *dev) -{ - struct s3c2410_nand_info *info = platform_get_drvdata(dev); - unsigned long sel; - - if (info) { - s3c2410_nand_clk_set_state(info, CLOCK_ENABLE); - s3c2410_nand_inithw(info); - - /* Restore the state of the nFCE line. */ - - sel = readl(info->sel_reg); - sel &= ~info->sel_bit; - sel |= info->save_sel & info->sel_bit; - writel(sel, info->sel_reg); - - s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND); - } - - return 0; -} - -#else -#define s3c24xx_nand_suspend NULL -#define s3c24xx_nand_resume NULL -#endif - -/* driver device registration */ - -static const struct platform_device_id s3c24xx_driver_ids[] = { - { - .name = "s3c2410-nand", - .driver_data = TYPE_S3C2410, - }, { - .name = "s3c2440-nand", - .driver_data = TYPE_S3C2440, - }, { - .name = "s3c2412-nand", - .driver_data = TYPE_S3C2412, - }, { - .name = "s3c6400-nand", - .driver_data = TYPE_S3C2412, /* compatible with 2412 */ - }, - { } -}; - -MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids); - -static struct platform_driver s3c24xx_nand_driver = { - .probe = s3c24xx_nand_probe, - .remove = s3c24xx_nand_remove, - .suspend = s3c24xx_nand_suspend, - .resume = s3c24xx_nand_resume, - .id_table = s3c24xx_driver_ids, - .driver = { - .name = "s3c24xx-nand", - .of_match_table = s3c24xx_nand_dt_ids, - }, -}; - -module_platform_driver(s3c24xx_nand_driver); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); -MODULE_DESCRIPTION("S3C24XX MTD NAND driver"); diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c index d957327fb4fa..c08d6b176372 100644 --- a/drivers/mtd/nand/raw/stm32_fmc2_nand.c +++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c @@ -2158,7 +2158,6 @@ static struct platform_driver stm32_fmc2_nfc_driver = { }; module_platform_driver(stm32_fmc2_nfc_driver); -MODULE_ALIAS("platform:stm32_fmc2_nfc"); MODULE_AUTHOR("Christophe Kerello <christophe.kerello@st.com>"); MODULE_DESCRIPTION("STMicroelectronics STM32 FMC2 NFC driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 162cd5f4f234..f6a8e8ae819d 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -2205,4 +2205,3 @@ module_platform_driver(sunxi_nfc_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Boris BREZILLON"); MODULE_DESCRIPTION("Allwinner NAND Flash Controller driver"); -MODULE_ALIAS("platform:sunxi_nand"); diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile index 258da42451a4..6d3d203df048 100644 --- a/drivers/mtd/nand/spi/Makefile +++ b/drivers/mtd/nand/spi/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 spinand-objs := core.o otp.o -spinand-objs += alliancememory.o ato.o esmt.o foresee.o gigadevice.o macronix.o +spinand-objs += alliancememory.o ato.o esmt.o fmsh.o foresee.o gigadevice.o macronix.o spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o obj-$(CONFIG_MTD_SPI_NAND) += spinand.o diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index b0898990b2a5..f92133b8e1a6 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -430,8 +430,16 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand, * Dirmap accesses are allowed to toggle the CS. * Toggling the CS during a continuous read is forbidden. */ - if (nbytes && req->continuous) - return -EIO; + if (nbytes && req->continuous) { + /* + * Spi controller with broken support of continuous + * reading was detected. Disable future use of + * continuous reading and return -EAGAIN to retry + * reading within regular mode. + */ + spinand->cont_read_possible = false; + return -EAGAIN; + } } if (req->datalen) @@ -899,10 +907,19 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t from, old_stats = mtd->ecc_stats; - if (spinand_use_cont_read(mtd, from, ops)) + if (spinand_use_cont_read(mtd, from, ops)) { ret = spinand_mtd_continuous_page_read(mtd, from, ops, &max_bitflips); - else + if (ret == -EAGAIN && !spinand->cont_read_possible) { + /* + * Spi controller with broken support of continuous + * reading was detected (see spinand_read_from_cache_op()), + * repeat reading in regular mode. + */ + ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips); + } + } else { ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips); + } if (ops->stats) { ops->stats->uncorrectable_errors += @@ -1093,22 +1110,50 @@ static int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs) return ret; } +static struct spi_mem_dirmap_desc *spinand_create_rdesc( + struct spinand_device *spinand, + struct spi_mem_dirmap_info *info) +{ + struct nand_device *nand = spinand_to_nand(spinand); + struct spi_mem_dirmap_desc *desc = NULL; + + if (spinand->cont_read_possible) { + /* + * spi controller may return an error if info->length is + * too large + */ + info->length = nanddev_eraseblock_size(nand); + desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, + spinand->spimem, info); + } + + if (IS_ERR_OR_NULL(desc)) { + /* + * continuous reading is not supported by flash or + * its spi controller, use regular reading + */ + spinand->cont_read_possible = false; + + info->length = nanddev_page_size(nand) + + nanddev_per_page_oobsize(nand); + desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, + spinand->spimem, info); + } + + return desc; +} + static int spinand_create_dirmap(struct spinand_device *spinand, unsigned int plane) { struct nand_device *nand = spinand_to_nand(spinand); - struct spi_mem_dirmap_info info = { - .length = nanddev_page_size(nand) + - nanddev_per_page_oobsize(nand), - }; + struct spi_mem_dirmap_info info = { 0 }; struct spi_mem_dirmap_desc *desc; - if (spinand->cont_read_possible) - info.length = nanddev_eraseblock_size(nand); - /* The plane number is passed in MSB just above the column address */ info.offset = plane << fls(nand->memorg.pagesize); + info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand); info.op_tmpl = *spinand->op_templates.update_cache; desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, spinand->spimem, &info); @@ -1118,8 +1163,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand, spinand->dirmaps[plane].wdesc = desc; info.op_tmpl = *spinand->op_templates.read_cache; - desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, - spinand->spimem, &info); + desc = spinand_create_rdesc(spinand, &info); if (IS_ERR(desc)) return PTR_ERR(desc); @@ -1132,6 +1176,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand, return 0; } + info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand); info.op_tmpl = *spinand->op_templates.update_cache; info.op_tmpl.data.ecc = true; desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, @@ -1143,8 +1188,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand, info.op_tmpl = *spinand->op_templates.read_cache; info.op_tmpl.data.ecc = true; - desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, - spinand->spimem, &info); + desc = spinand_create_rdesc(spinand, &info); if (IS_ERR(desc)) return PTR_ERR(desc); @@ -1184,6 +1228,7 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = { &alliancememory_spinand_manufacturer, &ato_spinand_manufacturer, &esmt_c8_spinand_manufacturer, + &fmsh_spinand_manufacturer, &foresee_spinand_manufacturer, &gigadevice_spinand_manufacturer, ¯onix_spinand_manufacturer, diff --git a/drivers/mtd/nand/spi/fmsh.c b/drivers/mtd/nand/spi/fmsh.c new file mode 100644 index 000000000000..8b2097bfc771 --- /dev/null +++ b/drivers/mtd/nand/spi/fmsh.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020-2021 Rockchip Electronics Co., Ltd. + * + * Author: Dingqiang Lin <jon.lin@rock-chips.com> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mtd/spinand.h> + +#define SPINAND_MFR_FMSH 0xA1 + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0), + SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0), + SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0), + SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0), + SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0), + SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0)); + +static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0), + SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0), + SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0)); + +static int fm25s01a_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + return -ERANGE; +} + +static int fm25s01a_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 2; + region->length = 62; + + return 0; +} + +static const struct mtd_ooblayout_ops fm25s01a_ooblayout = { + .ecc = fm25s01a_ooblayout_ecc, + .free = fm25s01a_ooblayout_free, +}; + +static const struct spinand_info fmsh_spinand_table[] = { + SPINAND_INFO("FM25S01A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE4), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(1, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&fm25s01a_ooblayout, NULL)), +}; + +static const struct spinand_manufacturer_ops fmsh_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer fmsh_spinand_manufacturer = { + .id = SPINAND_MFR_FMSH, + .name = "Fudan Micro", + .chips = fmsh_spinand_table, + .nchips = ARRAY_SIZE(fmsh_spinand_table), + .ops = &fmsh_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/gigadevice.c b/drivers/mtd/nand/spi/gigadevice.c index 93e40431dbe2..72ad36c9a126 100644 --- a/drivers/mtd/nand/spi/gigadevice.c +++ b/drivers/mtd/nand/spi/gigadevice.c @@ -4,6 +4,7 @@ * Chuanhong Guo <gch981213@gmail.com> */ +#include <linux/bitfield.h> #include <linux/device.h> #include <linux/kernel.h> #include <linux/mtd/spinand.h> @@ -23,6 +24,18 @@ #define GD5FXGQ4UXFXXG_STATUS_ECC_1_3_BITFLIPS (1 << 4) #define GD5FXGQ4UXFXXG_STATUS_ECC_UNCOR_ERROR (7 << 4) +/* Feature bit definitions */ +#define GD_FEATURE_NR BIT(3) /* Normal Read(1=normal,0=continuous) */ +#define GD_FEATURE_CRDC BIT(2) /* Continuous Read Dummy */ + +/* ECC status extraction helpers */ +#define GD_ECCSR_LAST_PAGE(eccsr) FIELD_GET(GENMASK(3, 0), eccsr) +#define GD_ECCSR_ACCUMULATED(eccsr) FIELD_GET(GENMASK(7, 4), eccsr) + +struct gigadevice_priv { + bool continuous_read; +}; + static SPINAND_OP_VARIANTS(read_cache_variants, SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0), SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0), @@ -63,6 +76,74 @@ static SPINAND_OP_VARIANTS(update_cache_variants, SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0), SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0)); +static int gd5fxgm9_get_eccsr(struct spinand_device *spinand, u8 *eccsr) +{ + struct gigadevice_priv *priv = spinand->priv; + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0x7c, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_DUMMY(1, 1), + SPI_MEM_OP_DATA_IN(1, eccsr, 1)); + int ret; + + ret = spi_mem_exec_op(spinand->spimem, &op); + if (ret) + return ret; + + if (priv->continuous_read) + *eccsr = GD_ECCSR_ACCUMULATED(*eccsr); + else + *eccsr = GD_ECCSR_LAST_PAGE(*eccsr); + + return 0; +} + +static int gd5fxgm9_ecc_get_status(struct spinand_device *spinand, u8 status) +{ + struct nand_device *nand = spinand_to_nand(spinand); + u8 eccsr; + int ret; + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS: + ret = gd5fxgm9_get_eccsr(spinand, spinand->scratchbuf); + if (ret) + return nanddev_get_ecc_conf(nand)->strength; + + eccsr = *spinand->scratchbuf; + if (WARN_ON(!eccsr || eccsr > nanddev_get_ecc_conf(nand)->strength)) + return nanddev_get_ecc_conf(nand)->strength; + + return eccsr; + + case GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS: + return 8; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: + return -EINVAL; + } +} + +static int gd5fxgm9_set_continuous_read(struct spinand_device *spinand, bool enable) +{ + struct gigadevice_priv *priv = spinand->priv; + int ret; + + ret = spinand_upd_cfg(spinand, GD_FEATURE_NR, + enable ? 0 : GD_FEATURE_NR); + if (ret) + return ret; + + priv->continuous_read = enable; + + return 0; +} + static int gd5fxgq4xa_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *region) { @@ -542,7 +623,8 @@ static const struct spinand_info gigadevice_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, - gd5fxgq4uexxg_ecc_get_status)), + gd5fxgm9_ecc_get_status), + SPINAND_CONT_READ(gd5fxgm9_set_continuous_read)), SPINAND_INFO("GD5F1GM9RExxG", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x81, 0x01), NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), @@ -552,10 +634,31 @@ static const struct spinand_info gigadevice_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, - gd5fxgq4uexxg_ecc_get_status)), + gd5fxgm9_ecc_get_status), + SPINAND_CONT_READ(gd5fxgm9_set_continuous_read)), }; +static int gd5fxgm9_spinand_init(struct spinand_device *spinand) +{ + struct gigadevice_priv *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spinand->priv = priv; + + return 0; +} + +static void gd5fxgm9_spinand_cleanup(struct spinand_device *spinand) +{ + kfree(spinand->priv); +} + static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = { + .init = gd5fxgm9_spinand_init, + .cleanup = gd5fxgm9_spinand_cleanup, }; const struct spinand_manufacturer gigadevice_spinand_manufacturer = { diff --git a/drivers/mtd/rfd_ftl.c b/drivers/mtd/rfd_ftl.c index c546f8c5f24d..be26cc67a1c4 100644 --- a/drivers/mtd/rfd_ftl.c +++ b/drivers/mtd/rfd_ftl.c @@ -190,8 +190,8 @@ static int scan_header(struct partition *part) if (!part->blocks) goto err; - part->sector_map = vmalloc(array_size(sizeof(u_long), - part->sector_count)); + part->sector_map = vmalloc_array(part->sector_count, + sizeof(u_long)); if (!part->sector_map) goto err; diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index ac4b960101cc..20ea80450f22 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -2014,6 +2014,76 @@ static const struct flash_info *spi_nor_detect(struct spi_nor *nor) return info; } +/* + * On Octal DTR capable flashes, reads cannot start or end at an odd + * address in Octal DTR mode. Extra bytes need to be read at the start + * or end to make sure both the start address and length remain even. + */ +static int spi_nor_octal_dtr_read(struct spi_nor *nor, loff_t from, size_t len, + u_char *buf) +{ + u_char *tmp_buf; + size_t tmp_len; + loff_t start, end; + int ret, bytes_read; + + if (IS_ALIGNED(from, 2) && IS_ALIGNED(len, 2)) + return spi_nor_read_data(nor, from, len, buf); + else if (IS_ALIGNED(from, 2) && len > PAGE_SIZE) + return spi_nor_read_data(nor, from, round_down(len, PAGE_SIZE), + buf); + + tmp_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!tmp_buf) + return -ENOMEM; + + start = round_down(from, 2); + end = round_up(from + len, 2); + + /* + * Avoid allocating too much memory. The requested read length might be + * quite large. Allocating a buffer just as large (slightly bigger, in + * fact) would put unnecessary memory pressure on the system. + * + * For example if the read is from 3 to 1M, then this will read from 2 + * to 4098. The reads from 4098 to 1M will then not need a temporary + * buffer so they can proceed as normal. + */ + tmp_len = min_t(size_t, end - start, PAGE_SIZE); + + ret = spi_nor_read_data(nor, start, tmp_len, tmp_buf); + if (ret == 0) { + ret = -EIO; + goto out; + } + if (ret < 0) + goto out; + + /* + * More bytes are read than actually requested, but that number can't be + * reported to the calling function or it will confuse its calculations. + * Calculate how many of the _requested_ bytes were read. + */ + bytes_read = ret; + + if (from != start) + ret -= from - start; + + /* + * Only account for extra bytes at the end if they were actually read. + * For example, if the total length was truncated because of temporary + * buffer size limit then the adjustment for the extra bytes at the end + * is not needed. + */ + if (start + bytes_read == end) + ret -= end - (from + len); + + memcpy(buf, tmp_buf + (from - start), ret); +out: + kfree(tmp_buf); + return ret; +} + static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { @@ -2031,7 +2101,11 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, while (len) { loff_t addr = from; - ret = spi_nor_read_data(nor, addr, len, buf); + if (nor->read_proto == SNOR_PROTO_8_8_8_DTR) + ret = spi_nor_octal_dtr_read(nor, addr, len, buf); + else + ret = spi_nor_read_data(nor, addr, len, buf); + if (ret == 0) { /* We shouldn't see 0-length reads */ ret = -EIO; @@ -2055,6 +2129,68 @@ read_err: } /* + * On Octal DTR capable flashes, writes cannot start or end at an odd address + * in Octal DTR mode. Extra 0xff bytes need to be appended or prepended to + * make sure the start address and end address are even. 0xff is used because + * on NOR flashes a program operation can only flip bits from 1 to 0, not the + * other way round. 0 to 1 flip needs to happen via erases. + */ +static int spi_nor_octal_dtr_write(struct spi_nor *nor, loff_t to, size_t len, + const u8 *buf) +{ + u8 *tmp_buf; + size_t bytes_written; + loff_t start, end; + int ret; + + if (IS_ALIGNED(to, 2) && IS_ALIGNED(len, 2)) + return spi_nor_write_data(nor, to, len, buf); + + tmp_buf = kmalloc(nor->params->page_size, GFP_KERNEL); + if (!tmp_buf) + return -ENOMEM; + + memset(tmp_buf, 0xff, nor->params->page_size); + + start = round_down(to, 2); + end = round_up(to + len, 2); + + memcpy(tmp_buf + (to - start), buf, len); + + ret = spi_nor_write_data(nor, start, end - start, tmp_buf); + if (ret == 0) { + ret = -EIO; + goto out; + } + if (ret < 0) + goto out; + + /* + * More bytes are written than actually requested, but that number can't + * be reported to the calling function or it will confuse its + * calculations. Calculate how many of the _requested_ bytes were + * written. + */ + bytes_written = ret; + + if (to != start) + ret -= to - start; + + /* + * Only account for extra bytes at the end if they were actually + * written. For example, if for some reason the controller could only + * complete a partial write then the adjustment for the extra bytes at + * the end is not needed. + */ + if (start + bytes_written == end) + ret -= end - (to + len); + +out: + kfree(tmp_buf); + return ret; +} + +/* * Write an address range to the nor chip. Data must be written in * FLASH_PAGESIZE chunks. The address range may be any size provided * it is within the physical boundaries. @@ -2090,7 +2226,12 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, goto write_err; } - ret = spi_nor_write_data(nor, addr, page_remain, buf + i); + if (nor->write_proto == SNOR_PROTO_8_8_8_DTR) + ret = spi_nor_octal_dtr_write(nor, addr, page_remain, + buf + i); + else + ret = spi_nor_write_data(nor, addr, page_remain, + buf + i); spi_nor_unlock_device(nor); if (ret < 0) goto write_err; diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index edd811444ce5..e0d88d3199c1 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -28,6 +28,17 @@ source "drivers/nvmem/layouts/Kconfig" # Devices +config NVMEM_AN8855_EFUSE + tristate "Airoha AN8855 eFuse support" + depends on MFD_AIROHA_AN8855 || COMPILE_TEST + help + Say y here to enable support for reading eFuses on Airoha AN8855 + Switch. These are e.g. used to store factory programmed + calibration data required for the PHY. + + This driver can also be built as a module. If so, the module will + be called nvmem-an8855-efuse. + config NVMEM_APPLE_EFUSES tristate "Apple eFuse support" depends on ARCH_APPLE || COMPILE_TEST @@ -240,6 +251,16 @@ config NVMEM_NINTENDO_OTP This driver can also be built as a module. If so, the module will be called nvmem-nintendo-otp. +config NVMEM_S32G_OCOTP + tristate "S32G SoC OCOTP support" + depends on ARCH_S32 + help + This is a driver for the 'OCOTP' peripheral available on S32G + platforms. + + If you say Y here, you will get support for the One Time + Programmable memory pages. + config NVMEM_QCOM_QFPROM tristate "QCOM QFPROM Support" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 2021d59688db..70a4464dcb1e 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -10,6 +10,8 @@ nvmem_layouts-y := layouts.o obj-y += layouts/ # Devices +obj-$(CONFIG_NVMEM_AN8855_EFUSE) += nvmem-an8855-efuse.o +nvmem-an8855-efuse-y := an8855-efuse.o obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o nvmem-apple-efuses-y := apple-efuses.o obj-$(CONFIG_NVMEM_APPLE_SPMI) += apple_nvmem_spmi.o @@ -79,6 +81,8 @@ obj-$(CONFIG_NVMEM_SUNPLUS_OCOTP) += nvmem_sunplus_ocotp.o nvmem_sunplus_ocotp-y := sunplus-ocotp.o obj-$(CONFIG_NVMEM_SUNXI_SID) += nvmem_sunxi_sid.o nvmem_sunxi_sid-y := sunxi_sid.o +obj-$(CONFIG_NVMEM_S32G_OCOTP) += nvmem-s32g-ocotp-nvmem.o +nvmem-s32g-ocotp-nvmem-y := s32g-ocotp-nvmem.o obj-$(CONFIG_NVMEM_U_BOOT_ENV) += nvmem_u-boot-env.o nvmem_u-boot-env-y := u-boot-env.o obj-$(CONFIG_NVMEM_UNIPHIER_EFUSE) += nvmem-uniphier-efuse.o diff --git a/drivers/nvmem/an8855-efuse.c b/drivers/nvmem/an8855-efuse.c new file mode 100644 index 000000000000..d1afde6f623f --- /dev/null +++ b/drivers/nvmem/an8855-efuse.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha AN8855 Switch EFUSE Driver + */ + +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define AN8855_EFUSE_CELL 50 + +#define AN8855_EFUSE_DATA0 0x1000a500 +#define AN8855_EFUSE_R50O GENMASK(30, 24) + +static int an8855_efuse_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct regmap *regmap = context; + + return regmap_bulk_read(regmap, AN8855_EFUSE_DATA0 + offset, + val, bytes / sizeof(u32)); +} + +static int an8855_efuse_probe(struct platform_device *pdev) +{ + struct nvmem_config an8855_nvmem_config = { + .name = "an8855-efuse", + .size = AN8855_EFUSE_CELL * sizeof(u32), + .stride = sizeof(u32), + .word_size = sizeof(u32), + .reg_read = an8855_efuse_read, + }; + struct device *dev = &pdev->dev; + struct nvmem_device *nvmem; + struct regmap *regmap; + + /* Assign NVMEM priv to MFD regmap */ + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENOENT; + + an8855_nvmem_config.priv = regmap; + an8855_nvmem_config.dev = dev; + nvmem = devm_nvmem_register(dev, &an8855_nvmem_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id an8855_efuse_of_match[] = { + { .compatible = "airoha,an8855-efuse", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, an8855_efuse_of_match); + +static struct platform_driver an8855_efuse_driver = { + .probe = an8855_efuse_probe, + .driver = { + .name = "an8855-efuse", + .of_match_table = an8855_efuse_of_match, + }, +}; +module_platform_driver(an8855_efuse_driver); + +MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>"); +MODULE_DESCRIPTION("Driver for AN8855 Switch EFUSE"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/s32g-ocotp-nvmem.c b/drivers/nvmem/s32g-ocotp-nvmem.c new file mode 100644 index 000000000000..119871ab3a94 --- /dev/null +++ b/drivers/nvmem/s32g-ocotp-nvmem.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2023-2025 NXP + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +struct s32g_ocotp_priv { + struct device *dev; + void __iomem *base; +}; + +static int s32g_ocotp_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct s32g_ocotp_priv *s32g_data = context; + u32 *dst = val; + + while (bytes >= sizeof(u32)) { + *dst++ = ioread32(s32g_data->base + offset); + + bytes -= sizeof(u32); + offset += sizeof(u32); + } + + return 0; +} + +static struct nvmem_keepout s32g_keepouts[] = { + { .start = 0, .end = 520 }, + { .start = 540, .end = 564 }, + { .start = 596, .end = 664 }, + { .start = 668, .end = 676 }, + { .start = 684, .end = 732 }, + { .start = 744, .end = 864 }, + { .start = 908, .end = 924 }, + { .start = 928, .end = 936 }, + { .start = 948, .end = 964 }, + { .start = 968, .end = 976 }, + { .start = 984, .end = 1012 }, +}; + +static struct nvmem_config s32g_ocotp_nvmem_config = { + .name = "s32g-ocotp", + .add_legacy_fixed_of_cells = true, + .read_only = true, + .word_size = 4, + .reg_read = s32g_ocotp_read, + .keepout = s32g_keepouts, + .nkeepout = ARRAY_SIZE(s32g_keepouts), +}; + +static const struct of_device_id ocotp_of_match[] = { + { .compatible = "nxp,s32g2-ocotp" }, + { /* sentinel */ } +}; + +static int s32g_ocotp_probe(struct platform_device *pdev) +{ + struct s32g_ocotp_priv *s32g_data; + struct device *dev = &pdev->dev; + struct nvmem_device *nvmem; + struct resource *res; + + s32g_data = devm_kzalloc(dev, sizeof(*s32g_data), GFP_KERNEL); + if (!s32g_data) + return -ENOMEM; + + s32g_data->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(s32g_data->base)) + return dev_err_probe(dev, PTR_ERR(s32g_data->base), + "Cannot map OCOTP device.\n"); + + s32g_data->dev = dev; + s32g_ocotp_nvmem_config.dev = dev; + s32g_ocotp_nvmem_config.priv = s32g_data; + s32g_ocotp_nvmem_config.size = resource_size(res); + + nvmem = devm_nvmem_register(dev, &s32g_ocotp_nvmem_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static struct platform_driver s32g_ocotp_driver = { + .probe = s32g_ocotp_probe, + .driver = { + .name = "s32g-ocotp", + .of_match_table = ocotp_of_match, + }, +}; +module_platform_driver(s32g_ocotp_driver); +MODULE_AUTHOR("NXP"); +MODULE_DESCRIPTION("S32G OCOTP driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/parisc/eisa_eeprom.c b/drivers/parisc/eisa_eeprom.c index 443b15422fc1..601cbb22574f 100644 --- a/drivers/parisc/eisa_eeprom.c +++ b/drivers/parisc/eisa_eeprom.c @@ -15,8 +15,6 @@ #include <linux/uaccess.h> #include <asm/eisa_eeprom.h> -#define EISA_EEPROM_MINOR 241 - static loff_t eisa_eeprom_llseek(struct file *file, loff_t offset, int origin) { return fixed_size_llseek(file, offset, origin, HPEE_MAX_LENGTH); diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index d97335a40193..17315a825674 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3829,7 +3829,7 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MELLANOX, 0xcf80, quirk_no_pm_reset); */ static void quirk_thunderbolt_hotplug_msi(struct pci_dev *pdev) { - if (pdev->is_hotplug_bridge && + if (pdev->is_pciehp && (pdev->device != PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C || pdev->revision <= 1)) pdev->no_msi = 1; diff --git a/drivers/peci/controller/peci-npcm.c b/drivers/peci/controller/peci-npcm.c index c77591ca583d..931868991241 100644 --- a/drivers/peci/controller/peci-npcm.c +++ b/drivers/peci/controller/peci-npcm.c @@ -221,7 +221,6 @@ static const struct regmap_config npcm_peci_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = NPCM_PECI_MAX_REG, - .fast_io = true, }; static const struct peci_controller_ops npcm_ops = { diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig index 06288aebc559..10f905d7d6bf 100644 --- a/drivers/platform/arm64/Kconfig +++ b/drivers/platform/arm64/Kconfig @@ -70,4 +70,24 @@ config EC_LENOVO_YOGA_C630 Say M or Y here to include this support. +config EC_LENOVO_THINKPAD_T14S + tristate "Lenovo Thinkpad T14s Embedded Controller driver" + depends on ARCH_QCOM || COMPILE_TEST + depends on I2C + depends on INPUT + select INPUT_SPARSEKMAP + select LEDS_CLASS + select NEW_LEDS + select SND_CTL_LED if SND + help + Driver for the Embedded Controller in the Qualcomm Snapdragon-based + Lenovo Thinkpad T14s, which provides access to keyboard backlight + and status LEDs. + + This driver provides support for the mentioned laptop where this + information is not properly exposed via the standard Qualcomm + devices. + + Say M or Y here to include this support. + endif # ARM64_PLATFORM_DEVICES diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile index 46a99eba3264..60c131cff6a1 100644 --- a/drivers/platform/arm64/Makefile +++ b/drivers/platform/arm64/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o +obj-$(CONFIG_EC_LENOVO_THINKPAD_T14S) += lenovo-thinkpad-t14s.o diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c new file mode 100644 index 000000000000..1d5d11adaf32 --- /dev/null +++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025, Sebastian Reichel + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/container_of.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/interrupt.h> +#include <linux/leds.h> +#include <linux/lockdep.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define T14S_EC_CMD_ECRD 0x02 +#define T14S_EC_CMD_ECWR 0x03 +#define T14S_EC_CMD_EVT 0xf0 + +#define T14S_EC_REG_LED 0x0c +#define T14S_EC_REG_KBD_BL1 0x0d +#define T14S_EC_REG_KBD_BL2 0xe1 +#define T14S_EC_KBD_BL1_MASK GENMASK_U8(7, 6) +#define T14S_EC_KBD_BL2_MASK GENMASK_U8(3, 2) +#define T14S_EC_REG_AUD 0x30 +#define T14S_EC_MIC_MUTE_LED BIT(5) +#define T14S_EC_SPK_MUTE_LED BIT(6) + +#define T14S_EC_EVT_NONE 0x00 +#define T14S_EC_EVT_KEY_FN_4 0x13 +#define T14S_EC_EVT_KEY_FN_F7 0x16 +#define T14S_EC_EVT_KEY_FN_SPACE 0x1f +#define T14S_EC_EVT_KEY_TP_DOUBLE_TAP 0x20 +#define T14S_EC_EVT_AC_CONNECTED 0x26 +#define T14S_EC_EVT_AC_DISCONNECTED 0x27 +#define T14S_EC_EVT_KEY_POWER 0x28 +#define T14S_EC_EVT_LID_OPEN 0x2a +#define T14S_EC_EVT_LID_CLOSED 0x2b +#define T14S_EC_EVT_THERMAL_TZ40 0x5c +#define T14S_EC_EVT_THERMAL_TZ42 0x5d +#define T14S_EC_EVT_THERMAL_TZ39 0x5e +#define T14S_EC_EVT_KEY_FN_F12 0x62 +#define T14S_EC_EVT_KEY_FN_TAB 0x63 +#define T14S_EC_EVT_KEY_FN_F8 0x64 +#define T14S_EC_EVT_KEY_FN_F10 0x65 +#define T14S_EC_EVT_KEY_FN_F4 0x6a +#define T14S_EC_EVT_KEY_FN_D 0x6b +#define T14S_EC_EVT_KEY_FN_T 0x6c +#define T14S_EC_EVT_KEY_FN_H 0x6d +#define T14S_EC_EVT_KEY_FN_M 0x6e +#define T14S_EC_EVT_KEY_FN_L 0x6f +#define T14S_EC_EVT_KEY_FN_RIGHT_SHIFT 0x71 +#define T14S_EC_EVT_KEY_FN_ESC 0x74 +#define T14S_EC_EVT_KEY_FN_N 0x79 +#define T14S_EC_EVT_KEY_FN_F11 0x7a +#define T14S_EC_EVT_KEY_FN_G 0x7e + +/* Hardware LED blink rate is 1 Hz (500ms off, 500ms on) */ +#define T14S_EC_BLINK_RATE_ON_OFF_MS 500 + +/* + * Add a virtual offset on all key event codes for sparse keymap handling, + * since the sparse keymap infrastructure does not map some raw key event + * codes used by the EC. For example 0x16 (T14S_EC_EVT_KEY_FN_F7) is mapped + * to KEY_MUTE if no offset is applied. + */ +#define T14S_EC_KEY_EVT_OFFSET 0x1000 +#define T14S_EC_KEY_ENTRY(key, value) \ + { KE_KEY, T14S_EC_KEY_EVT_OFFSET + T14S_EC_EVT_KEY_##key, { value } } + +enum t14s_ec_led_status_t { + T14S_EC_LED_OFF = 0x00, + T14S_EC_LED_ON = 0x80, + T14S_EC_LED_BLINK = 0xc0, +}; + +struct t14s_ec_led_classdev { + struct led_classdev led_classdev; + int led; + enum t14s_ec_led_status_t cache; + struct t14s_ec *ec; +}; + +struct t14s_ec { + struct regmap *regmap; + struct device *dev; + struct t14s_ec_led_classdev led_pwr_btn; + struct t14s_ec_led_classdev led_chrg_orange; + struct t14s_ec_led_classdev led_chrg_white; + struct t14s_ec_led_classdev led_lid_logo_dot; + struct led_classdev kbd_backlight; + struct led_classdev led_mic_mute; + struct led_classdev led_spk_mute; + struct input_dev *inputdev; +}; + +static const struct regmap_config t14s_ec_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, +}; + +static int t14s_ec_write(void *context, unsigned int reg, + unsigned int val) +{ + struct t14s_ec *ec = context; + struct i2c_client *client = to_i2c_client(ec->dev); + u8 buf[5] = {T14S_EC_CMD_ECWR, reg, 0x00, 0x01, val}; + int ret; + + ret = i2c_master_send(client, buf, sizeof(buf)); + if (ret < 0) + return ret; + + return 0; +} + +static int t14s_ec_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct t14s_ec *ec = context; + struct i2c_client *client = to_i2c_client(ec->dev); + u8 buf[4] = {T14S_EC_CMD_ECRD, reg, 0x00, 0x01}; + struct i2c_msg request, response; + u8 result; + int ret; + + request.addr = client->addr; + request.flags = I2C_M_STOP; + request.len = sizeof(buf); + request.buf = buf; + response.addr = client->addr; + response.flags = I2C_M_RD; + response.len = 1; + response.buf = &result; + + i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); + + ret = __i2c_transfer(client->adapter, &request, 1); + if (ret < 0) + goto out; + + ret = __i2c_transfer(client->adapter, &response, 1); + if (ret < 0) + goto out; + + *val = result; + ret = 0; + +out: + i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); + return ret; +} + +static const struct regmap_bus t14s_ec_regmap_bus = { + .reg_write = t14s_ec_write, + .reg_read = t14s_ec_read, +}; + +static int t14s_ec_read_evt(struct t14s_ec *ec, u8 *val) +{ + struct i2c_client *client = to_i2c_client(ec->dev); + u8 buf[4] = {T14S_EC_CMD_EVT, 0x00, 0x00, 0x01}; + struct i2c_msg request, response; + int ret; + + request.addr = client->addr; + request.flags = I2C_M_STOP; + request.len = sizeof(buf); + request.buf = buf; + response.addr = client->addr; + response.flags = I2C_M_RD; + response.len = 1; + response.buf = val; + + i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); + + ret = __i2c_transfer(client->adapter, &request, 1); + if (ret < 0) + goto out; + + ret = __i2c_transfer(client->adapter, &response, 1); + if (ret < 0) + goto out; + + ret = 0; + +out: + i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); + return ret; +} + +static int t14s_led_set_status(struct t14s_ec *ec, + struct t14s_ec_led_classdev *led, + const enum t14s_ec_led_status_t ledstatus) +{ + int ret; + + ret = regmap_write(ec->regmap, T14S_EC_REG_LED, + led->led | ledstatus); + if (ret < 0) + return ret; + + led->cache = ledstatus; + return 0; +} + +static int t14s_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct t14s_ec_led_classdev *led = container_of(led_cdev, + struct t14s_ec_led_classdev, led_classdev); + enum t14s_ec_led_status_t new_state; + + if (brightness == LED_OFF) + new_state = T14S_EC_LED_OFF; + else if (led->cache == T14S_EC_LED_BLINK) + new_state = T14S_EC_LED_BLINK; + else + new_state = T14S_EC_LED_ON; + + return t14s_led_set_status(led->ec, led, new_state); +} + +static int t14s_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct t14s_ec_led_classdev *led = container_of(led_cdev, + struct t14s_ec_led_classdev, led_classdev); + + if (*delay_on == 0 && *delay_off == 0) { + /* Userspace does not provide a blink rate; we can choose it */ + *delay_on = T14S_EC_BLINK_RATE_ON_OFF_MS; + *delay_off = T14S_EC_BLINK_RATE_ON_OFF_MS; + } else if ((*delay_on != T14S_EC_BLINK_RATE_ON_OFF_MS) || + (*delay_off != T14S_EC_BLINK_RATE_ON_OFF_MS)) + return -EINVAL; + + return t14s_led_set_status(led->ec, led, T14S_EC_LED_BLINK); +} + +static int t14s_init_led(struct t14s_ec *ec, struct t14s_ec_led_classdev *led, + u8 id, const char *name) +{ + led->led_classdev.name = name; + led->led_classdev.flags = LED_RETAIN_AT_SHUTDOWN; + led->led_classdev.max_brightness = 1; + led->led_classdev.brightness_set_blocking = t14s_led_brightness_set; + led->led_classdev.blink_set = t14s_led_blink_set; + led->ec = ec; + led->led = id; + + return devm_led_classdev_register(ec->dev, &led->led_classdev); +} + +static int t14s_leds_probe(struct t14s_ec *ec) +{ + int ret; + + ret = t14s_init_led(ec, &ec->led_pwr_btn, 0, "platform::power"); + if (ret) + return ret; + + ret = t14s_init_led(ec, &ec->led_chrg_orange, 1, + "platform:amber:battery-charging"); + if (ret) + return ret; + + ret = t14s_init_led(ec, &ec->led_chrg_white, 2, + "platform:white:battery-full"); + if (ret) + return ret; + + ret = t14s_init_led(ec, &ec->led_lid_logo_dot, 10, + "platform::lid_logo_dot"); + if (ret) + return ret; + + return 0; +} + +static int t14s_kbd_bl_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec, + kbd_backlight); + int ret; + u8 val; + + val = FIELD_PREP(T14S_EC_KBD_BL1_MASK, brightness); + ret = regmap_update_bits(ec->regmap, T14S_EC_REG_KBD_BL1, + T14S_EC_KBD_BL1_MASK, val); + if (ret < 0) + return ret; + + val = FIELD_PREP(T14S_EC_KBD_BL2_MASK, brightness); + ret = regmap_update_bits(ec->regmap, T14S_EC_REG_KBD_BL2, + T14S_EC_KBD_BL2_MASK, val); + if (ret < 0) + return ret; + + return 0; +} + +static enum led_brightness t14s_kbd_bl_get(struct led_classdev *led_cdev) +{ + struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec, + kbd_backlight); + unsigned int val; + int ret; + + ret = regmap_read(ec->regmap, T14S_EC_REG_KBD_BL1, &val); + if (ret < 0) + return ret; + + return FIELD_GET(T14S_EC_KBD_BL1_MASK, val); +} + +static void t14s_kbd_bl_update(struct t14s_ec *ec) +{ + enum led_brightness brightness = t14s_kbd_bl_get(&ec->kbd_backlight); + + led_classdev_notify_brightness_hw_changed(&ec->kbd_backlight, brightness); +} + +static int t14s_kbd_backlight_probe(struct t14s_ec *ec) +{ + ec->kbd_backlight.name = "platform::kbd_backlight"; + ec->kbd_backlight.flags = LED_BRIGHT_HW_CHANGED; + ec->kbd_backlight.max_brightness = 2; + ec->kbd_backlight.brightness_set_blocking = t14s_kbd_bl_set; + ec->kbd_backlight.brightness_get = t14s_kbd_bl_get; + + return devm_led_classdev_register(ec->dev, &ec->kbd_backlight); +} + +static enum led_brightness t14s_audio_led_get(struct t14s_ec *ec, u8 led_bit) +{ + unsigned int val; + int ret; + + ret = regmap_read(ec->regmap, T14S_EC_REG_AUD, &val); + if (ret < 0) + return ret; + + return !!(val & led_bit) ? LED_ON : LED_OFF; +} + +static enum led_brightness t14s_audio_led_set(struct t14s_ec *ec, + u8 led_mask, + enum led_brightness brightness) +{ + return regmap_assign_bits(ec->regmap, T14S_EC_REG_AUD, led_mask, brightness > 0); +} + +static enum led_brightness t14s_mic_mute_led_get(struct led_classdev *led_cdev) +{ + struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec, + led_mic_mute); + + return t14s_audio_led_get(ec, T14S_EC_MIC_MUTE_LED); +} + +static int t14s_mic_mute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec, + led_mic_mute); + + return t14s_audio_led_set(ec, T14S_EC_MIC_MUTE_LED, brightness); +} + +static enum led_brightness t14s_spk_mute_led_get(struct led_classdev *led_cdev) +{ + struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec, + led_spk_mute); + + return t14s_audio_led_get(ec, T14S_EC_SPK_MUTE_LED); +} + +static int t14s_spk_mute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct t14s_ec *ec = container_of(led_cdev, struct t14s_ec, + led_spk_mute); + + return t14s_audio_led_set(ec, T14S_EC_SPK_MUTE_LED, brightness); +} + +static int t14s_kbd_audio_led_probe(struct t14s_ec *ec) +{ + int ret; + + ec->led_mic_mute.name = "platform::micmute"; + ec->led_mic_mute.max_brightness = 1; + ec->led_mic_mute.default_trigger = "audio-micmute"; + ec->led_mic_mute.brightness_set_blocking = t14s_mic_mute_led_set; + ec->led_mic_mute.brightness_get = t14s_mic_mute_led_get; + + ec->led_spk_mute.name = "platform::mute"; + ec->led_spk_mute.max_brightness = 1; + ec->led_spk_mute.default_trigger = "audio-mute"; + ec->led_spk_mute.brightness_set_blocking = t14s_spk_mute_led_set; + ec->led_spk_mute.brightness_get = t14s_spk_mute_led_get; + + ret = devm_led_classdev_register(ec->dev, &ec->led_mic_mute); + if (ret) + return ret; + + return devm_led_classdev_register(ec->dev, &ec->led_spk_mute); +} + +static const struct key_entry t14s_keymap[] = { + T14S_EC_KEY_ENTRY(FN_4, KEY_SLEEP), + T14S_EC_KEY_ENTRY(FN_N, KEY_VENDOR), + T14S_EC_KEY_ENTRY(FN_F4, KEY_MICMUTE), + T14S_EC_KEY_ENTRY(FN_F7, KEY_SWITCHVIDEOMODE), + T14S_EC_KEY_ENTRY(FN_F8, KEY_PERFORMANCE), + T14S_EC_KEY_ENTRY(FN_F10, KEY_SELECTIVE_SCREENSHOT), + T14S_EC_KEY_ENTRY(FN_F11, KEY_LINK_PHONE), + T14S_EC_KEY_ENTRY(FN_F12, KEY_BOOKMARKS), + T14S_EC_KEY_ENTRY(FN_SPACE, KEY_KBDILLUMTOGGLE), + T14S_EC_KEY_ENTRY(FN_ESC, KEY_FN_ESC), + T14S_EC_KEY_ENTRY(FN_TAB, KEY_ZOOM), + T14S_EC_KEY_ENTRY(FN_RIGHT_SHIFT, KEY_FN_RIGHT_SHIFT), + T14S_EC_KEY_ENTRY(TP_DOUBLE_TAP, KEY_PROG4), + { KE_END } +}; + +static int t14s_input_probe(struct t14s_ec *ec) +{ + int ret; + + ec->inputdev = devm_input_allocate_device(ec->dev); + if (!ec->inputdev) + return -ENOMEM; + + ec->inputdev->name = "ThinkPad Extra Buttons"; + ec->inputdev->phys = "thinkpad/input0"; + ec->inputdev->id.bustype = BUS_HOST; + ec->inputdev->dev.parent = ec->dev; + + ret = sparse_keymap_setup(ec->inputdev, t14s_keymap, NULL); + if (ret) + return ret; + + return input_register_device(ec->inputdev); +} + +static irqreturn_t t14s_ec_irq_handler(int irq, void *data) +{ + struct t14s_ec *ec = data; + int ret; + u8 val; + + ret = t14s_ec_read_evt(ec, &val); + if (ret < 0) { + dev_err(ec->dev, "Failed to read event\n"); + return IRQ_HANDLED; + } + + switch (val) { + case T14S_EC_EVT_NONE: + break; + case T14S_EC_EVT_KEY_FN_SPACE: + t14s_kbd_bl_update(ec); + fallthrough; + case T14S_EC_EVT_KEY_FN_F4: + case T14S_EC_EVT_KEY_FN_F7: + case T14S_EC_EVT_KEY_FN_4: + case T14S_EC_EVT_KEY_FN_F8: + case T14S_EC_EVT_KEY_FN_F12: + case T14S_EC_EVT_KEY_FN_TAB: + case T14S_EC_EVT_KEY_FN_F10: + case T14S_EC_EVT_KEY_FN_N: + case T14S_EC_EVT_KEY_FN_F11: + case T14S_EC_EVT_KEY_FN_ESC: + case T14S_EC_EVT_KEY_FN_RIGHT_SHIFT: + case T14S_EC_EVT_KEY_TP_DOUBLE_TAP: + sparse_keymap_report_event(ec->inputdev, + T14S_EC_KEY_EVT_OFFSET + val, 1, true); + break; + case T14S_EC_EVT_AC_CONNECTED: + dev_dbg(ec->dev, "AC connected\n"); + break; + case T14S_EC_EVT_AC_DISCONNECTED: + dev_dbg(ec->dev, "AC disconnected\n"); + break; + case T14S_EC_EVT_KEY_POWER: + dev_dbg(ec->dev, "power button\n"); + break; + case T14S_EC_EVT_LID_OPEN: + dev_dbg(ec->dev, "LID open\n"); + break; + case T14S_EC_EVT_LID_CLOSED: + dev_dbg(ec->dev, "LID closed\n"); + break; + case T14S_EC_EVT_THERMAL_TZ40: + dev_dbg(ec->dev, "Thermal Zone 40 Status Change Event (CPU/GPU)\n"); + break; + case T14S_EC_EVT_THERMAL_TZ42: + dev_dbg(ec->dev, "Thermal Zone 42 Status Change Event (Battery)\n"); + break; + case T14S_EC_EVT_THERMAL_TZ39: + dev_dbg(ec->dev, "Thermal Zone 39 Status Change Event (CPU/GPU)\n"); + break; + case T14S_EC_EVT_KEY_FN_G: + dev_dbg(ec->dev, "FN + G - toggle double-tapping\n"); + break; + case T14S_EC_EVT_KEY_FN_L: + dev_dbg(ec->dev, "FN + L - low performance mode\n"); + break; + case T14S_EC_EVT_KEY_FN_M: + dev_dbg(ec->dev, "FN + M - medium performance mode\n"); + break; + case T14S_EC_EVT_KEY_FN_H: + dev_dbg(ec->dev, "FN + H - high performance mode\n"); + break; + case T14S_EC_EVT_KEY_FN_T: + dev_dbg(ec->dev, "FN + T - toggle intelligent cooling mode\n"); + break; + case T14S_EC_EVT_KEY_FN_D: + dev_dbg(ec->dev, "FN + D - toggle privacy guard mode\n"); + break; + default: + dev_info(ec->dev, "Unknown EC event: 0x%02x\n", val); + break; + } + + return IRQ_HANDLED; +} + +static int t14s_ec_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct t14s_ec *ec; + int ret; + + ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL); + if (!ec) + return -ENOMEM; + + ec->dev = dev; + + ec->regmap = devm_regmap_init(dev, &t14s_ec_regmap_bus, + ec, &t14s_ec_regmap_config); + if (IS_ERR(ec->regmap)) + return dev_err_probe(dev, PTR_ERR(ec->regmap), + "Failed to init regmap\n"); + + ret = devm_request_threaded_irq(dev, client->irq, NULL, + t14s_ec_irq_handler, + IRQF_ONESHOT, dev_name(dev), ec); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get IRQ\n"); + + ret = t14s_leds_probe(ec); + if (ret < 0) + return ret; + + ret = t14s_kbd_backlight_probe(ec); + if (ret < 0) + return ret; + + ret = t14s_kbd_audio_led_probe(ec); + if (ret < 0) + return ret; + + ret = t14s_input_probe(ec); + if (ret < 0) + return ret; + + /* + * Disable wakeup support by default, because the driver currently does + * not support masking any events and the laptop should not wake up when + * the LID is closed. + */ + device_wakeup_disable(dev); + + return 0; +} + +static const struct of_device_id t14s_ec_of_match[] = { + { .compatible = "lenovo,thinkpad-t14s-ec" }, + {} +}; +MODULE_DEVICE_TABLE(of, t14s_ec_of_match); + +static const struct i2c_device_id t14s_ec_i2c_id_table[] = { + { "thinkpad-t14s-ec", }, + {} +}; +MODULE_DEVICE_TABLE(i2c, t14s_ec_i2c_id_table); + +static struct i2c_driver t14s_ec_i2c_driver = { + .driver = { + .name = "thinkpad-t14s-ec", + .of_match_table = t14s_ec_of_match, + }, + .probe = t14s_ec_probe, + .id_table = t14s_ec_i2c_id_table, +}; +module_i2c_driver(t14s_ec_i2c_driver); + +MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>"); +MODULE_DESCRIPTION("Lenovo Thinkpad T14s Embedded Controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 6d238e120dce..46e62feeda3c 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -118,6 +118,18 @@ config XIAOMI_WMI To compile this driver as a module, choose M here: the module will be called xiaomi-wmi. +config REDMI_WMI + tristate "Redmibook WMI key driver" + depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + help + Say Y here if you want support for WMI-based hotkey events on + Xiaomi Redmibook devices. + + To compile this driver as a module, choose M here: the module will + be called redmi-wmi. + config GIGABYTE_WMI tristate "Gigabyte WMI temperature driver" depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index a0c5848513e3..c7db2a88c11a 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o obj-$(CONFIG_MXM_WMI) += mxm-wmi.o obj-$(CONFIG_NVIDIA_WMI_EC_BACKLIGHT) += nvidia-wmi-ec-backlight.o obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o +obj-$(CONFIG_REDMI_WMI) += redmi-wmi.o obj-$(CONFIG_GIGABYTE_WMI) += gigabyte-wmi.o # Acer diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index a94009203e01..d0b74d243ce4 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -495,12 +495,12 @@ static int init_acpi(struct device *dev) if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) { ret = hsmp_get_tbl_dram_base(sock_ind); if (ret) - dev_err(dev, "Failed to init metric table\n"); + dev_info(dev, "Failed to init metric table\n"); } ret = hsmp_create_sensor(dev, sock_ind); if (ret) - dev_err(dev, "Failed to register HSMP sensors with hwmon\n"); + dev_info(dev, "Failed to register HSMP sensors with hwmon\n"); dev_set_drvdata(dev, &hsmp_pdev->sock[sock_ind]); diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c index 22f50b6235d6..e07f68575055 100644 --- a/drivers/platform/x86/amd/hsmp/plat.c +++ b/drivers/platform/x86/amd/hsmp/plat.c @@ -189,13 +189,13 @@ static int init_platform_device(struct device *dev) if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) { ret = hsmp_get_tbl_dram_base(i); if (ret) - dev_err(dev, "Failed to init metric table\n"); + dev_info(dev, "Failed to init metric table\n"); } /* Register with hwmon interface for reporting power */ ret = hsmp_create_sensor(dev, i); if (ret) - dev_err(dev, "Failed to register HSMP sensors with hwmon\n"); + dev_info(dev, "Failed to register HSMP sensors with hwmon\n"); } return 0; diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c index f75f7ecd8cd9..13c4fec2c7ef 100644 --- a/drivers/platform/x86/amd/pmf/acpi.c +++ b/drivers/platform/x86/amd/pmf/acpi.c @@ -161,6 +161,11 @@ int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index) return !!(pdev->supported_func & BIT(index - 1)); } +int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev) +{ + return !!(pdev->notifications & CUSTOM_BIOS_INPUT_BITS); +} + int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev, struct amd_pmf_apts_granular_output *data, u32 apts_idx) { @@ -315,12 +320,26 @@ int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req)); } +int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req) +{ + return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req)); +} + int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req) { return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req)); } +static void amd_pmf_handle_early_preq(struct amd_pmf_dev *pdev) +{ + if (!pdev->cb_flag) + return; + + amd_pmf_invoke_cmd_enact(pdev); + pdev->cb_flag = false; +} + static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data) { struct amd_pmf_dev *pmf_dev = data; @@ -329,8 +348,32 @@ static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data) guard(mutex)(&pmf_dev->cb_mutex); ret = apmf_get_sbios_requests_v2(pmf_dev, &pmf_dev->req); - if (ret) + if (ret) { dev_err(pmf_dev->dev, "Failed to get v2 SBIOS requests: %d\n", ret); + return; + } + + dev_dbg(pmf_dev->dev, "Pending request (preq): 0x%x\n", pmf_dev->req.pending_req); + + amd_pmf_handle_early_preq(pmf_dev); +} + +static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data) +{ + struct amd_pmf_dev *pmf_dev = data; + int ret; + + guard(mutex)(&pmf_dev->cb_mutex); + + ret = apmf_get_sbios_requests_v1(pmf_dev, &pmf_dev->req1); + if (ret) { + dev_err(pmf_dev->dev, "Failed to get v1 SBIOS requests: %d\n", ret); + return; + } + + dev_dbg(pmf_dev->dev, "Pending request (preq1): 0x%x\n", pmf_dev->req1.pending_req); + + amd_pmf_handle_early_preq(pmf_dev); } static void apmf_event_handler(acpi_handle handle, u32 event, void *data) @@ -385,6 +428,7 @@ static int apmf_if_verify_interface(struct amd_pmf_dev *pdev) pdev->pmf_if_version = output.version; + pdev->notifications = output.notification_mask; return 0; } @@ -421,6 +465,11 @@ int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_ return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_DC, data, sizeof(*data)); } +static apmf_event_handler_t apmf_event_handlers[] = { + [PMF_IF_V1] = apmf_event_handler_v1, + [PMF_IF_V2] = apmf_event_handler_v2, +}; + int apmf_install_handler(struct amd_pmf_dev *pmf_dev) { acpi_handle ahandle = ACPI_HANDLE(pmf_dev->dev); @@ -440,13 +489,26 @@ int apmf_install_handler(struct amd_pmf_dev *pmf_dev) apmf_event_handler(ahandle, 0, pmf_dev); } - if (pmf_dev->smart_pc_enabled && pmf_dev->pmf_if_version == PMF_IF_V2) { + if (!pmf_dev->smart_pc_enabled) + return -EINVAL; + + switch (pmf_dev->pmf_if_version) { + case PMF_IF_V1: + if (!is_apmf_bios_input_notifications_supported(pmf_dev)) + break; + fallthrough; + case PMF_IF_V2: status = acpi_install_notify_handler(ahandle, ACPI_ALL_NOTIFY, - apmf_event_handler_v2, pmf_dev); + apmf_event_handlers[pmf_dev->pmf_if_version], pmf_dev); if (ACPI_FAILURE(status)) { - dev_err(pmf_dev->dev, "failed to install notify handler for custom BIOS inputs\n"); + dev_err(pmf_dev->dev, + "failed to install notify handler v%d for custom BIOS inputs\n", + pmf_dev->pmf_if_version); return -ENODEV; } + break; + default: + break; } return 0; @@ -500,8 +562,21 @@ void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev) is_apmf_func_supported(pmf_dev, APMF_FUNC_SBIOS_REQUESTS)) acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, apmf_event_handler); - if (pmf_dev->smart_pc_enabled && pmf_dev->pmf_if_version == PMF_IF_V2) - acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, apmf_event_handler_v2); + if (!pmf_dev->smart_pc_enabled) + return; + + switch (pmf_dev->pmf_if_version) { + case PMF_IF_V1: + if (!is_apmf_bios_input_notifications_supported(pmf_dev)) + break; + fallthrough; + case PMF_IF_V2: + acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, + apmf_event_handlers[pmf_dev->pmf_if_version]); + break; + default: + break; + } } int apmf_acpi_init(struct amd_pmf_dev *pmf_dev) diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 45b60238d527..bd19f2a6bc78 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -93,6 +93,8 @@ struct cookie_header { #define PMF_POLICY_BIOS_OUTPUT_1 10 #define PMF_POLICY_BIOS_OUTPUT_2 11 #define PMF_POLICY_P3T 38 +#define PMF_POLICY_PMF_PPT 54 +#define PMF_POLICY_PMF_PPT_APU_ONLY 55 #define PMF_POLICY_BIOS_OUTPUT_3 57 #define PMF_POLICY_BIOS_OUTPUT_4 58 #define PMF_POLICY_BIOS_OUTPUT_5 59 @@ -116,6 +118,9 @@ struct cookie_header { #define PMF_IF_V2 2 #define APTS_MAX_STATES 16 +#define CUSTOM_BIOS_INPUT_BITS GENMASK(16, 7) + +typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); /* APTS PMF BIOS Interface */ struct amd_pmf_apts_output { @@ -184,6 +189,24 @@ struct apmf_sbios_req { u8 skin_temp_hs2; } __packed; +/* As per APMF spec 1.3 */ +struct apmf_sbios_req_v1 { + u16 size; + u32 pending_req; + u8 rsvd; + u8 cql_event; + u8 amt_event; + u32 fppt; + u32 sppt; + u32 sppt_apu_only; + u32 spl; + u32 stt_min_limit; + u8 skin_temp_apu; + u8 skin_temp_hs2; + u8 enable_cnqf; + u32 custom_policy[10]; +} __packed; + struct apmf_sbios_req_v2 { u16 size; u32 pending_req; @@ -331,6 +354,10 @@ enum power_modes_v2 { POWER_MODE_V2_MAX, }; +struct pmf_bios_inputs_prev { + u32 custom_bios_inputs[10]; +}; + struct amd_pmf_dev { void __iomem *regbase; void __iomem *smu_virt_addr; @@ -375,6 +402,10 @@ struct amd_pmf_dev { struct resource *res; struct apmf_sbios_req_v2 req; /* To get custom bios pending request */ struct mutex cb_mutex; + u32 notifications; + struct apmf_sbios_req_v1 req1; + struct pmf_bios_inputs_prev cb_prev; /* To preserve custom BIOS inputs */ + bool cb_flag; /* To handle first custom BIOS input */ }; struct apmf_sps_prop_granular_v2 { @@ -621,14 +652,35 @@ enum ta_slider { TA_MAX, }; -enum apmf_smartpc_custom_bios_inputs { - APMF_SMARTPC_CUSTOM_BIOS_INPUT1, - APMF_SMARTPC_CUSTOM_BIOS_INPUT2, +struct amd_pmf_pb_bitmap { + const char *name; + u32 bit_mask; +}; + +static const struct amd_pmf_pb_bitmap custom_bios_inputs[] __used = { + {"NOTIFY_CUSTOM_BIOS_INPUT1", BIT(5)}, + {"NOTIFY_CUSTOM_BIOS_INPUT2", BIT(6)}, + {"NOTIFY_CUSTOM_BIOS_INPUT3", BIT(7)}, + {"NOTIFY_CUSTOM_BIOS_INPUT4", BIT(8)}, + {"NOTIFY_CUSTOM_BIOS_INPUT5", BIT(9)}, + {"NOTIFY_CUSTOM_BIOS_INPUT6", BIT(10)}, + {"NOTIFY_CUSTOM_BIOS_INPUT7", BIT(11)}, + {"NOTIFY_CUSTOM_BIOS_INPUT8", BIT(12)}, + {"NOTIFY_CUSTOM_BIOS_INPUT9", BIT(13)}, + {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(14)}, }; -enum apmf_preq_smartpc { - NOTIFY_CUSTOM_BIOS_INPUT1 = 5, - NOTIFY_CUSTOM_BIOS_INPUT2, +static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = { + {"NOTIFY_CUSTOM_BIOS_INPUT1", BIT(7)}, + {"NOTIFY_CUSTOM_BIOS_INPUT2", BIT(8)}, + {"NOTIFY_CUSTOM_BIOS_INPUT3", BIT(9)}, + {"NOTIFY_CUSTOM_BIOS_INPUT4", BIT(10)}, + {"NOTIFY_CUSTOM_BIOS_INPUT5", BIT(11)}, + {"NOTIFY_CUSTOM_BIOS_INPUT6", BIT(12)}, + {"NOTIFY_CUSTOM_BIOS_INPUT7", BIT(13)}, + {"NOTIFY_CUSTOM_BIOS_INPUT8", BIT(14)}, + {"NOTIFY_CUSTOM_BIOS_INPUT9", BIT(15)}, + {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)}, }; enum platform_type { @@ -677,6 +729,8 @@ struct pmf_action_table { u32 stt_skintemp_apu; /* in C */ u32 stt_skintemp_hs2; /* in C */ u32 p3t_limit; /* in mW */ + u32 pmf_ppt; /* in mW */ + u32 pmf_ppt_apu_only; /* in mW */ }; /* Input conditions */ @@ -686,8 +740,7 @@ struct ta_pmf_condition_info { u32 power_slider; u32 lid_state; bool user_present; - u32 bios_input1; - u32 bios_input2; + u32 bios_input_1[2]; u32 monitor_count; u32 rsvd2[2]; u32 bat_design; @@ -711,7 +764,9 @@ struct ta_pmf_condition_info { u32 workload_type; u32 display_type; u32 display_state; - u32 rsvd5[150]; + u32 rsvd5_1[17]; + u32 bios_input_2[8]; + u32 rsvd5[125]; }; struct ta_pmf_load_policy_table { @@ -737,6 +792,7 @@ struct ta_pmf_enact_table { struct ta_pmf_action { u32 action_index; u32 value; + u32 spl_arg; }; /* Output actions from TA */ @@ -778,6 +834,7 @@ int apmf_os_power_slider_update(struct amd_pmf_dev *dev, u8 flag); int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer); int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag); u32 fixp_q88_fromint(u32 val); +int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev); /* SPS Layer */ int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf); @@ -805,6 +862,7 @@ void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev); void amd_pmf_deinit_auto_mode(struct amd_pmf_dev *dev); void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t time_elapsed_ms); int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req); +int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req); int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v2 *req); void amd_pmf_update_2_cql(struct amd_pmf_dev *dev, bool is_cql_event); @@ -828,5 +886,6 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq /* Smart PC - TA interfaces */ void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); +int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev); #endif /* PMF_H */ diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index 1d90f9382024..85192c7536b8 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -70,8 +70,22 @@ static const char *ta_slider_as_str(unsigned int state) } } +static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) +{ + switch (index) { + case 0 ... 1: + return in->ev_info.bios_input_1[index]; + case 2 ... 9: + return in->ev_info.bios_input_2[index - 2]; + default: + return 0; + } +} + void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) { + int i; + dev_dbg(dev->dev, "==== TA inputs START ====\n"); dev_dbg(dev->dev, "Slider State: %s\n", ta_slider_as_str(in->ev_info.power_slider)); dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source)); @@ -90,33 +104,81 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table * dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type)); dev_dbg(dev->dev, "Laptop placement: %s\n", laptop_placement_as_str(in->ev_info.device_state)); - dev_dbg(dev->dev, "Custom BIOS input1: %u\n", in->ev_info.bios_input1); - dev_dbg(dev->dev, "Custom BIOS input2: %u\n", in->ev_info.bios_input2); + for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) + dev_dbg(dev->dev, "Custom BIOS input%d: %u\n", i + 1, + amd_pmf_get_ta_custom_bios_inputs(in, i)); dev_dbg(dev->dev, "==== TA inputs END ====\n"); } #else void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) {} #endif +/* + * This helper function sets the appropriate BIOS input value in the TA enact + * table based on the provided index. We need this approach because the custom + * BIOS input array is not continuous, due to the existing TA structure layout. + */ +static void amd_pmf_set_ta_custom_bios_input(struct ta_pmf_enact_table *in, int index, u32 value) +{ + switch (index) { + case 0 ... 1: + in->ev_info.bios_input_1[index] = value; + break; + case 2 ... 9: + in->ev_info.bios_input_2[index - 2] = value; + break; + default: + return; + } +} + +static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, u32 pending_req, + const struct amd_pmf_pb_bitmap *inputs, + const u32 *custom_policy, struct ta_pmf_enact_table *in) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) { + if (!(pending_req & inputs[i].bit_mask)) + continue; + amd_pmf_set_ta_custom_bios_input(in, i, custom_policy[i]); + pdev->cb_prev.custom_bios_inputs[i] = custom_policy[i]; + dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, custom_policy[i]); + } +} + static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev, struct ta_pmf_enact_table *in) { - if (!pdev->req.pending_req) + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) + amd_pmf_set_ta_custom_bios_input(in, i, pdev->cb_prev.custom_bios_inputs[i]); + + if (!(pdev->req.pending_req || pdev->req1.pending_req)) return; - switch (pdev->req.pending_req) { - case BIT(NOTIFY_CUSTOM_BIOS_INPUT1): - in->ev_info.bios_input1 = pdev->req.custom_policy[APMF_SMARTPC_CUSTOM_BIOS_INPUT1]; + if (!pdev->smart_pc_enabled) + return; + + switch (pdev->pmf_if_version) { + case PMF_IF_V1: + if (!is_apmf_bios_input_notifications_supported(pdev)) + return; + amd_pmf_update_bios_inputs(pdev, pdev->req1.pending_req, custom_bios_inputs_v1, + pdev->req1.custom_policy, in); break; - case BIT(NOTIFY_CUSTOM_BIOS_INPUT2): - in->ev_info.bios_input2 = pdev->req.custom_policy[APMF_SMARTPC_CUSTOM_BIOS_INPUT2]; + case PMF_IF_V2: + amd_pmf_update_bios_inputs(pdev, pdev->req.pending_req, custom_bios_inputs, + pdev->req.custom_policy, in); break; default: - dev_dbg(pdev->dev, "Invalid preq for BIOS input: 0x%x\n", pdev->req.pending_req); + break; } /* Clear pending requests after handling */ memset(&pdev->req, 0, sizeof(pdev->req)); + memset(&pdev->req1, 0, sizeof(pdev->req1)); } static void amd_pmf_get_c0_residency(u16 *core_res, size_t size, struct ta_pmf_enact_table *in) diff --git a/drivers/platform/x86/amd/pmf/sps.c b/drivers/platform/x86/amd/pmf/sps.c index 49e14ca94a9e..c28f3c5744c2 100644 --- a/drivers/platform/x86/amd/pmf/sps.c +++ b/drivers/platform/x86/amd/pmf/sps.c @@ -283,7 +283,7 @@ int amd_pmf_set_sps_power_limits(struct amd_pmf_dev *pmf) bool is_pprof_balanced(struct amd_pmf_dev *pmf) { - return (pmf->current_profile == PLATFORM_PROFILE_BALANCED) ? true : false; + return pmf->current_profile == PLATFORM_PROFILE_BALANCED; } static int amd_pmf_profile_get(struct device *dev, diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c index 4f626ebcb619..6e8116bef4f6 100644 --- a/drivers/platform/x86/amd/pmf/tee-if.c +++ b/drivers/platform/x86/amd/pmf/tee-if.c @@ -147,6 +147,22 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_ } break; + case PMF_POLICY_PMF_PPT: + if (dev->prev_data->pmf_ppt != val) { + amd_pmf_send_cmd(dev, SET_PMF_PPT, false, val, NULL); + dev_dbg(dev->dev, "update PMF PPT: %u\n", val); + dev->prev_data->pmf_ppt = val; + } + break; + + case PMF_POLICY_PMF_PPT_APU_ONLY: + if (dev->prev_data->pmf_ppt_apu_only != val) { + amd_pmf_send_cmd(dev, SET_PMF_PPT_APU_ONLY, false, val, NULL); + dev_dbg(dev->dev, "update PMF PPT APU ONLY: %u\n", val); + dev->prev_data->pmf_ppt_apu_only = val; + } + break; + case PMF_POLICY_SYSTEM_STATE: switch (val) { case 0: @@ -209,7 +225,7 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_ } } -static int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev) +int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev) { struct ta_pmf_shared_memory *ta_sm = NULL; struct ta_pmf_enact_result *out = NULL; @@ -561,8 +577,10 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev) ret = amd_pmf_start_policy_engine(dev); dev_dbg(dev->dev, "start policy engine ret: %d\n", ret); status = ret == TA_PMF_TYPE_SUCCESS; - if (status) + if (status) { + dev->cb_flag = true; break; + } amd_pmf_tee_deinit(dev); } diff --git a/drivers/platform/x86/barco-p50-gpio.c b/drivers/platform/x86/barco-p50-gpio.c index 28012eebdb10..6f13e81f98fb 100644 --- a/drivers/platform/x86/barco-p50-gpio.c +++ b/drivers/platform/x86/barco-p50-gpio.c @@ -11,6 +11,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/delay.h> +#include <linux/dev_printk.h> #include <linux/dmi.h> #include <linux/err.h> #include <linux/io.h> @@ -18,10 +19,11 @@ #include <linux/leds.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/gpio_keys.h> #include <linux/gpio/driver.h> #include <linux/gpio/machine.h> -#include <linux/input.h> +#include <linux/gpio/property.h> +#include <linux/input-event-codes.h> +#include <linux/property.h> #define DRIVER_NAME "barco-p50-gpio" @@ -78,44 +80,57 @@ static const char * const gpio_names[] = { [P50_GPIO_LINE_BTN] = "identify-button", }; - -static struct gpiod_lookup_table p50_gpio_led_table = { - .dev_id = "leds-gpio", - .table = { - GPIO_LOOKUP_IDX(DRIVER_NAME, P50_GPIO_LINE_LED, NULL, 0, GPIO_ACTIVE_HIGH), - {} - } +static const struct software_node gpiochip_node = { + .name = DRIVER_NAME, }; /* GPIO LEDs */ -static struct gpio_led leds[] = { - { .name = "identify" } +static const struct software_node gpio_leds_node = { + .name = "gpio-leds-identify", }; -static struct gpio_led_platform_data leds_pdata = { - .num_leds = ARRAY_SIZE(leds), - .leds = leds, +static const struct property_entry identify_led_props[] = { + PROPERTY_ENTRY_GPIO("gpios", &gpiochip_node, P50_GPIO_LINE_LED, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node identify_led_node = { + .parent = &gpio_leds_node, + .name = "identify", + .properties = identify_led_props, }; /* GPIO keyboard */ -static struct gpio_keys_button buttons[] = { - { - .code = KEY_VENDOR, - .gpio = P50_GPIO_LINE_BTN, - .active_low = 1, - .type = EV_KEY, - .value = 1, - }, +static const struct property_entry gpio_keys_props[] = { + PROPERTY_ENTRY_STRING("label", "identify"), + PROPERTY_ENTRY_U32("poll-interval", 100), + { } }; -static struct gpio_keys_platform_data keys_pdata = { - .buttons = buttons, - .nbuttons = ARRAY_SIZE(buttons), - .poll_interval = 100, - .rep = 0, - .name = "identify", +static const struct software_node gpio_keys_node = { + .name = "gpio-keys-identify", + .properties = gpio_keys_props, }; +static struct property_entry vendor_key_props[] = { + PROPERTY_ENTRY_U32("linux,code", KEY_VENDOR), + PROPERTY_ENTRY_GPIO("gpios", &gpiochip_node, P50_GPIO_LINE_BTN, GPIO_ACTIVE_LOW), + { } +}; + +static const struct software_node vendor_key_node = { + .parent = &gpio_keys_node, + .properties = vendor_key_props, +}; + +static const struct software_node *p50_swnodes[] = { + &gpiochip_node, + &gpio_leds_node, + &identify_led_node, + &gpio_keys_node, + &vendor_key_node, + NULL +}; /* low level access routines */ @@ -285,6 +300,16 @@ static int p50_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) static int p50_gpio_probe(struct platform_device *pdev) { + struct platform_device_info key_info = { + .name = "gpio-keys-polled", + .id = PLATFORM_DEVID_NONE, + .parent = &pdev->dev, + }; + struct platform_device_info led_info = { + .name = "leds-gpio", + .id = PLATFORM_DEVID_NONE, + .parent = &pdev->dev, + }; struct p50_gpio *p50; struct resource *res; int ret; @@ -339,25 +364,20 @@ static int p50_gpio_probe(struct platform_device *pdev) return ret; } - gpiod_add_lookup_table(&p50_gpio_led_table); - - p50->leds_pdev = platform_device_register_data(&pdev->dev, - "leds-gpio", PLATFORM_DEVID_NONE, &leds_pdata, sizeof(leds_pdata)); + ret = software_node_register_node_group(p50_swnodes); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to register software nodes"); + led_info.fwnode = software_node_fwnode(&gpio_leds_node); + p50->leds_pdev = platform_device_register_full(&led_info); if (IS_ERR(p50->leds_pdev)) { ret = PTR_ERR(p50->leds_pdev); dev_err(&pdev->dev, "Could not register leds-gpio: %d\n", ret); goto err_leds; } - /* gpio-keys-polled uses old-style gpio interface, pass the right identifier */ - buttons[0].gpio += p50->gc.base; - - p50->keys_pdev = - platform_device_register_data(&pdev->dev, "gpio-keys-polled", - PLATFORM_DEVID_NONE, - &keys_pdata, sizeof(keys_pdata)); - + key_info.fwnode = software_node_fwnode(&gpio_keys_node); + p50->keys_pdev = platform_device_register_full(&key_info); if (IS_ERR(p50->keys_pdev)) { ret = PTR_ERR(p50->keys_pdev); dev_err(&pdev->dev, "Could not register gpio-keys-polled: %d\n", ret); @@ -369,7 +389,7 @@ static int p50_gpio_probe(struct platform_device *pdev) err_keys: platform_device_unregister(p50->leds_pdev); err_leds: - gpiod_remove_lookup_table(&p50_gpio_led_table); + software_node_unregister_node_group(p50_swnodes); return ret; } @@ -381,7 +401,7 @@ static void p50_gpio_remove(struct platform_device *pdev) platform_device_unregister(p50->keys_pdev); platform_device_unregister(p50->leds_pdev); - gpiod_remove_lookup_table(&p50_gpio_led_table); + software_node_unregister_node_group(p50_swnodes); } static struct platform_driver p50_gpio_driver = { diff --git a/drivers/platform/x86/dell/dell_rbu.c b/drivers/platform/x86/dell/dell_rbu.c index 2a140d1c656a..403df9bd9522 100644 --- a/drivers/platform/x86/dell/dell_rbu.c +++ b/drivers/platform/x86/dell/dell_rbu.c @@ -232,7 +232,8 @@ static int packetize_data(const u8 *data, size_t length) done = 1; } - if ((rc = create_packet(temp, packet_length))) + rc = create_packet(temp, packet_length); + if (rc) return rc; pr_debug("%p:%td\n", temp, (end - temp)); @@ -276,7 +277,7 @@ static int do_packet_read(char *data, struct packet_data *newpacket, return bytes_copied; } -static int packet_read_list(char *data, size_t * pread_length) +static int packet_read_list(char *data, size_t *pread_length) { struct packet_data *newpacket; int temp_count = 0; @@ -445,7 +446,8 @@ static ssize_t read_packet_data(char *buffer, loff_t pos, size_t count) bytes_left = rbu_data.imagesize - pos; data_length = min(bytes_left, count); - if ((retval = packet_read_list(ptempBuf, &data_length)) < 0) + retval = packet_read_list(ptempBuf, &data_length); + if (retval < 0) goto read_rbu_data_exit; if ((pos + count) > rbu_data.imagesize) { diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index bdfb8a800c54..1505fc3ef7a8 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -129,6 +129,7 @@ skl_int3472_gpiod_get_from_temp_lookup(struct int3472_discrete_device *int3472, * @hid: The ACPI HID of the device without the instance number e.g. INT347E * @type_from: The GPIO type from ACPI ?SDT * @type_to: The assigned GPIO type, typically same as @type_from + * @enable_time_us: Enable time in usec for GPIOs mapped to regulators * @con_id: The name of the GPIO for the device * @polarity_low: GPIO_ACTIVE_LOW true if the @polarity_low is true, * GPIO_ACTIVE_HIGH otherwise @@ -138,18 +139,36 @@ struct int3472_gpio_map { u8 type_from; u8 type_to; bool polarity_low; + unsigned int enable_time_us; const char *con_id; }; static const struct int3472_gpio_map int3472_gpio_map[] = { - /* mt9m114 designs declare a powerdown pin which controls the regulators */ - { "INT33F0", INT3472_GPIO_TYPE_POWERDOWN, INT3472_GPIO_TYPE_POWER_ENABLE, false, "vdd" }, - /* ov7251 driver / DT-bindings expect "enable" as con_id for reset */ - { "INT347E", INT3472_GPIO_TYPE_RESET, INT3472_GPIO_TYPE_RESET, false, "enable" }, + { /* mt9m114 designs declare a powerdown pin which controls the regulators */ + .hid = "INT33F0", + .type_from = INT3472_GPIO_TYPE_POWERDOWN, + .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, + .con_id = "vdd", + .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, + }, + { /* ov7251 driver / DT-bindings expect "enable" as con_id for reset */ + .hid = "INT347E", + .type_from = INT3472_GPIO_TYPE_RESET, + .type_to = INT3472_GPIO_TYPE_RESET, + .con_id = "enable", + }, + { /* ov08x40's handshake pin needs a 45 ms delay on some HP laptops */ + .hid = "OVTI08F4", + .type_from = INT3472_GPIO_TYPE_HANDSHAKE, + .type_to = INT3472_GPIO_TYPE_HANDSHAKE, + .con_id = "dvdd", + .enable_time_us = 45 * USEC_PER_MSEC, + }, }; static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3472, u8 *type, - const char **con_id, unsigned long *gpio_flags) + const char **con_id, unsigned long *gpio_flags, + unsigned int *enable_time_us) { struct acpi_device *adev = int3472->sensor; unsigned int i; @@ -173,9 +192,12 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 *gpio_flags = int3472_gpio_map[i].polarity_low ? GPIO_ACTIVE_LOW : GPIO_ACTIVE_HIGH; *con_id = int3472_gpio_map[i].con_id; + *enable_time_us = int3472_gpio_map[i].enable_time_us; return; } + *enable_time_us = GPIO_REGULATOR_ENABLE_TIME; + switch (*type) { case INT3472_GPIO_TYPE_RESET: *con_id = "reset"; @@ -204,6 +226,8 @@ static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3 case INT3472_GPIO_TYPE_HANDSHAKE: *con_id = "dvdd"; *gpio_flags = GPIO_ACTIVE_HIGH; + /* Setups using a handshake pin need 25 ms enable delay */ + *enable_time_us = 25 * USEC_PER_MSEC; break; default: *con_id = "unknown"; @@ -249,13 +273,15 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, void *data) { struct int3472_discrete_device *int3472 = data; + const char *second_sensor = NULL; struct acpi_resource_gpio *agpio; + unsigned int enable_time_us; u8 active_value, pin, type; + unsigned long gpio_flags; union acpi_object *obj; struct gpio_desc *gpio; const char *err_msg; const char *con_id; - unsigned long gpio_flags; int ret; if (!acpi_gpio_get_io_resource(ares, &agpio)) @@ -278,7 +304,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, type = FIELD_GET(INT3472_GPIO_DSM_TYPE, obj->integer.value); - int3472_get_con_id_and_polarity(int3472, &type, &con_id, &gpio_flags); + int3472_get_con_id_and_polarity(int3472, &type, &con_id, &gpio_flags, &enable_time_us); pin = FIELD_GET(INT3472_GPIO_DSM_PIN, obj->integer.value); /* Pin field is not really used under Windows and wraps around at 8 bits */ @@ -328,21 +354,13 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, break; case INT3472_GPIO_TYPE_POWER_ENABLE: - ret = skl_int3472_register_regulator(int3472, gpio, - GPIO_REGULATOR_ENABLE_TIME, - con_id, - int3472->quirks.avdd_second_sensor); - if (ret) - err_msg = "Failed to map power-enable to sensor\n"; - - break; + second_sensor = int3472->quirks.avdd_second_sensor; + fallthrough; case INT3472_GPIO_TYPE_HANDSHAKE: - /* Setups using a handshake pin need 25 ms enable delay */ - ret = skl_int3472_register_regulator(int3472, gpio, - 25 * USEC_PER_MSEC, - con_id, NULL); + ret = skl_int3472_register_regulator(int3472, gpio, enable_time_us, + con_id, second_sensor); if (ret) - err_msg = "Failed to map handshake to sensor\n"; + err_msg = "Failed to register regulator\n"; break; default: /* Never reached */ diff --git a/drivers/platform/x86/intel/pmc/Makefile b/drivers/platform/x86/intel/pmc/Makefile index 5f68c8503a56..bb960c8721d7 100644 --- a/drivers/platform/x86/intel/pmc/Makefile +++ b/drivers/platform/x86/intel/pmc/Makefile @@ -4,7 +4,7 @@ # intel_pmc_core-y := core.o spt.o cnp.o icl.o \ - tgl.o adl.o mtl.o arl.o lnl.o ptl.o + tgl.o adl.o mtl.o arl.o lnl.o ptl.o wcl.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv-y := pltdrv.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core_pltdrv.o diff --git a/drivers/platform/x86/intel/pmc/arl.c b/drivers/platform/x86/intel/pmc/arl.c index 9d66d65e7577..17ad87b392ab 100644 --- a/drivers/platform/x86/intel/pmc/arl.c +++ b/drivers/platform/x86/intel/pmc/arl.c @@ -725,9 +725,11 @@ struct pmc_dev_info arl_pmc_dev = { .dmu_guid = ARL_PMT_DMU_GUID, .regmap_list = arl_pmc_info_list, .map = &arl_socs_reg_map, + .sub_req_show = &pmc_core_substate_req_regs_fops, .suspend = cnl_suspend, .resume = arl_resume, .init = arl_core_init, + .sub_req = pmc_core_pmt_get_lpm_req, }; struct pmc_dev_info arl_h_pmc_dev = { @@ -735,7 +737,9 @@ struct pmc_dev_info arl_h_pmc_dev = { .dmu_guid = ARL_PMT_DMU_GUID, .regmap_list = arl_pmc_info_list, .map = &mtl_socm_reg_map, + .sub_req_show = &pmc_core_substate_req_regs_fops, .suspend = cnl_suspend, .resume = arl_h_resume, .init = arl_h_core_init, + .sub_req = pmc_core_pmt_get_lpm_req, }; diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c index d040290e80ff..ac3d19ae8c56 100644 --- a/drivers/platform/x86/intel/pmc/core.c +++ b/drivers/platform/x86/intel/pmc/core.c @@ -11,6 +11,11 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +enum header_type { + HEADER_STATUS, + HEADER_VALUE, +}; + #include <linux/bitfield.h> #include <linux/debugfs.h> #include <linux/delay.h> @@ -828,19 +833,86 @@ static int pmc_core_substate_l_sts_regs_show(struct seq_file *s, void *unused) } DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_l_sts_regs); -static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index) +static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index, + enum header_type type) { struct pmc_dev *pmcdev = s->private; int mode; - seq_printf(s, "%30s |", "Element"); + seq_printf(s, "%40s |", "Element"); pmc_for_each_mode(mode, pmcdev) seq_printf(s, " %9s |", pmc_lpm_modes[mode]); - seq_printf(s, " %9s |", "Status"); - seq_printf(s, " %11s |\n", "Live Status"); + if (type == HEADER_STATUS) { + seq_printf(s, " %9s |", "Status"); + seq_printf(s, " %11s |\n", "Live Status"); + } else { + seq_printf(s, " %9s |\n", "Value"); + } } +static int pmc_core_substate_blk_req_show(struct seq_file *s, void *unused) +{ + struct pmc_dev *pmcdev = s->private; + unsigned int pmc_idx; + + for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); pmc_idx++) { + const struct pmc_bit_map **maps; + unsigned int arr_size, r_idx; + u32 offset, counter; + u32 *lpm_req_regs; + struct pmc *pmc; + + pmc = pmcdev->pmcs[pmc_idx]; + if (!pmc || !pmc->lpm_req_regs) + continue; + + lpm_req_regs = pmc->lpm_req_regs; + maps = pmc->map->s0ix_blocker_maps; + offset = pmc->map->s0ix_blocker_offset; + arr_size = pmc_core_lpm_get_arr_size(maps); + + /* Display the header */ + pmc_core_substate_req_header_show(s, pmc_idx, HEADER_VALUE); + + for (r_idx = 0; r_idx < arr_size; r_idx++) { + const struct pmc_bit_map *map; + + for (map = maps[r_idx]; map->name; map++) { + int mode; + + if (!map->blk) + continue; + + counter = pmc_core_reg_read(pmc, offset); + seq_printf(s, "pmc%u: %34s |", pmc_idx, map->name); + pmc_for_each_mode(mode, pmcdev) { + bool required = *lpm_req_regs & BIT(mode); + + seq_printf(s, " %9s |", required ? "Required" : " "); + } + seq_printf(s, " %9u |\n", counter); + offset += map->blk * S0IX_BLK_SIZE; + lpm_req_regs++; + } + } + } + return 0; +} + +static int pmc_core_substate_blk_req_open(struct inode *inode, struct file *file) +{ + return single_open(file, pmc_core_substate_blk_req_show, inode->i_private); +} + +const struct file_operations pmc_core_substate_blk_req_fops = { + .owner = THIS_MODULE, + .open = pmc_core_substate_blk_req_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) { struct pmc_dev *pmcdev = s->private; @@ -872,7 +944,7 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) continue; /* Display the header */ - pmc_core_substate_req_header_show(s, pmc_index); + pmc_core_substate_req_header_show(s, pmc_index, HEADER_STATUS); /* Loop over maps */ for (mp = 0; mp < num_maps; mp++) { @@ -910,7 +982,7 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) } /* Display the element name in the first column */ - seq_printf(s, "pmc%d: %26s |", pmc_index, map[i].name); + seq_printf(s, "pmc%d: %34s |", pmc_index, map[i].name); /* Loop over the enabled states and display if required */ pmc_for_each_mode(mode, pmcdev) { @@ -931,7 +1003,19 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) } return 0; } -DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_req_regs); + +static int pmc_core_substate_req_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, pmc_core_substate_req_regs_show, inode->i_private); +} + +const struct file_operations pmc_core_substate_req_regs_fops = { + .owner = THIS_MODULE, + .open = pmc_core_substate_req_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; static unsigned int pmc_core_get_crystal_freq(void) { @@ -1160,7 +1244,7 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev) for (mode = 0; mode < LPM_MAX_NUM_MODES; mode++) pri_order[mode_order[mode]] = mode; else - dev_warn(&pmcdev->pdev->dev, + dev_dbg(&pmcdev->pdev->dev, "Assuming a default substate order for this platform\n"); /* @@ -1264,7 +1348,7 @@ static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev) debugfs_remove_recursive(pmcdev->dbgfs_dir); } -static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev) +static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info) { struct pmc *primary_pmc = pmcdev->pmcs[PMC_IDX_MAIN]; struct dentry *dir; @@ -1331,7 +1415,7 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev) if (primary_pmc->lpm_req_regs) { debugfs_create_file("substate_requirements", 0444, pmcdev->dbgfs_dir, pmcdev, - &pmc_core_substate_req_regs_fops); + pmc_dev_info->sub_req_show); } if (primary_pmc->map->pson_residency_offset && pmc_core_is_pson_residency_enabled(pmcdev)) { @@ -1399,36 +1483,22 @@ static u32 pmc_core_find_guid(struct pmc_info *list, const struct pmc_reg_map *m * +----+---------------------------------------------------------+ * */ -static int pmc_core_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct pci_dev *pcidev) +int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep) { - struct telem_endpoint *ep; const u8 *lpm_indices; int num_maps, mode_offset = 0; int ret, mode; int lpm_size; - u32 guid; lpm_indices = pmc->map->lpm_reg_index; num_maps = pmc->map->lpm_num_maps; lpm_size = LPM_MAX_NUM_MODES * num_maps; - guid = pmc_core_find_guid(pmcdev->regmap_list, pmc->map); - if (!guid) - return -ENXIO; - - ep = pmt_telem_find_and_register_endpoint(pcidev, guid, 0); - if (IS_ERR(ep)) { - dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %pe", ep); - return -EPROBE_DEFER; - } - pmc->lpm_req_regs = devm_kzalloc(&pmcdev->pdev->dev, lpm_size * sizeof(u32), GFP_KERNEL); - if (!pmc->lpm_req_regs) { - ret = -ENOMEM; - goto unregister_ep; - } + if (!pmc->lpm_req_regs) + return -ENOMEM; mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET; pmc_for_each_mode(mode, pmcdev) { @@ -1442,34 +1512,74 @@ static int pmc_core_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct if (ret) { dev_err(&pmcdev->pdev->dev, "couldn't read Low Power Mode requirements: %d\n", ret); - goto unregister_ep; + return ret; } ++req_offset; } mode_offset += LPM_REG_COUNT + LPM_MODE_OFFSET; } + return ret; +} + +int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc, + struct telem_endpoint *ep) +{ + u32 num_blocker, sample_offset; + unsigned int index; + u32 *req_offset; + int ret; -unregister_ep: - pmt_telem_unregister_endpoint(ep); + num_blocker = pmc->map->num_s0ix_blocker; + sample_offset = pmc->map->blocker_req_offset; - return ret; + pmc->lpm_req_regs = devm_kcalloc(&pmcdev->pdev->dev, num_blocker, + sizeof(u32), GFP_KERNEL); + if (!pmc->lpm_req_regs) + return -ENOMEM; + + req_offset = pmc->lpm_req_regs; + for (index = 0; index < num_blocker; index++, req_offset++) { + ret = pmt_telem_read32(ep, index + sample_offset, req_offset, 1); + if (ret) { + dev_err(&pmcdev->pdev->dev, + "couldn't read Low Power Mode requirements: %d\n", ret); + return ret; + } + } + return 0; } -static int pmc_core_ssram_get_lpm_reqs(struct pmc_dev *pmcdev, int func) +static int pmc_core_get_telem_info(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info) { struct pci_dev *pcidev __free(pci_dev_put) = NULL; + struct telem_endpoint *ep; unsigned int i; + u32 guid; int ret; - pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, func)); + pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, pmc_dev_info->pci_func)); if (!pcidev) return -ENODEV; for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) { - if (!pmcdev->pmcs[i]) + struct pmc *pmc; + + pmc = pmcdev->pmcs[i]; + if (!pmc) continue; - ret = pmc_core_get_lpm_req(pmcdev, pmcdev->pmcs[i], pcidev); + guid = pmc_core_find_guid(pmcdev->regmap_list, pmc->map); + if (!guid) + return -ENXIO; + + ep = pmt_telem_find_and_register_endpoint(pcidev, guid, 0); + if (IS_ERR(ep)) { + dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %pe", ep); + return -EPROBE_DEFER; + } + + ret = pmc_dev_info->sub_req(pmcdev, pmc, ep); + pmt_telem_unregister_endpoint(ep); if (ret) return ret; } @@ -1583,7 +1693,7 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info) pmc_core_punit_pmt_init(pmcdev, pmc_dev_info->dmu_guid); if (ssram) { - ret = pmc_core_ssram_get_lpm_reqs(pmcdev, pmc_dev_info->pci_func); + ret = pmc_core_get_telem_info(pmcdev, pmc_dev_info); if (ret) goto unmap_regbase; } @@ -1632,6 +1742,7 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = { X86_MATCH_VFM(INTEL_ARROWLAKE_U, &arl_h_pmc_dev), X86_MATCH_VFM(INTEL_LUNARLAKE_M, &lnl_pmc_dev), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, &ptl_pmc_dev), + X86_MATCH_VFM(INTEL_WILDCATLAKE_L, &wcl_pmc_dev), {} }; @@ -1758,7 +1869,7 @@ static int pmc_core_probe(struct platform_device *pdev) pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(primary_pmc); pmc_core_do_dmi_quirks(primary_pmc); - pmc_core_dbgfs_register(pmcdev); + pmc_core_dbgfs_register(pmcdev, pmc_dev_info); pm_report_max_hw_sleep(FIELD_MAX(SLP_S0_RES_COUNTER_MASK) * pmc_core_adjust_slp_s0_step(primary_pmc, 1)); diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index 4a94a4ee031e..f4dadb696a31 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -297,6 +297,12 @@ enum ppfear_regs { #define PTL_PMC_LTR_CUR_ASLT 0x1C28 #define PTL_PMC_LTR_CUR_PLT 0x1C2C #define PTL_PCD_PMC_MMIO_REG_LEN 0x31A8 +#define PTL_NUM_S0IX_BLOCKER 106 +#define PTL_BLK_REQ_OFFSET 55 + +/* Wildcat Lake */ +#define WCL_PMC_LTR_RESERVED 0x1B64 +#define WCL_PCD_PMC_MMIO_REG_LEN 0x3178 /* SSRAM PMC Device ID */ /* LNL */ @@ -306,6 +312,9 @@ enum ppfear_regs { #define PMC_DEVID_PTL_PCDH 0xe37f #define PMC_DEVID_PTL_PCDP 0xe47f +/* WCL */ +#define PMC_DEVID_WCL_PCDN 0x4d7f + /* ARL */ #define PMC_DEVID_ARL_SOCM 0x777f #define PMC_DEVID_ARL_SOCS 0xae7f @@ -344,6 +353,8 @@ struct pmc_bit_map { * @pm_read_disable_bit: Bit index to read PMC_READ_DISABLE * @slps0_dbg_offset: PWRMBASE offset to SLP_S0_DEBUG_REG* * @s0ix_blocker_offset PWRMBASE offset to S0ix blocker counter + * @num_s0ix_blocker: Number of S0ix blockers + * @blocker_req_offset: Telemetry offset to S0ix blocker low power mode substate requirement table * * Each PCH has unique set of register offsets and bit indexes. This structure * captures them to have a common implementation. @@ -369,6 +380,8 @@ struct pmc_reg_map { const u32 ltr_ignore_max; const u32 pm_vric1_offset; const u32 s0ix_blocker_offset; + const u32 num_s0ix_blocker; + const u32 blocker_req_offset; /* Low Power Mode registers */ const int lpm_num_maps; const int lpm_num_modes; @@ -474,18 +487,22 @@ enum pmc_index { * SSRAM support. * @map: Pointer to a pmc_reg_map struct that contains platform * specific attributes of the primary PMC + * @sub_req_show: File operations to show substate requirements * @suspend: Function to perform platform specific suspend * @resume: Function to perform platform specific resume * @init: Function to perform platform specific init action + * @sub_req: Function to achieve low power mode substate requirements */ struct pmc_dev_info { u8 pci_func; u32 dmu_guid; struct pmc_info *regmap_list; const struct pmc_reg_map *map; + const struct file_operations *sub_req_show; void (*suspend)(struct pmc_dev *pmcdev); int (*resume)(struct pmc_dev *pmcdev); int (*init)(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info); + int (*sub_req)(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep); }; extern const struct pmc_bit_map msr_map[]; @@ -505,6 +522,9 @@ extern const struct pmc_bit_map mtl_socm_vnn_misc_status_map[]; extern const struct pmc_bit_map mtl_socm_signal_status_map[]; extern const struct pmc_reg_map mtl_socm_reg_map; extern const struct pmc_reg_map mtl_ioep_reg_map; +extern const struct pmc_bit_map ptl_pcdp_clocksource_status_map[]; +extern const struct pmc_bit_map ptl_pcdp_vnn_req_status_3_map[]; +extern const struct pmc_bit_map ptl_pcdp_signal_status_map[]; void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev); int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore); @@ -528,9 +548,16 @@ extern struct pmc_dev_info arl_pmc_dev; extern struct pmc_dev_info arl_h_pmc_dev; extern struct pmc_dev_info lnl_pmc_dev; extern struct pmc_dev_info ptl_pmc_dev; +extern struct pmc_dev_info wcl_pmc_dev; void cnl_suspend(struct pmc_dev *pmcdev); int cnl_resume(struct pmc_dev *pmcdev); +int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep); +int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc, + struct telem_endpoint *ep); + +extern const struct file_operations pmc_core_substate_req_regs_fops; +extern const struct file_operations pmc_core_substate_blk_req_fops; #define pmc_for_each_mode(mode, pmcdev) \ for (unsigned int __i = 0, __cond; \ diff --git a/drivers/platform/x86/intel/pmc/lnl.c b/drivers/platform/x86/intel/pmc/lnl.c index da513c234714..6fa027e7071f 100644 --- a/drivers/platform/x86/intel/pmc/lnl.c +++ b/drivers/platform/x86/intel/pmc/lnl.c @@ -13,6 +13,10 @@ #include "core.h" +#define SOCM_LPM_REQ_GUID 0x15099748 + +static const u8 LNL_LPM_REG_INDEX[] = {0, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20}; + static const struct pmc_bit_map lnl_ltr_show_map[] = { {"SOUTHPORT_A", CNP_PMC_LTR_SPA}, {"SOUTHPORT_B", CNP_PMC_LTR_SPB}, @@ -528,6 +532,16 @@ static const struct pmc_reg_map lnl_socm_reg_map = { .lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET, .s0ix_blocker_maps = lnl_blk_maps, .s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET, + .lpm_reg_index = LNL_LPM_REG_INDEX, +}; + +static struct pmc_info lnl_pmc_info_list[] = { + { + .guid = SOCM_LPM_REQ_GUID, + .devid = PMC_DEVID_LNL_SOCM, + .map = &lnl_socm_reg_map, + }, + {} }; #define LNL_NPU_PCI_DEV 0x643e @@ -557,8 +571,12 @@ static int lnl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in } struct pmc_dev_info lnl_pmc_dev = { + .pci_func = 2, + .regmap_list = lnl_pmc_info_list, .map = &lnl_socm_reg_map, + .sub_req_show = &pmc_core_substate_req_regs_fops, .suspend = cnl_suspend, .resume = lnl_resume, .init = lnl_core_init, + .sub_req = pmc_core_pmt_get_lpm_req, }; diff --git a/drivers/platform/x86/intel/pmc/mtl.c b/drivers/platform/x86/intel/pmc/mtl.c index faa13a7ee688..0b87e10f864e 100644 --- a/drivers/platform/x86/intel/pmc/mtl.c +++ b/drivers/platform/x86/intel/pmc/mtl.c @@ -997,7 +997,9 @@ struct pmc_dev_info mtl_pmc_dev = { .dmu_guid = MTL_PMT_DMU_GUID, .regmap_list = mtl_pmc_info_list, .map = &mtl_socm_reg_map, + .sub_req_show = &pmc_core_substate_req_regs_fops, .suspend = cnl_suspend, .resume = mtl_resume, .init = mtl_core_init, + .sub_req = pmc_core_pmt_get_lpm_req, }; diff --git a/drivers/platform/x86/intel/pmc/ptl.c b/drivers/platform/x86/intel/pmc/ptl.c index 394515af60d6..1b35b84e06fa 100644 --- a/drivers/platform/x86/intel/pmc/ptl.c +++ b/drivers/platform/x86/intel/pmc/ptl.c @@ -10,6 +10,17 @@ #include "core.h" +/* PMC SSRAM PMT Telemetry GUIDS */ +#define PCDP_LPM_REQ_GUID 0x47179370 + +/* + * Die Mapping to Product. + * Product PCDDie + * PTL-H PCD-H + * PTL-P PCD-P + * PTL-U PCD-P + */ + static const struct pmc_bit_map ptl_pcdp_pfear_map[] = { {"PMC_0", BIT(0)}, {"FUSE_OSSE", BIT(1)}, @@ -162,7 +173,7 @@ static const struct pmc_bit_map ptl_pcdp_ltr_show_map[] = { {} }; -static const struct pmc_bit_map ptl_pcdp_clocksource_status_map[] = { +const struct pmc_bit_map ptl_pcdp_clocksource_status_map[] = { {"AON2_OFF_STS", BIT(0), 1}, {"AON3_OFF_STS", BIT(1), 0}, {"AON4_OFF_STS", BIT(2), 1}, @@ -382,7 +393,7 @@ static const struct pmc_bit_map ptl_pcdp_vnn_req_status_2_map[] = { {} }; -static const struct pmc_bit_map ptl_pcdp_vnn_req_status_3_map[] = { +const struct pmc_bit_map ptl_pcdp_vnn_req_status_3_map[] = { {"DTS0_VNN_REQ_STS", BIT(7), 0}, {"GPIOCOM5_VNN_REQ_STS", BIT(11), 1}, {} @@ -421,7 +432,7 @@ static const struct pmc_bit_map ptl_pcdp_vnn_misc_status_map[] = { {} }; -static const struct pmc_bit_map ptl_pcdp_signal_status_map[] = { +const struct pmc_bit_map ptl_pcdp_signal_status_map[] = { {"LSX_Wake0_STS", BIT(0), 0}, {"LSX_Wake1_STS", BIT(1), 0}, {"LSX_Wake2_STS", BIT(2), 0}, @@ -515,6 +526,22 @@ static const struct pmc_reg_map ptl_pcdp_reg_map = { .lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET, .s0ix_blocker_maps = ptl_pcdp_blk_maps, .s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET, + .num_s0ix_blocker = PTL_NUM_S0IX_BLOCKER, + .blocker_req_offset = PTL_BLK_REQ_OFFSET, +}; + +static struct pmc_info ptl_pmc_info_list[] = { + { + .guid = PCDP_LPM_REQ_GUID, + .devid = PMC_DEVID_PTL_PCDH, + .map = &ptl_pcdp_reg_map, + }, + { + .guid = PCDP_LPM_REQ_GUID, + .devid = PMC_DEVID_PTL_PCDP, + .map = &ptl_pcdp_reg_map, + }, + {} }; #define PTL_NPU_PCI_DEV 0xb03e @@ -543,8 +570,12 @@ static int ptl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in } struct pmc_dev_info ptl_pmc_dev = { + .pci_func = 2, + .regmap_list = ptl_pmc_info_list, .map = &ptl_pcdp_reg_map, + .sub_req_show = &pmc_core_substate_blk_req_fops, .suspend = cnl_suspend, .resume = ptl_resume, .init = ptl_core_init, + .sub_req = pmc_core_pmt_get_blk_sub_req, }; diff --git a/drivers/platform/x86/intel/pmc/ssram_telemetry.c b/drivers/platform/x86/intel/pmc/ssram_telemetry.c index 93579152188e..03fad9331fc0 100644 --- a/drivers/platform/x86/intel/pmc/ssram_telemetry.c +++ b/drivers/platform/x86/intel/pmc/ssram_telemetry.c @@ -190,6 +190,7 @@ static const struct pci_device_id intel_pmc_ssram_telemetry_pci_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_LNL_SOCM) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDH) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDP) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_WCL_PCDN) }, { } }; MODULE_DEVICE_TABLE(pci, intel_pmc_ssram_telemetry_pci_ids); diff --git a/drivers/platform/x86/intel/pmc/tgl.c b/drivers/platform/x86/intel/pmc/tgl.c index 02e731ed3391..fc5b4cacc1c6 100644 --- a/drivers/platform/x86/intel/pmc/tgl.c +++ b/drivers/platform/x86/intel/pmc/tgl.c @@ -273,8 +273,8 @@ void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev) addr = (u32 *)out_obj->buffer.pointer; - lpm_req_regs = devm_kzalloc(&pdev->dev, lpm_size * sizeof(u32), - GFP_KERNEL); + lpm_req_regs = devm_kcalloc(&pdev->dev, lpm_size, sizeof(u32), + GFP_KERNEL); if (!lpm_req_regs) goto free_acpi_obj; diff --git a/drivers/platform/x86/intel/pmc/wcl.c b/drivers/platform/x86/intel/pmc/wcl.c new file mode 100644 index 000000000000..85e90a639e65 --- /dev/null +++ b/drivers/platform/x86/intel/pmc/wcl.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file contains platform specific structure definitions + * and init function used by Wildcat Lake PCH. + * + * Copyright (c) 2025, Intel Corporation. + */ + +#include <linux/bits.h> +#include <linux/pci.h> + +#include "core.h" + +static const struct pmc_bit_map wcl_pcdn_pfear_map[] = { + {"PMC_0", BIT(0)}, + {"FUSE_OSSE", BIT(1)}, + {"ESPISPI", BIT(2)}, + {"XHCI", BIT(3)}, + {"SPA", BIT(4)}, + {"RSVD", BIT(5)}, + {"MPFPW2", BIT(6)}, + {"GBE", BIT(7)}, + + {"SBR16B21", BIT(0)}, + {"SBR16B5", BIT(1)}, + {"SBR8B1", BIT(2)}, + {"SBR8B0", BIT(3)}, + {"P2SB0", BIT(4)}, + {"D2D_DISP_1", BIT(5)}, + {"LPSS", BIT(6)}, + {"LPC", BIT(7)}, + + {"SMB", BIT(0)}, + {"ISH", BIT(1)}, + {"DBG_SBR16B", BIT(2)}, + {"NPK_0", BIT(3)}, + {"D2D_NOC_1", BIT(4)}, + {"FIA_P", BIT(5)}, + {"FUSE", BIT(6)}, + {"DBG_PSF", BIT(7)}, + + {"DISP_PGA1", BIT(0)}, + {"XDCI", BIT(1)}, + {"EXI", BIT(2)}, + {"CSE", BIT(3)}, + {"KVMCC", BIT(4)}, + {"PMT", BIT(5)}, + {"CLINK", BIT(6)}, + {"PTIO", BIT(7)}, + + {"USBR0", BIT(0)}, + {"SBR16B22", BIT(1)}, + {"SMT1", BIT(2)}, + {"MPFPW1", BIT(3)}, + {"SMS2", BIT(4)}, + {"SMS1", BIT(5)}, + {"CSMERTC", BIT(6)}, + {"CSMEPSF", BIT(7)}, + + {"D2D_NOC_0", BIT(0)}, + {"ESE", BIT(1)}, + {"FIACPCB_P", BIT(2)}, + {"RSVD", BIT(3)}, + {"SBR8B2", BIT(4)}, + {"OSSE_SMT1", BIT(5)}, + {"D2D_DISP", BIT(6)}, + {"P2SB1", BIT(7)}, + + {"U3FPW1", BIT(0)}, + {"SBR16B3", BIT(1)}, + {"PSF4", BIT(2)}, + {"CNVI", BIT(3)}, + {"UFSX2", BIT(4)}, + {"ENDBG", BIT(5)}, + {"DBC", BIT(6)}, + {"SBRG", BIT(7)}, + + {"RSVD", BIT(0)}, + {"NPK1", BIT(1)}, + {"SBR16B7", BIT(2)}, + {"SBR16B4", BIT(3)}, + {"FIA_XG", BIT(4)}, + {"PSF6", BIT(5)}, + {"UFSPW1", BIT(6)}, + {"FIA_U", BIT(7)}, + + {"PSF8", BIT(0)}, + {"PSF0", BIT(1)}, + {"RSVD", BIT(2)}, + {"FIACPCB_U", BIT(3)}, + {"TAM", BIT(4)}, + {"SBR16B0", BIT(5)}, + {"TBTLSX", BIT(6)}, + {"THC0", BIT(7)}, + + {"THC1", BIT(0)}, + {"PMC_1", BIT(1)}, + {"FIACPCB_XG", BIT(2)}, + {"TCSS", BIT(3)}, + {"DISP_PGA", BIT(4)}, + {"SBR16B20", BIT(5)}, + {"SBR8B20", BIT(6)}, + {"DBG_SBR", BIT(7)}, + + {"SPC", BIT(0)}, + {"ACE_0", BIT(1)}, + {"ACE_1", BIT(2)}, + {"ACE_2", BIT(3)}, + {"ACE_3", BIT(4)}, + {"ACE_4", BIT(5)}, + {"ACE_5", BIT(6)}, + {"ACE_6", BIT(7)}, + + {"ACE_7", BIT(0)}, + {"ACE_8", BIT(1)}, + {"ACE_9", BIT(2)}, + {"ACE_10", BIT(3)}, + {"SBR16B2", BIT(4)}, + {"SBR8B4", BIT(5)}, + {"OSSE", BIT(6)}, + {"SBR16B1", BIT(7)}, + {} +}; + +static const struct pmc_bit_map *ext_wcl_pcdn_pfear_map[] = { + wcl_pcdn_pfear_map, + NULL +}; + +static const struct pmc_bit_map wcl_pcdn_ltr_show_map[] = { + {"SOUTHPORT_A", CNP_PMC_LTR_SPA}, + {"RSVD", WCL_PMC_LTR_RESERVED}, + {"SATA", CNP_PMC_LTR_SATA}, + {"GIGABIT_ETHERNET", CNP_PMC_LTR_GBE}, + {"XHCI", CNP_PMC_LTR_XHCI}, + {"SOUTHPORT_F", ADL_PMC_LTR_SPF}, + {"ME", CNP_PMC_LTR_ME}, + {"SATA1", CNP_PMC_LTR_EVA}, + {"SOUTHPORT_C", CNP_PMC_LTR_SPC}, + {"HD_AUDIO", CNP_PMC_LTR_AZ}, + {"CNV", CNP_PMC_LTR_CNV}, + {"LPSS", CNP_PMC_LTR_LPSS}, + {"SOUTHPORT_D", CNP_PMC_LTR_SPD}, + {"SOUTHPORT_E", CNP_PMC_LTR_SPE}, + {"SATA2", PTL_PMC_LTR_SATA2}, + {"ESPI", CNP_PMC_LTR_ESPI}, + {"SCC", CNP_PMC_LTR_SCC}, + {"ISH", CNP_PMC_LTR_ISH}, + {"UFSX2", CNP_PMC_LTR_UFSX2}, + {"EMMC", CNP_PMC_LTR_EMMC}, + {"WIGIG", ICL_PMC_LTR_WIGIG}, + {"THC0", TGL_PMC_LTR_THC0}, + {"THC1", TGL_PMC_LTR_THC1}, + {"SOUTHPORT_G", MTL_PMC_LTR_SPG}, + {"ESE", MTL_PMC_LTR_ESE}, + {"IOE_PMC", MTL_PMC_LTR_IOE_PMC}, + {"DMI3", ARL_PMC_LTR_DMI3}, + {"OSSE", LNL_PMC_LTR_OSSE}, + + /* Below two cannot be used for LTR_IGNORE */ + {"CURRENT_PLATFORM", PTL_PMC_LTR_CUR_PLT}, + {"AGGREGATED_SYSTEM", PTL_PMC_LTR_CUR_ASLT}, + {} +}; + +static const struct pmc_bit_map wcl_pcdn_power_gating_status_0_map[] = { + {"PMC_PGD0_PG_STS", BIT(0), 0}, + {"FUSE_OSSE_PGD0_PG_STS", BIT(1), 0}, + {"ESPISPI_PGD0_PG_STS", BIT(2), 0}, + {"XHCI_PGD0_PG_STS", BIT(3), 1}, + {"SPA_PGD0_PG_STS", BIT(4), 1}, + {"RSVD_5", BIT(5), 0}, + {"MPFPW2_PGD0_PG_STS", BIT(6), 0}, + {"GBE_PGD0_PG_STS", BIT(7), 1}, + {"SBR16B21_PGD0_PG_STS", BIT(8), 0}, + {"SBR16B5_PGD0_PG_STS", BIT(9), 0}, + {"SBR8B1_PGD0_PG_STS", BIT(10), 0}, + {"SBR8B0_PGD0_PG_STS", BIT(11), 0}, + {"P2SB0_PG_STS", BIT(12), 1}, + {"D2D_DISP_PGD1_PG_STS", BIT(13), 0}, + {"LPSS_PGD0_PG_STS", BIT(14), 1}, + {"LPC_PGD0_PG_STS", BIT(15), 0}, + {"SMB_PGD0_PG_STS", BIT(16), 0}, + {"ISH_PGD0_PG_STS", BIT(17), 0}, + {"DBG_SBR16B_PGD0_PG_STS", BIT(18), 0}, + {"NPK_PGD0_PG_STS", BIT(19), 0}, + {"D2D_NOC_PGD1_PG_STS", BIT(20), 0}, + {"FIA_P_PGD0_PG_STS", BIT(21), 0}, + {"FUSE_PGD0_PG_STS", BIT(22), 0}, + {"DBG_PSF_PGD0_PG_STS", BIT(23), 0}, + {"DISP_PGA1_PGD0_PG_STS", BIT(24), 0}, + {"XDCI_PGD0_PG_STS", BIT(25), 1}, + {"EXI_PGD0_PG_STS", BIT(26), 0}, + {"CSE_PGD0_PG_STS", BIT(27), 1}, + {"KVMCC_PGD0_PG_STS", BIT(28), 1}, + {"PMT_PGD0_PG_STS", BIT(29), 1}, + {"CLINK_PGD0_PG_STS", BIT(30), 1}, + {"PTIO_PGD0_PG_STS", BIT(31), 1}, + {} +}; + +static const struct pmc_bit_map wcl_pcdn_power_gating_status_1_map[] = { + {"USBR0_PGD0_PG_STS", BIT(0), 1}, + {"SBR16B22_PGD0_PG_STS", BIT(1), 0}, + {"SMT1_PGD0_PG_STS", BIT(2), 1}, + {"MPFPW1_PGD0_PG_STS", BIT(3), 0}, + {"SMS2_PGD0_PG_STS", BIT(4), 1}, + {"SMS1_PGD0_PG_STS", BIT(5), 1}, + {"CSMERTC_PGD0_PG_STS", BIT(6), 0}, + {"CSMEPSF_PGD0_PG_STS", BIT(7), 0}, + {"D2D_NOC_PGD0_PG_STS", BIT(8), 0}, + {"ESE_PGD0_PG_STS", BIT(9), 1}, + {"FIACPCB_P_PGD0_PG_STS", BIT(10), 0}, + {"SBR8B2_PGD0_PG_STS", BIT(12), 0}, + {"OSSE_SMT1_PGD0_PG_STS", BIT(13), 1}, + {"D2D_DISP_PGD0_PG_STS", BIT(14), 0}, + {"P2SB1_PGD0_PG_STS", BIT(15), 1}, + {"U3FPW1_PGD0_PG_STS", BIT(16), 0}, + {"SBR16B3_PGD0_PG_STS", BIT(17), 0}, + {"PSF4_PGD0_PG_STS", BIT(18), 0}, + {"CNVI_PGD0_PG_STS", BIT(19), 0}, + {"UFSX2_PGD0_PG_STS", BIT(20), 1}, + {"ENDBG_PGD0_PG_STS", BIT(21), 0}, + {"DBC_PGD0_PG_STS", BIT(22), 0}, + {"SBRG_PGD0_PG_STS", BIT(23), 0}, + {"NPK_PGD1_PG_STS", BIT(25), 0}, + {"SBR16B7_PGD0_PG_STS", BIT(26), 0}, + {"SBR16B4_PGD0_PG_STS", BIT(27), 0}, + {"FIA_XG_PSF_PGD0_PG_STS", BIT(28), 0}, + {"PSF6_PGD0_PG_STS", BIT(29), 0}, + {"UFSPW1_PGD0_PG_STS", BIT(30), 0}, + {"FIA_U_PGD0_PG_STS", BIT(31), 0}, + {} +}; + +static const struct pmc_bit_map wcl_pcdn_power_gating_status_2_map[] = { + {"PSF8_PGD0_PG_STS", BIT(0), 0}, + {"PSF0_PGD0_PG_STS", BIT(1), 0}, + {"FIACPCB_U_PGD0_PG_STS", BIT(3), 0}, + {"TAM_PGD0_PG_STS", BIT(4), 1}, + {"SBR16B0_PGD0_PG_STS", BIT(5), 0}, + {"TBTLSX_PGD0_PG_STS", BIT(6), 1}, + {"THC0_PGD0_PG_STS", BIT(7), 1}, + {"THC1_PGD0_PG_STS", BIT(8), 1}, + {"PMC_PGD1_PG_STS", BIT(9), 0}, + {"FIACPCB_XG_PGD0_PG_STS", BIT(10), 0}, + {"TCSS_PGD0_PG_STS", BIT(11), 0}, + {"DISP_PGA_PGD0_PG_STS", BIT(12), 0}, + {"SBR8B4_PGD0_PG_STS", BIT(13), 0}, + {"SBR8B20_PGD0_PG_STS", BIT(14), 0}, + {"DBG_PGD0_PG_STS", BIT(15), 0}, + {"SPC_PGD0_PG_STS", BIT(16), 1}, + {"ACE_PGD0_PG_STS", BIT(17), 0}, + {"ACE_PGD1_PG_STS", BIT(18), 0}, + {"ACE_PGD2_PG_STS", BIT(19), 0}, + {"ACE_PGD3_PG_STS", BIT(20), 0}, + {"ACE_PGD4_PG_STS", BIT(21), 0}, + {"ACE_PGD5_PG_STS", BIT(22), 0}, + {"ACE_PGD6_PG_STS", BIT(23), 0}, + {"ACE_PGD7_PG_STS", BIT(24), 0}, + {"ACE_PGD8_PG_STS", BIT(25), 0}, + {"ACE_PGD9_PG_STS", BIT(26), 0}, + {"ACE_PGD10_PG_STS", BIT(27), 0}, + {"SBR16B2_PG_PGD0_PG_STS", BIT(28), 0}, + {"SBR16B20_PGD0_PG_STS", BIT(29), 0}, + {"OSSE_PGD0_PG_STS", BIT(30), 1}, + {"SBR16B1_PGD0_PG_STS", BIT(31), 0}, + {} +}; + +static const struct pmc_bit_map wcl_pcdn_d3_status_0_map[] = { + {"LPSS_D3_STS", BIT(3), 1}, + {"XDCI_D3_STS", BIT(4), 1}, + {"XHCI_D3_STS", BIT(5), 1}, + {"SPA_D3_STS", BIT(12), 0}, + {"SPC_D3_STS", BIT(14), 0}, + {"OSSE_D3_STS", BIT(15), 0}, + {"ESPISPI_D3_STS", BIT(18), 0}, + {"PSTH_D3_STS", BIT(21), 0}, + {} +}; + +static const struct pmc_bit_map wcl_pcdn_d3_status_1_map[] = { + {"OSSE_SMT1_D3_STS", BIT(16), 0}, + {"GBE_D3_STS", BIT(19), 0}, + {"ITSS_D3_STS", BIT(23), 0}, + {"CNVI_D3_STS", BIT(27), 0}, + {"UFSX2_D3_STS", BIT(28), 0}, + {} +}; + +static const struct pmc_bit_map wcl_pcdn_d3_status_2_map[] = { + {"CSMERTC_D3_STS", BIT(1), 0}, + {"ESE_D3_STS", BIT(2), 0}, + {"CSE_D3_STS", BIT(4), 0}, + {"KVMCC_D3_STS", BIT(5), 0}, + {"USBR0_D3_STS", BIT(6), 0}, + {"ISH_D3_STS", BIT(7), 0}, + {"SMT1_D3_STS", BIT(8), 0}, + {"SMT2_D3_STS", BIT(9), 0}, + {"SMT3_D3_STS", BIT(10), 0}, + {"CLINK_D3_STS", BIT(14), 0}, + {"PTIO_D3_STS", BIT(16), 0}, + {"PMT_D3_STS", BIT(17), 0}, + {"SMS1_D3_STS", BIT(18), 0}, + {"SMS2_D3_STS", BIT(19), 0}, + {"OSSE_SMT2_D3_STS", BIT(22), 0}, + {} +}; + +static const struct pmc_bit_map wcl_pcdn_d3_status_3_map[] = { + {"THC0_D3_STS", BIT(14), 1}, + {"THC1_D3_STS", BIT(15), 1}, + {"OSSE_SMT3_D3_STS", BIT(16), 0}, + {"ACE_D3_STS", BIT(23), 0}, + {} +}; + +static const struct pmc_bit_map wcl_pcdn_vnn_req_status_0_map[] = { + {"LPSS_VNN_REQ_STS", BIT(3), 1}, + {"OSSE_VNN_REQ_STS", BIT(15), 1}, + {"ESPISPI_VNN_REQ_STS", BIT(18), 1}, + {} +}; + +static const struct pmc_bit_map wcl_pcdn_vnn_req_status_1_map[] = { + {"NPK_VNN_REQ_STS", BIT(4), 1}, + {"DFXAGG_VNN_REQ_STS", BIT(8), 0}, + {"EXI_VNN_REQ_STS", BIT(9), 1}, + {"OSSE_SMT1_VNN_REQ_STS", BIT(16), 1}, + {"P2D_VNN_REQ_STS", BIT(18), 1}, + {"GBE_VNN_REQ_STS", BIT(19), 1}, + {"SMB_VNN_REQ_STS", BIT(25), 1}, + {"LPC_VNN_REQ_STS", BIT(26), 0}, + {} +}; + +static const struct pmc_bit_map wcl_pcdn_vnn_req_status_2_map[] = { + {"CSMERTC_VNN_REQ_STS", BIT(1), 1}, + {"ESE_VNN_REQ_STS", BIT(2), 1}, + {"CSE_VNN_REQ_STS", BIT(4), 1}, + {"ISH_VNN_REQ_STS", BIT(7), 1}, + {"SMT1_VNN_REQ_STS", BIT(8), 1}, + {"CLINK_VNN_REQ_STS", BIT(14), 1}, + {"SMS1_VNN_REQ_STS", BIT(18), 1}, + {"SMS2_VNN_REQ_STS", BIT(19), 1}, + {"GPIOCOM4_VNN_REQ_STS", BIT(20), 1}, + {"GPIOCOM3_VNN_REQ_STS", BIT(21), 1}, + {"GPIOCOM1_VNN_REQ_STS", BIT(23), 1}, + {"GPIOCOM0_VNN_REQ_STS", BIT(24), 1}, + {"DISP_SHIM_VNN_REQ_STS", BIT(31), 1}, + {} +}; + +static const struct pmc_bit_map wcl_pcdn_vnn_misc_status_map[] = { + {"CPU_C10_REQ_STS", BIT(0), 0}, + {"TS_OFF_REQ_STS", BIT(1), 0}, + {"PNDE_MET_REQ_STS", BIT(2), 1}, + {"FW_THROTTLE_ALLOWED_REQ_STS", BIT(4), 0}, + {"VNN_SOC_REQ_STS", BIT(6), 1}, + {"ISH_VNNAON_REQ_STS", BIT(7), 0}, + {"D2D_NOC_CFI_QACTIVE_REQ_STS", BIT(8), 1}, + {"D2D_NOC_GPSB_QACTIVE_REQ_STS", BIT(9), 1}, + {"PLT_GREATER_REQ_STS", BIT(11), 1}, + {"ALL_SBR_IDLE_REQ_STS", BIT(12), 0}, + {"PMC_IDLE_FB_OCP_REQ_STS", BIT(13), 0}, + {"PM_SYNC_STATES_REQ_STS", BIT(14), 0}, + {"EA_REQ_STS", BIT(15), 0}, + {"MPHY_CORE_OFF_REQ_STS", BIT(16), 0}, + {"BRK_EV_EN_REQ_STS", BIT(17), 0}, + {"AUTO_DEMO_EN_REQ_STS", BIT(18), 0}, + {"ITSS_CLK_SRC_REQ_STS", BIT(19), 1}, + {"ARC_IDLE_REQ_STS", BIT(21), 0}, + {"FIA_DEEP_PM_REQ_STS", BIT(23), 0}, + {"XDCI_ATTACHED_REQ_STS", BIT(24), 1}, + {"ARC_INTERRUPT_WAKE_REQ_STS", BIT(25), 0}, + {"D2D_DISP_DDI_QACTIVE_REQ_STS", BIT(26), 1}, + {"PRE_WAKE0_REQ_STS", BIT(27), 1}, + {"PRE_WAKE1_REQ_STS", BIT(28), 1}, + {"PRE_WAKE2_REQ_STS", BIT(29), 1}, + {} +}; + +static const struct pmc_bit_map wcl_pcdn_rsc_status_map[] = { + {"Memory", 0, 1}, + {"PSF0", 0, 1}, + {"PSF6", 0, 1}, + {"PSF8", 0, 1}, + {"SAF_CFI_LINK", 0, 1}, + {"SB", 0, 1}, + {} +}; + +static const struct pmc_bit_map *wcl_pcdn_lpm_maps[] = { + ptl_pcdp_clocksource_status_map, + wcl_pcdn_power_gating_status_0_map, + wcl_pcdn_power_gating_status_1_map, + wcl_pcdn_power_gating_status_2_map, + wcl_pcdn_d3_status_0_map, + wcl_pcdn_d3_status_1_map, + wcl_pcdn_d3_status_2_map, + wcl_pcdn_d3_status_3_map, + wcl_pcdn_vnn_req_status_0_map, + wcl_pcdn_vnn_req_status_1_map, + wcl_pcdn_vnn_req_status_2_map, + ptl_pcdp_vnn_req_status_3_map, + wcl_pcdn_vnn_misc_status_map, + ptl_pcdp_signal_status_map, + NULL +}; + +static const struct pmc_bit_map *wcl_pcdn_blk_maps[] = { + wcl_pcdn_power_gating_status_0_map, + wcl_pcdn_power_gating_status_1_map, + wcl_pcdn_power_gating_status_2_map, + wcl_pcdn_rsc_status_map, + wcl_pcdn_vnn_req_status_0_map, + wcl_pcdn_vnn_req_status_1_map, + wcl_pcdn_vnn_req_status_2_map, + ptl_pcdp_vnn_req_status_3_map, + wcl_pcdn_d3_status_0_map, + wcl_pcdn_d3_status_1_map, + wcl_pcdn_d3_status_2_map, + wcl_pcdn_d3_status_3_map, + ptl_pcdp_clocksource_status_map, + wcl_pcdn_vnn_misc_status_map, + ptl_pcdp_signal_status_map, + NULL +}; + +static const struct pmc_reg_map wcl_pcdn_reg_map = { + .pfear_sts = ext_wcl_pcdn_pfear_map, + .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET, + .slp_s0_res_counter_step = TGL_PMC_SLP_S0_RES_COUNTER_STEP, + .ltr_show_sts = wcl_pcdn_ltr_show_map, + .msr_sts = msr_map, + .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET, + .regmap_length = WCL_PCD_PMC_MMIO_REG_LEN, + .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A, + .ppfear_buckets = LNL_PPFEAR_NUM_ENTRIES, + .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET, + .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT, + .lpm_num_maps = PTL_LPM_NUM_MAPS, + .ltr_ignore_max = LNL_NUM_IP_IGN_ALLOWED, + .lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2, + .etr3_offset = ETR3_OFFSET, + .lpm_sts_latch_en_offset = MTL_LPM_STATUS_LATCH_EN_OFFSET, + .lpm_priority_offset = MTL_LPM_PRI_OFFSET, + .lpm_en_offset = MTL_LPM_EN_OFFSET, + .lpm_residency_offset = MTL_LPM_RESIDENCY_OFFSET, + .lpm_sts = wcl_pcdn_lpm_maps, + .lpm_status_offset = MTL_LPM_STATUS_OFFSET, + .lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET, + .s0ix_blocker_maps = wcl_pcdn_blk_maps, + .s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET, +}; + +#define WCL_NPU_PCI_DEV 0xfd3e + +/* + * Set power state of select devices that do not have drivers to D3 + * so that they do not block Package C entry. + */ +static void wcl_d3_fixup(void) +{ + pmc_core_set_device_d3(WCL_NPU_PCI_DEV); +} + +static int wcl_resume(struct pmc_dev *pmcdev) +{ + wcl_d3_fixup(); + return cnl_resume(pmcdev); +} + +static int wcl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info) +{ + wcl_d3_fixup(); + return generic_core_init(pmcdev, pmc_dev_info); +} + +struct pmc_dev_info wcl_pmc_dev = { + .map = &wcl_pcdn_reg_map, + .suspend = cnl_suspend, + .resume = wcl_resume, + .init = wcl_core_init, +}; diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c index bfcf92aa4d69..1237d9570886 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c @@ -374,6 +374,77 @@ static void uncore_set_agent_type(struct tpmi_uncore_cluster_info *cluster_info) cluster_info->uncore_data.agent_type_mask = FIELD_GET(UNCORE_AGENT_TYPES, status); } +#define MAX_PARTITIONS 2 + +/* IO domain ID start index for a partition */ +static u8 io_die_start[MAX_PARTITIONS]; + +/* Next IO domain ID index after the current partition IO die IDs */ +static u8 io_die_index_next; + +/* Lock to protect io_die_start, io_die_index_next */ +static DEFINE_MUTEX(domain_lock); + +static void set_domain_id(int id, int num_resources, + struct oobmsm_plat_info *plat_info, + struct tpmi_uncore_cluster_info *cluster_info) +{ + u8 part_io_index, cdie_range, pkg_io_index, max_dies; + + if (plat_info->partition >= MAX_PARTITIONS) { + cluster_info->uncore_data.domain_id = id; + return; + } + + if (cluster_info->uncore_data.agent_type_mask & AGENT_TYPE_CORE) { + cluster_info->uncore_data.domain_id = cluster_info->cdie_id; + return; + } + + /* Unlikely but cdie_mask may have holes, so take range */ + cdie_range = fls(plat_info->cdie_mask) - ffs(plat_info->cdie_mask) + 1; + max_dies = topology_max_dies_per_package(); + + /* + * If the CPU doesn't enumerate dies, then use current cdie range + * as the max. + */ + if (cdie_range > max_dies) + max_dies = cdie_range; + + guard(mutex)(&domain_lock); + + if (!io_die_index_next) + io_die_index_next = max_dies; + + if (!io_die_start[plat_info->partition]) { + io_die_start[plat_info->partition] = io_die_index_next; + /* + * number of IO dies = num_resources - cdie_range. Hence + * next partition io_die_index_next is set after IO dies + * in the current partition. + */ + io_die_index_next += (num_resources - cdie_range); + } + + /* + * Index from IO die start within the partition: + * This is the first valid domain after the cdies. + * For example the current resource index 5 and cdies end at + * index 3 (cdie_cnt = 4). Then the IO only index 5 - 4 = 1. + */ + part_io_index = id - cdie_range; + + /* + * Add to the IO die start index for this partition in this package + * to make unique in the package. + */ + pkg_io_index = io_die_start[plat_info->partition] + part_io_index; + + /* Assign this to domain ID */ + cluster_info->uncore_data.domain_id = pkg_io_index; +} + /* Callback for sysfs read for TPMI uncore values. Called under mutex locks. */ static int uncore_read(struct uncore_data *data, unsigned int *value, enum uncore_index index) { @@ -610,11 +681,12 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_ cluster_info->uncore_data.package_id = pkg; /* There are no dies like Cascade Lake */ cluster_info->uncore_data.die_id = 0; - cluster_info->uncore_data.domain_id = i; cluster_info->uncore_data.cluster_id = j; set_cdie_id(i, cluster_info, plat_info); + set_domain_id(i, num_resources, plat_info, cluster_info); + cluster_info->uncore_root = tpmi_uncore; if (TPMI_MINOR_VERSION(pd_info->ufs_header_ver) >= UNCORE_ELC_SUPPORTED_VERSION) @@ -638,7 +710,7 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_ auxiliary_set_drvdata(auxdev, tpmi_uncore); - if (topology_max_dies_per_package() > 1) + if (topology_max_dies_per_package() > 1 || plat_info->partition) return 0; tpmi_uncore->root_cluster.root_domain = true; diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c index 0992b41b6221..540b472b1bf3 100644 --- a/drivers/platform/x86/lenovo/think-lmi.c +++ b/drivers/platform/x86/lenovo/think-lmi.c @@ -119,6 +119,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); * You must reboot the computer before the changes will take effect. */ #define LENOVO_SET_BIOS_CERT_GUID "26861C9F-47E9-44C4-BD8B-DFE7FA2610FE" +#define LENOVO_TC_SET_BIOS_CERT_GUID "955aaf7d-8bc4-4f04-90aa-97469512f167" /* * Name: UpdateBiosCert @@ -128,6 +129,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); * You must reboot the computer before the changes will take effect. */ #define LENOVO_UPDATE_BIOS_CERT_GUID "9AA3180A-9750-41F7-B9F7-D5D3B1BAC3CE" +#define LENOVO_TC_UPDATE_BIOS_CERT_GUID "5f5bbbb2-c72f-4fb8-8129-228eef4fdbed" /* * Name: ClearBiosCert @@ -137,6 +139,8 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); * You must reboot the computer before the changes will take effect. */ #define LENOVO_CLEAR_BIOS_CERT_GUID "B2BC39A7-78DD-4D71-B059-A510DEC44890" +#define LENOVO_TC_CLEAR_BIOS_CERT_GUID "97849cb6-cb44-42d1-a750-26a596a9eec4" + /* * Name: CertToPassword * Description: Switch from certificate to password authentication. @@ -145,6 +149,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); * You must reboot the computer before the changes will take effect. */ #define LENOVO_CERT_TO_PASSWORD_GUID "0DE8590D-5510-4044-9621-77C227F5A70D" +#define LENOVO_TC_CERT_TO_PASSWORD_GUID "ef65480d-38c9-420d-b700-ab3d6c8ebaca" /* * Name: SetBiosSettingCert @@ -153,6 +158,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); * Format: "Item,Value,Signature" */ #define LENOVO_SET_BIOS_SETTING_CERT_GUID "34A008CC-D205-4B62-9E67-31DFA8B90003" +#define LENOVO_TC_SET_BIOS_SETTING_CERT_GUID "19ecba3b-b318-4192-a89b-43d94bc60cea" /* * Name: SaveBiosSettingCert @@ -161,6 +167,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); * Format: "Signature" */ #define LENOVO_SAVE_BIOS_SETTING_CERT_GUID "C050FB9D-DF5F-4606-B066-9EFC401B2551" +#define LENOVO_TC_SAVE_BIOS_SETTING_CERT_GUID "0afaf46f-7cca-450a-b455-a826a0bf1af5" /* * Name: CertThumbprint @@ -177,12 +184,43 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); #define TLMI_CERT_SVC BIT(7) /* Admin Certificate Based */ #define TLMI_CERT_SMC BIT(8) /* System Certificate Based */ +static const struct tlmi_cert_guids thinkpad_cert_guid = { + .thumbprint = LENOVO_CERT_THUMBPRINT_GUID, + .set_bios_setting = LENOVO_SET_BIOS_SETTING_CERT_GUID, + .save_bios_setting = LENOVO_SAVE_BIOS_SETTING_CERT_GUID, + .cert_to_password = LENOVO_CERT_TO_PASSWORD_GUID, + .clear_bios_cert = LENOVO_CLEAR_BIOS_CERT_GUID, + .update_bios_cert = LENOVO_UPDATE_BIOS_CERT_GUID, + .set_bios_cert = LENOVO_SET_BIOS_CERT_GUID, +}; + +static const struct tlmi_cert_guids thinkcenter_cert_guid = { + .thumbprint = NULL, + .set_bios_setting = LENOVO_TC_SET_BIOS_SETTING_CERT_GUID, + .save_bios_setting = LENOVO_TC_SAVE_BIOS_SETTING_CERT_GUID, + .cert_to_password = LENOVO_TC_CERT_TO_PASSWORD_GUID, + .clear_bios_cert = LENOVO_TC_CLEAR_BIOS_CERT_GUID, + .update_bios_cert = LENOVO_TC_UPDATE_BIOS_CERT_GUID, + .set_bios_cert = LENOVO_TC_SET_BIOS_CERT_GUID, +}; + static const struct tlmi_err_codes tlmi_errs[] = { {"Success", 0}, + {"Set Certificate operation was successful.", 0}, {"Not Supported", -EOPNOTSUPP}, {"Invalid Parameter", -EINVAL}, {"Access Denied", -EACCES}, {"System Busy", -EBUSY}, + {"Set Certificate operation failed with status:Invalid Parameter.", -EINVAL}, + {"Set Certificate operation failed with status:Invalid certificate type.", -EINVAL}, + {"Set Certificate operation failed with status:Invalid password format.", -EINVAL}, + {"Set Certificate operation failed with status:Password retry count exceeded.", -EACCES}, + {"Set Certificate operation failed with status:Password Invalid.", -EACCES}, + {"Set Certificate operation failed with status:Operation aborted.", -EBUSY}, + {"Set Certificate operation failed with status:No free slots to write.", -ENOSPC}, + {"Set Certificate operation failed with status:Certificate not found.", -EEXIST}, + {"Set Certificate operation failed with status:Internal error.", -EFAULT}, + {"Set Certificate operation failed with status:Certificate too large.", -EFBIG}, }; static const char * const encoding_options[] = { @@ -668,7 +706,10 @@ static ssize_t cert_thumbprint(char *buf, const char *arg, int count) const union acpi_object *obj; acpi_status status; - status = wmi_evaluate_method(LENOVO_CERT_THUMBPRINT_GUID, 0, 0, &input, &output); + if (!tlmi_priv.cert_guid->thumbprint) + return -EOPNOTSUPP; + + status = wmi_evaluate_method(tlmi_priv.cert_guid->thumbprint, 0, 0, &input, &output); if (ACPI_FAILURE(status)) { kfree(output.pointer); return -EIO; @@ -751,7 +792,7 @@ static ssize_t cert_to_password_store(struct kobject *kobj, kfree_sensitive(passwd); return -ENOMEM; } - ret = tlmi_simple_call(LENOVO_CERT_TO_PASSWORD_GUID, auth_str); + ret = tlmi_simple_call(tlmi_priv.cert_guid->cert_to_password, auth_str); kfree(auth_str); kfree_sensitive(passwd); @@ -774,7 +815,7 @@ static ssize_t certificate_store(struct kobject *kobj, char *auth_str, *new_cert; const char *serial; char *signature; - char *guid; + const char *guid; int ret; if (!capable(CAP_SYS_ADMIN)) @@ -797,7 +838,7 @@ static ssize_t certificate_store(struct kobject *kobj, if (!auth_str) return -ENOMEM; - ret = tlmi_simple_call(LENOVO_CLEAR_BIOS_CERT_GUID, auth_str); + ret = tlmi_simple_call(tlmi_priv.cert_guid->clear_bios_cert, auth_str); kfree(auth_str); return ret ?: count; @@ -834,7 +875,7 @@ static ssize_t certificate_store(struct kobject *kobj, kfree(new_cert); return -EACCES; } - guid = LENOVO_UPDATE_BIOS_CERT_GUID; + guid = tlmi_priv.cert_guid->update_bios_cert; /* Format: 'Certificate,Signature' */ auth_str = cert_command(setting, new_cert, signature); } else { @@ -845,9 +886,17 @@ static ssize_t certificate_store(struct kobject *kobj, kfree(new_cert); return -EACCES; } - guid = LENOVO_SET_BIOS_CERT_GUID; - /* Format: 'Certificate, password' */ - auth_str = cert_command(setting, new_cert, setting->password); + guid = tlmi_priv.cert_guid->set_bios_cert; + if (tlmi_priv.thinkcenter_mode) { + /* Format: 'Certificate, password, encoding, kbdlang' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s,%s", new_cert, + setting->password, + encoding_options[setting->encoding], + setting->kbdlang); + } else { + /* Format: 'Certificate, password' */ + auth_str = cert_command(setting, new_cert, setting->password); + } } kfree(new_cert); if (!auth_str) @@ -1071,13 +1120,13 @@ static ssize_t current_value_store(struct kobject *kobj, goto out; } - ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTING_CERT_GUID, set_str); + ret = tlmi_simple_call(tlmi_priv.cert_guid->set_bios_setting, set_str); if (ret) goto out; if (tlmi_priv.save_mode == TLMI_SAVE_BULK) tlmi_priv.save_required = true; else - ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID, + ret = tlmi_simple_call(tlmi_priv.cert_guid->save_bios_setting, tlmi_priv.pwd_admin->save_signature); } else if (tlmi_priv.opcode_support) { /* @@ -1282,7 +1331,7 @@ static ssize_t save_settings_store(struct kobject *kobj, struct kobj_attribute * ret = -EINVAL; goto out; } - ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID, + ret = tlmi_simple_call(tlmi_priv.cert_guid->save_bios_setting, tlmi_priv.pwd_admin->save_signature); if (ret) goto out; @@ -1583,6 +1632,15 @@ static int tlmi_analyze(struct wmi_device *wdev) wmi_has_guid(LENOVO_SAVE_BIOS_SETTING_CERT_GUID)) tlmi_priv.certificate_support = true; + /* ThinkCenter uses different GUIDs for certificate support */ + if (wmi_has_guid(LENOVO_TC_SET_BIOS_CERT_GUID) && + wmi_has_guid(LENOVO_TC_SET_BIOS_SETTING_CERT_GUID) && + wmi_has_guid(LENOVO_TC_SAVE_BIOS_SETTING_CERT_GUID)) { + tlmi_priv.certificate_support = true; + tlmi_priv.thinkcenter_mode = true; + pr_info("ThinkCenter modified support being used\n"); + } + /* * Try to find the number of valid settings of this machine * and use it to create sysfs attributes. @@ -1728,10 +1786,16 @@ static int tlmi_analyze(struct wmi_device *wdev) } if (tlmi_priv.certificate_support) { - tlmi_priv.pwd_admin->cert_installed = - tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SVC; - tlmi_priv.pwd_system->cert_installed = - tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SMC; + if (tlmi_priv.thinkcenter_mode) { + tlmi_priv.cert_guid = &thinkcenter_cert_guid; + tlmi_priv.pwd_admin->cert_installed = tlmi_priv.pwdcfg.core.password_mode; + } else { + tlmi_priv.cert_guid = &thinkpad_cert_guid; + tlmi_priv.pwd_admin->cert_installed = + tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SVC; + tlmi_priv.pwd_system->cert_installed = + tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SMC; + } } return 0; diff --git a/drivers/platform/x86/lenovo/think-lmi.h b/drivers/platform/x86/lenovo/think-lmi.h index 9b014644d316..017644323d46 100644 --- a/drivers/platform/x86/lenovo/think-lmi.h +++ b/drivers/platform/x86/lenovo/think-lmi.h @@ -41,6 +41,17 @@ enum save_mode { TLMI_SAVE_SAVE, }; +/* GUIDs can differ between platforms */ +struct tlmi_cert_guids { + const char *thumbprint; + const char *set_bios_setting; + const char *save_bios_setting; + const char *cert_to_password; + const char *clear_bios_cert; + const char *update_bios_cert; + const char *set_bios_cert; +}; + /* password configuration details */ #define TLMI_PWDCFG_MODE_LEGACY 0 #define TLMI_PWDCFG_MODE_PASSWORD 1 @@ -109,6 +120,7 @@ struct think_lmi { enum save_mode save_mode; bool save_required; bool reboot_required; + bool thinkcenter_mode; struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT]; struct device *class_dev; @@ -121,6 +133,8 @@ struct think_lmi { struct tlmi_pwd_setting *pwd_system; struct tlmi_pwd_setting *pwd_hdd; struct tlmi_pwd_setting *pwd_nvme; + + const struct tlmi_cert_guids *cert_guid; }; #endif /* !_THINK_LMI_H_ */ diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.c b/drivers/platform/x86/lenovo/wmi-capdata01.c index c922680b3cba..fc7e3454e71d 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata01.c +++ b/drivers/platform/x86/lenovo/wmi-capdata01.c @@ -93,7 +93,7 @@ int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata0 continue; memcpy(output, &list->data[idx], sizeof(list->data[idx])); return 0; - }; + } return -EINVAL; } diff --git a/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c b/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c index 1b33c977f6d7..8551ab4d2c7d 100644 --- a/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c +++ b/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c @@ -255,6 +255,11 @@ static int yt2_1380_fc_pdev_probe(struct platform_device *pdev) if (!serdev) return -ENOMEM; + /* Propagate pdev-fwnode set by x86-android-tablets to serdev */ + device_set_node(&serdev->dev, dev_fwnode(&pdev->dev)); + /* The fwnode is a managed node, so it will be auto-put on serdev_device_put() */ + fwnode_handle_get(dev_fwnode(&serdev->dev)); + ret = serdev_device_add(serdev); if (ret) { serdev_device_put(serdev); diff --git a/drivers/platform/x86/meraki-mx100.c b/drivers/platform/x86/meraki-mx100.c index 3751ed36a980..8c5276d98512 100644 --- a/drivers/platform/x86/meraki-mx100.c +++ b/drivers/platform/x86/meraki-mx100.c @@ -15,135 +15,256 @@ #include <linux/dmi.h> #include <linux/err.h> -#include <linux/gpio_keys.h> #include <linux/gpio/machine.h> -#include <linux/input.h> +#include <linux/gpio/property.h> +#include <linux/input-event-codes.h> #include <linux/io.h> #include <linux/kernel.h> -#include <linux/leds.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/property.h> #define TINK_GPIO_DRIVER_NAME "gpio_ich" +static const struct software_node gpio_ich_node = { + .name = TINK_GPIO_DRIVER_NAME, +}; + /* LEDs */ -static const struct gpio_led tink_leds[] = { - { - .name = "mx100:green:internet", - .default_trigger = "default-on", - }, - { - .name = "mx100:green:lan2", - }, - { - .name = "mx100:green:lan3", - }, - { - .name = "mx100:green:lan4", - }, - { - .name = "mx100:green:lan5", - }, - { - .name = "mx100:green:lan6", - }, - { - .name = "mx100:green:lan7", - }, - { - .name = "mx100:green:lan8", - }, - { - .name = "mx100:green:lan9", - }, - { - .name = "mx100:green:lan10", - }, - { - .name = "mx100:green:lan11", - }, - { - .name = "mx100:green:ha", - }, - { - .name = "mx100:orange:ha", - }, - { - .name = "mx100:green:usb", - }, - { - .name = "mx100:orange:usb", - }, +static const struct software_node tink_gpio_leds_node = { + .name = "meraki-mx100-leds", }; -static const struct gpio_led_platform_data tink_leds_pdata = { - .num_leds = ARRAY_SIZE(tink_leds), - .leds = tink_leds, -}; - -static struct gpiod_lookup_table tink_leds_table = { - .dev_id = "leds-gpio", - .table = { - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 11, - NULL, 0, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 18, - NULL, 1, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 20, - NULL, 2, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 22, - NULL, 3, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 23, - NULL, 4, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 32, - NULL, 5, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 34, - NULL, 6, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 35, - NULL, 7, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 36, - NULL, 8, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 37, - NULL, 9, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 48, - NULL, 10, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 16, - NULL, 11, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 7, - NULL, 12, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 21, - NULL, 13, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 19, - NULL, 14, GPIO_ACTIVE_LOW), - {} /* Terminating entry */ - } +static const struct property_entry tink_internet_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:internet"), + PROPERTY_ENTRY_STRING("linux,default-trigger", "default-on"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 11, GPIO_ACTIVE_LOW), + { } +}; + +static const struct software_node tink_internet_led_node = { + .name = "internet-led", + .parent = &tink_gpio_leds_node, + .properties = tink_internet_led_props, +}; + +static const struct property_entry tink_lan2_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:lan2"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 18, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node tink_lan2_led_node = { + .name = "lan2-led", + .parent = &tink_gpio_leds_node, + .properties = tink_lan2_led_props, +}; + +static const struct property_entry tink_lan3_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:lan3"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 20, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node tink_lan3_led_node = { + .name = "lan3-led", + .parent = &tink_gpio_leds_node, + .properties = tink_lan3_led_props, +}; + +static const struct property_entry tink_lan4_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:lan4"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 22, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node tink_lan4_led_node = { + .name = "lan4-led", + .parent = &tink_gpio_leds_node, + .properties = tink_lan4_led_props, +}; + +static const struct property_entry tink_lan5_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:lan5"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 23, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node tink_lan5_led_node = { + .name = "lan5-led", + .parent = &tink_gpio_leds_node, + .properties = tink_lan5_led_props, +}; + +static const struct property_entry tink_lan6_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:lan6"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 32, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node tink_lan6_led_node = { + .name = "lan6-led", + .parent = &tink_gpio_leds_node, + .properties = tink_lan6_led_props, +}; + +static const struct property_entry tink_lan7_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:lan7"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 34, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node tink_lan7_led_node = { + .name = "lan7-led", + .parent = &tink_gpio_leds_node, + .properties = tink_lan7_led_props, +}; + +static const struct property_entry tink_lan8_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:lan8"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 35, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node tink_lan8_led_node = { + .name = "lan8-led", + .parent = &tink_gpio_leds_node, + .properties = tink_lan8_led_props, +}; + +static const struct property_entry tink_lan9_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:lan9"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 36, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node tink_lan9_led_node = { + .name = "lan9-led", + .parent = &tink_gpio_leds_node, + .properties = tink_lan9_led_props, +}; + +static const struct property_entry tink_lan10_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:lan10"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 37, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node tink_lan10_led_node = { + .name = "lan10-led", + .parent = &tink_gpio_leds_node, + .properties = tink_lan10_led_props, +}; + +static const struct property_entry tink_lan11_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:lan11"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 48, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node tink_lan11_led_node = { + .name = "lan11-led", + .parent = &tink_gpio_leds_node, + .properties = tink_lan11_led_props, +}; + +static const struct property_entry tink_ha_green_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:ha"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 16, GPIO_ACTIVE_LOW), + { } +}; + +static const struct software_node tink_ha_green_led_node = { + .name = "ha-green-led", + .parent = &tink_gpio_leds_node, + .properties = tink_ha_green_led_props, +}; + +static const struct property_entry tink_ha_orange_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:orange:ha"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 7, GPIO_ACTIVE_LOW), + { } +}; + +static const struct software_node tink_ha_orange_led_node = { + .name = "ha-orange-led", + .parent = &tink_gpio_leds_node, + .properties = tink_ha_orange_led_props, +}; + +static const struct property_entry tink_usb_green_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:green:usb"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 21, GPIO_ACTIVE_LOW), + { } +}; + +static const struct software_node tink_usb_green_led_node = { + .name = "usb-green-led", + .parent = &tink_gpio_leds_node, + .properties = tink_usb_green_led_props, +}; + +static const struct property_entry tink_usb_orange_led_props[] = { + PROPERTY_ENTRY_STRING("label", "mx100:orange:usb"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 19, GPIO_ACTIVE_LOW), + { } +}; + +static const struct software_node tink_usb_orange_led_node = { + .name = "usb-orange-led", + .parent = &tink_gpio_leds_node, + .properties = tink_usb_orange_led_props, }; /* Reset Button */ -static struct gpio_keys_button tink_buttons[] = { - { - .desc = "Reset", - .type = EV_KEY, - .code = KEY_RESTART, - .active_low = 1, - .debounce_interval = 100, - }, +static const struct property_entry tink_gpio_keys_props[] = { + PROPERTY_ENTRY_U32("poll-interval", 20), + { } }; -static const struct gpio_keys_platform_data tink_buttons_pdata = { - .buttons = tink_buttons, - .nbuttons = ARRAY_SIZE(tink_buttons), - .poll_interval = 20, - .rep = 0, - .name = "mx100-keys", +static const struct software_node tink_gpio_keys_node = { + .name = "mx100-keys", + .properties = tink_gpio_keys_props, }; -static struct gpiod_lookup_table tink_keys_table = { - .dev_id = "gpio-keys-polled", - .table = { - GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 60, - NULL, 0, GPIO_ACTIVE_LOW), - {} /* Terminating entry */ - } +static const struct property_entry tink_reset_key_props[] = { + PROPERTY_ENTRY_U32("linux,code", KEY_RESTART), + PROPERTY_ENTRY_STRING("label", "Reset"), + PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 60, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_U32("linux,input-type", EV_KEY), + PROPERTY_ENTRY_U32("debounce-interval", 100), + { } +}; + +static const struct software_node tink_reset_key_node = { + .name = "reset", + .parent = &tink_gpio_keys_node, + .properties = tink_reset_key_props, +}; + +static const struct software_node *tink_swnodes[] = { + &gpio_ich_node, + /* LEDs nodes */ + &tink_gpio_leds_node, + &tink_internet_led_node, + &tink_lan2_led_node, + &tink_lan3_led_node, + &tink_lan4_led_node, + &tink_lan5_led_node, + &tink_lan6_led_node, + &tink_lan7_led_node, + &tink_lan8_led_node, + &tink_lan9_led_node, + &tink_lan10_led_node, + &tink_lan11_led_node, + &tink_ha_green_led_node, + &tink_ha_orange_led_node, + &tink_usb_green_led_node, + &tink_usb_orange_led_node, + /* Keys nodes */ + &tink_gpio_keys_node, + &tink_reset_key_node, + NULL }; /* Board setup */ @@ -161,22 +282,17 @@ MODULE_DEVICE_TABLE(dmi, tink_systems); static struct platform_device *tink_leds_pdev; static struct platform_device *tink_keys_pdev; -static struct platform_device * __init tink_create_dev( - const char *name, const void *pdata, size_t sz) -{ - struct platform_device *pdev; - - pdev = platform_device_register_data(NULL, - name, PLATFORM_DEVID_NONE, pdata, sz); - if (IS_ERR(pdev)) - pr_err("failed registering %s: %ld\n", name, PTR_ERR(pdev)); - - return pdev; -} - static int __init tink_board_init(void) { - int ret; + struct platform_device_info keys_info = { + .name = "gpio-keys-polled", + .id = PLATFORM_DEVID_NONE, + }; + struct platform_device_info leds_info = { + .name = "leds-gpio", + .id = PLATFORM_DEVID_NONE, + }; + int err; if (!dmi_first_match(tink_systems)) return -ENODEV; @@ -188,30 +304,35 @@ static int __init tink_board_init(void) */ outl(inl(0x530) | BIT(28), 0x530); - gpiod_add_lookup_table(&tink_leds_table); - gpiod_add_lookup_table(&tink_keys_table); + err = software_node_register_node_group(tink_swnodes); + if (err) { + pr_err("failed to register software nodes: %d\n", err); + return err; + } - tink_leds_pdev = tink_create_dev("leds-gpio", - &tink_leds_pdata, sizeof(tink_leds_pdata)); + leds_info.fwnode = software_node_fwnode(&tink_gpio_leds_node); + tink_leds_pdev = platform_device_register_full(&leds_info); if (IS_ERR(tink_leds_pdev)) { - ret = PTR_ERR(tink_leds_pdev); - goto err; + err = PTR_ERR(tink_leds_pdev); + pr_err("failed to create LED device: %d\n", err); + goto err_unregister_swnodes; } - tink_keys_pdev = tink_create_dev("gpio-keys-polled", - &tink_buttons_pdata, sizeof(tink_buttons_pdata)); + keys_info.fwnode = software_node_fwnode(&tink_gpio_keys_node); + tink_keys_pdev = platform_device_register_full(&keys_info); if (IS_ERR(tink_keys_pdev)) { - ret = PTR_ERR(tink_keys_pdev); - platform_device_unregister(tink_leds_pdev); - goto err; + err = PTR_ERR(tink_keys_pdev); + pr_err("failed to create key device: %d\n", err); + goto err_unregister_leds; } return 0; -err: - gpiod_remove_lookup_table(&tink_keys_table); - gpiod_remove_lookup_table(&tink_leds_table); - return ret; +err_unregister_leds: + platform_device_unregister(tink_leds_pdev); +err_unregister_swnodes: + software_node_unregister_node_group(tink_swnodes); + return err; } module_init(tink_board_init); @@ -219,8 +340,7 @@ static void __exit tink_board_exit(void) { platform_device_unregister(tink_keys_pdev); platform_device_unregister(tink_leds_pdev); - gpiod_remove_lookup_table(&tink_keys_table); - gpiod_remove_lookup_table(&tink_leds_table); + software_node_unregister_node_group(tink_swnodes); } module_exit(tink_board_exit); diff --git a/drivers/platform/x86/pcengines-apuv2.c b/drivers/platform/x86/pcengines-apuv2.c index 3aa63b18a2e1..3b086863c6ac 100644 --- a/drivers/platform/x86/pcengines-apuv2.c +++ b/drivers/platform/x86/pcengines-apuv2.c @@ -12,13 +12,13 @@ #include <linux/dmi.h> #include <linux/err.h> +#include <linux/gpio/machine.h> +#include <linux/gpio/property.h> +#include <linux/input-event-codes.h> #include <linux/kernel.h> -#include <linux/leds.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/gpio_keys.h> -#include <linux/gpio/machine.h> -#include <linux/input.h> +#include <linux/property.h> #include <linux/platform_data/gpio/gpio-amd-fch.h> /* @@ -72,60 +72,91 @@ static const struct amd_fch_gpio_pdata board_apu2 = { .gpio_names = apu2_gpio_names, }; +static const struct software_node apu2_gpiochip_node = { + .name = AMD_FCH_GPIO_DRIVER_NAME, +}; + /* GPIO LEDs device */ +static const struct software_node apu2_leds_node = { + .name = "apu2-leds", +}; -static const struct gpio_led apu2_leds[] = { - { .name = "apu:green:1" }, - { .name = "apu:green:2" }, - { .name = "apu:green:3" }, +static const struct property_entry apu2_led1_props[] = { + PROPERTY_ENTRY_STRING("label", "apu:green:1"), + PROPERTY_ENTRY_GPIO("gpios", &apu2_gpiochip_node, + APU2_GPIO_LINE_LED1, GPIO_ACTIVE_LOW), + { } }; -static const struct gpio_led_platform_data apu2_leds_pdata = { - .num_leds = ARRAY_SIZE(apu2_leds), - .leds = apu2_leds, +static const struct software_node apu2_led1_swnode = { + .name = "led-1", + .parent = &apu2_leds_node, + .properties = apu2_led1_props, }; -static struct gpiod_lookup_table gpios_led_table = { - .dev_id = "leds-gpio", - .table = { - GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED1, - NULL, 0, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED2, - NULL, 1, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED3, - NULL, 2, GPIO_ACTIVE_LOW), - {} /* Terminating entry */ - } +static const struct property_entry apu2_led2_props[] = { + PROPERTY_ENTRY_STRING("label", "apu:green:2"), + PROPERTY_ENTRY_GPIO("gpios", &apu2_gpiochip_node, + APU2_GPIO_LINE_LED2, GPIO_ACTIVE_LOW), + { } +}; + +static const struct software_node apu2_led2_swnode = { + .name = "led-2", + .parent = &apu2_leds_node, + .properties = apu2_led2_props, +}; + +static const struct property_entry apu2_led3_props[] = { + PROPERTY_ENTRY_STRING("label", "apu:green:3"), + PROPERTY_ENTRY_GPIO("gpios", &apu2_gpiochip_node, + APU2_GPIO_LINE_LED3, GPIO_ACTIVE_LOW), + { } +}; + +static const struct software_node apu2_led3_swnode = { + .name = "led-3", + .parent = &apu2_leds_node, + .properties = apu2_led3_props, }; /* GPIO keyboard device */ +static const struct property_entry apu2_keys_props[] = { + PROPERTY_ENTRY_U32("poll-interval", 100), + { } +}; -static struct gpio_keys_button apu2_keys_buttons[] = { - { - .code = KEY_RESTART, - .active_low = 1, - .desc = "front button", - .type = EV_KEY, - .debounce_interval = 10, - .value = 1, - }, +static const struct software_node apu2_keys_node = { + .name = "apu2-keys", + .properties = apu2_keys_props, }; -static const struct gpio_keys_platform_data apu2_keys_pdata = { - .buttons = apu2_keys_buttons, - .nbuttons = ARRAY_SIZE(apu2_keys_buttons), - .poll_interval = 100, - .rep = 0, - .name = "apu2-keys", +static const struct property_entry apu2_front_button_props[] = { + PROPERTY_ENTRY_STRING("label", "front button"), + PROPERTY_ENTRY_U32("linux,code", KEY_RESTART), + PROPERTY_ENTRY_GPIO("gpios", &apu2_gpiochip_node, + APU2_GPIO_LINE_MODESW, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_U32("debounce-interval", 10), + { } }; -static struct gpiod_lookup_table gpios_key_table = { - .dev_id = "gpio-keys-polled", - .table = { - GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_MODESW, - NULL, 0, GPIO_ACTIVE_LOW), - {} /* Terminating entry */ - } +static const struct software_node apu2_front_button_swnode = { + .name = "front-button", + .parent = &apu2_keys_node, + .properties = apu2_front_button_props, +}; + +static const struct software_node *apu2_swnodes[] = { + &apu2_gpiochip_node, + /* LEDs nodes */ + &apu2_leds_node, + &apu2_led1_swnode, + &apu2_led2_swnode, + &apu2_led3_swnode, + /* Keys nodes */ + &apu2_keys_node, + &apu2_front_button_swnode, + NULL }; /* Board setup */ @@ -222,23 +253,25 @@ static struct platform_device *apu_gpio_pdev; static struct platform_device *apu_leds_pdev; static struct platform_device *apu_keys_pdev; -static struct platform_device * __init apu_create_pdev( - const char *name, - const void *pdata, - size_t sz) +static struct platform_device * __init apu_create_pdev(const char *name, + const void *data, size_t size, + const struct software_node *swnode) { + struct platform_device_info pdev_info = { + .name = name, + .id = PLATFORM_DEVID_NONE, + .data = data, + .size_data = size, + .fwnode = software_node_fwnode(swnode), + }; struct platform_device *pdev; + int err; - pdev = platform_device_register_resndata(NULL, - name, - PLATFORM_DEVID_NONE, - NULL, - 0, - pdata, - sz); + pdev = platform_device_register_full(&pdev_info); - if (IS_ERR(pdev)) - pr_err("failed registering %s: %ld\n", name, PTR_ERR(pdev)); + err = PTR_ERR_OR_ZERO(pdev); + if (err) + pr_err("failed registering %s: %d\n", name, err); return pdev; } @@ -246,6 +279,7 @@ static struct platform_device * __init apu_create_pdev( static int __init apu_board_init(void) { const struct dmi_system_id *id; + int err; id = dmi_first_match(apu_gpio_dmi_table); if (!id) { @@ -253,35 +287,45 @@ static int __init apu_board_init(void) return -ENODEV; } - gpiod_add_lookup_table(&gpios_led_table); - gpiod_add_lookup_table(&gpios_key_table); + err = software_node_register_node_group(apu2_swnodes); + if (err) { + pr_err("failed to register software nodes: %d\n", err); + return err; + } - apu_gpio_pdev = apu_create_pdev( - AMD_FCH_GPIO_DRIVER_NAME, - id->driver_data, - sizeof(struct amd_fch_gpio_pdata)); + apu_gpio_pdev = apu_create_pdev(AMD_FCH_GPIO_DRIVER_NAME, + id->driver_data, sizeof(struct amd_fch_gpio_pdata), NULL); + err = PTR_ERR_OR_ZERO(apu_gpio_pdev); + if (err) + goto err_unregister_swnodes; - apu_leds_pdev = apu_create_pdev( - "leds-gpio", - &apu2_leds_pdata, - sizeof(apu2_leds_pdata)); + apu_leds_pdev = apu_create_pdev("leds-gpio", NULL, 0, &apu2_leds_node); + err = PTR_ERR_OR_ZERO(apu_leds_pdev); + if (err) + goto err_unregister_gpio; - apu_keys_pdev = apu_create_pdev( - "gpio-keys-polled", - &apu2_keys_pdata, - sizeof(apu2_keys_pdata)); + apu_keys_pdev = apu_create_pdev("gpio-keys-polled", NULL, 0, &apu2_keys_node); + err = PTR_ERR_OR_ZERO(apu_keys_pdev); + if (err) + goto err_unregister_leds; return 0; + +err_unregister_leds: + platform_device_unregister(apu_leds_pdev); +err_unregister_gpio: + platform_device_unregister(apu_gpio_pdev); +err_unregister_swnodes: + software_node_unregister_node_group(apu2_swnodes); + return err; } static void __exit apu_board_exit(void) { - gpiod_remove_lookup_table(&gpios_led_table); - gpiod_remove_lookup_table(&gpios_key_table); - platform_device_unregister(apu_keys_pdev); platform_device_unregister(apu_leds_pdev); platform_device_unregister(apu_gpio_pdev); + software_node_unregister_node_group(apu2_swnodes); } module_init(apu_board_init); diff --git a/drivers/platform/x86/portwell-ec.c b/drivers/platform/x86/portwell-ec.c index 322f296e9315..ac506ea40eff 100644 --- a/drivers/platform/x86/portwell-ec.c +++ b/drivers/platform/x86/portwell-ec.c @@ -5,15 +5,13 @@ * Tested on: * - Portwell NANO-6064 * - * This driver provides support for GPIO and Watchdog Timer - * functionalities of the Portwell boards with ITE embedded controller (EC). + * This driver supports Portwell boards with an ITE embedded controller (EC). * The EC is accessed through I/O ports and provides: + * - Temperature and voltage readings (hwmon) * - 8 GPIO pins for control and monitoring * - Hardware watchdog with 1-15300 second timeout range * - * It integrates with the Linux GPIO and Watchdog subsystems, allowing - * userspace interaction with EC GPIO pins and watchdog control, - * ensuring system stability and configurability. + * It integrates with the Linux hwmon, GPIO and Watchdog subsystems. * * (C) Copyright 2025 Portwell, Inc. * Author: Yen-Chi Huang (jesse.huang@portwell.com.tw) @@ -22,16 +20,20 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/acpi.h> +#include <linux/bits.h> #include <linux/bitfield.h> #include <linux/dmi.h> #include <linux/gpio/driver.h> +#include <linux/hwmon.h> #include <linux/init.h> #include <linux/io.h> #include <linux/ioport.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/pm.h> #include <linux/sizes.h> #include <linux/string.h> +#include <linux/units.h> #include <linux/watchdog.h> #define PORTWELL_EC_IOSPACE 0xe300 @@ -41,6 +43,9 @@ #define PORTWELL_GPIO_DIR_REG 0x2b #define PORTWELL_GPIO_VAL_REG 0x2c +#define PORTWELL_HWMON_TEMP_NUM 3 +#define PORTWELL_HWMON_VOLT_NUM 5 + #define PORTWELL_WDT_EC_CONFIG_ADDR 0x06 #define PORTWELL_WDT_CONFIG_ENABLE 0x1 #define PORTWELL_WDT_CONFIG_DISABLE 0x0 @@ -52,16 +57,60 @@ #define PORTWELL_EC_FW_VENDOR_LENGTH 3 #define PORTWELL_EC_FW_VENDOR_NAME "PWG" +#define PORTWELL_EC_ADC_MAX 1023 + static bool force; module_param(force, bool, 0444); MODULE_PARM_DESC(force, "Force loading EC driver without checking DMI boardname"); +/* A sensor's metadata (label, scale, and register) */ +struct pwec_sensor_prop { + const char *label; + u8 reg; + u32 scale; +}; + +/* Master configuration with properties for all possible sensors */ +static const struct { + const struct pwec_sensor_prop temp_props[PORTWELL_HWMON_TEMP_NUM]; + const struct pwec_sensor_prop in_props[PORTWELL_HWMON_VOLT_NUM]; +} pwec_master_data = { + .temp_props = { + { "CPU Temperature", 0x00, 0 }, + { "System Temperature", 0x02, 0 }, + { "Aux Temperature", 0x04, 0 }, + }, + .in_props = { + { "Vcore", 0x20, 3000 }, + { "3.3V", 0x22, 6000 }, + { "5V", 0x24, 9600 }, + { "12V", 0x30, 19800 }, + { "VDIMM", 0x32, 3000 }, + }, +}; + +struct pwec_board_info { + u32 temp_mask; /* bit N = temperature channel N */ + u32 in_mask; /* bit N = voltage channel N */ +}; + +static const struct pwec_board_info pwec_board_info_default = { + .temp_mask = GENMASK(PORTWELL_HWMON_TEMP_NUM - 1, 0), + .in_mask = GENMASK(PORTWELL_HWMON_VOLT_NUM - 1, 0), +}; + +static const struct pwec_board_info pwec_board_info_nano = { + .temp_mask = BIT(0) | BIT(1), + .in_mask = GENMASK(4, 0), +}; + static const struct dmi_system_id pwec_dmi_table[] = { { .ident = "NANO-6064 series", .matches = { DMI_MATCH(DMI_BOARD_NAME, "NANO-6064"), }, + .driver_data = (void *)&pwec_board_info_nano, }, { } }; @@ -79,6 +128,20 @@ static u8 pwec_read(u8 address) return inb(PORTWELL_EC_IOSPACE + address); } +/* Ensure consistent 16-bit read across potential MSB rollover. */ +static u16 pwec_read16_stable(u8 lsb_reg) +{ + u8 lsb, msb, old_msb; + + do { + old_msb = pwec_read(lsb_reg + 1); + lsb = pwec_read(lsb_reg); + msb = pwec_read(lsb_reg + 1); + } while (msb != old_msb); + + return (msb << 8) | lsb; +} + /* GPIO functions */ static int pwec_gpio_get(struct gpio_chip *chip, unsigned int offset) @@ -204,6 +267,81 @@ static struct watchdog_device ec_wdt_dev = { .max_timeout = PORTWELL_WDT_EC_MAX_COUNT_SECOND, }; +/* HWMON functions */ + +static umode_t pwec_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct pwec_board_info *info = drvdata; + + switch (type) { + case hwmon_temp: + return (info->temp_mask & BIT(channel)) ? 0444 : 0; + case hwmon_in: + return (info->in_mask & BIT(channel)) ? 0444 : 0; + default: + return 0; + } +} + +static int pwec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + u16 tmp16; + + switch (type) { + case hwmon_temp: + *val = pwec_read(pwec_master_data.temp_props[channel].reg) * MILLIDEGREE_PER_DEGREE; + return 0; + case hwmon_in: + tmp16 = pwec_read16_stable(pwec_master_data.in_props[channel].reg); + *val = (tmp16 * pwec_master_data.in_props[channel].scale) / PORTWELL_EC_ADC_MAX; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int pwec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + *str = pwec_master_data.temp_props[channel].label; + return 0; + case hwmon_in: + *str = pwec_master_data.in_props[channel].label; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_channel_info *pwec_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), + NULL +}; + +static const struct hwmon_ops pwec_hwmon_ops = { + .is_visible = pwec_hwmon_is_visible, + .read = pwec_hwmon_read, + .read_string = pwec_hwmon_read_string, +}; + +static const struct hwmon_chip_info pwec_chip_info = { + .ops = &pwec_hwmon_ops, + .info = pwec_hwmon_info, +}; + static int pwec_firmware_vendor_check(void) { u8 buf[PORTWELL_EC_FW_VENDOR_LENGTH + 1]; @@ -218,6 +356,8 @@ static int pwec_firmware_vendor_check(void) static int pwec_probe(struct platform_device *pdev) { + struct device *hwmon_dev; + void *drvdata = dev_get_platdata(&pdev->dev); int ret; if (!devm_request_region(&pdev->dev, PORTWELL_EC_IOSPACE, @@ -236,19 +376,40 @@ static int pwec_probe(struct platform_device *pdev) return ret; } - ec_wdt_dev.parent = &pdev->dev; - ret = devm_watchdog_register_device(&pdev->dev, &ec_wdt_dev); - if (ret < 0) { - dev_err(&pdev->dev, "failed to register Portwell EC Watchdog\n"); - return ret; + if (IS_REACHABLE(CONFIG_HWMON)) { + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + "portwell_ec", drvdata, &pwec_chip_info, NULL); + ret = PTR_ERR_OR_ZERO(hwmon_dev); + if (ret) + return ret; } + ec_wdt_dev.parent = &pdev->dev; + return devm_watchdog_register_device(&pdev->dev, &ec_wdt_dev); +} + +static int pwec_suspend(struct device *dev) +{ + if (watchdog_active(&ec_wdt_dev)) + return pwec_wdt_stop(&ec_wdt_dev); + return 0; } +static int pwec_resume(struct device *dev) +{ + if (watchdog_active(&ec_wdt_dev)) + return pwec_wdt_start(&ec_wdt_dev); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(pwec_dev_pm_ops, pwec_suspend, pwec_resume); + static struct platform_driver pwec_driver = { .driver = { .name = "portwell-ec", + .pm = pm_sleep_ptr(&pwec_dev_pm_ops), }, .probe = pwec_probe, }; @@ -257,19 +418,26 @@ static struct platform_device *pwec_dev; static int __init pwec_init(void) { + const struct dmi_system_id *match; + const struct pwec_board_info *hwmon_data; int ret; - if (!dmi_check_system(pwec_dmi_table)) { + match = dmi_first_match(pwec_dmi_table); + if (!match) { if (!force) return -ENODEV; - pr_warn("force load portwell-ec without DMI check\n"); + hwmon_data = &pwec_board_info_default; + pr_warn("force load portwell-ec without DMI check, using full display config\n"); + } else { + hwmon_data = match->driver_data; } ret = platform_driver_register(&pwec_driver); if (ret) return ret; - pwec_dev = platform_device_register_simple("portwell-ec", -1, NULL, 0); + pwec_dev = platform_device_register_data(NULL, "portwell-ec", PLATFORM_DEVID_NONE, + hwmon_data, sizeof(*hwmon_data)); if (IS_ERR(pwec_dev)) { platform_driver_unregister(&pwec_driver); return PTR_ERR(pwec_dev); diff --git a/drivers/platform/x86/quickstart.c b/drivers/platform/x86/quickstart.c index c332c7cdaff5..acb58518be37 100644 --- a/drivers/platform/x86/quickstart.c +++ b/drivers/platform/x86/quickstart.c @@ -154,13 +154,6 @@ static void quickstart_notify_remove(void *context) acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify); } -static void quickstart_mutex_destroy(void *data) -{ - struct mutex *lock = data; - - mutex_destroy(lock); -} - static int quickstart_probe(struct platform_device *pdev) { struct quickstart_data *data; @@ -179,8 +172,7 @@ static int quickstart_probe(struct platform_device *pdev) data->dev = &pdev->dev; dev_set_drvdata(&pdev->dev, data); - mutex_init(&data->input_lock); - ret = devm_add_action_or_reset(&pdev->dev, quickstart_mutex_destroy, &data->input_lock); + ret = devm_mutex_init(&pdev->dev, &data->input_lock); if (ret < 0) return ret; diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c new file mode 100644 index 000000000000..949236b93a32 --- /dev/null +++ b/drivers/platform/x86/redmi-wmi.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0 +/* WMI driver for Xiaomi Redmibooks */ + +#include <linux/acpi.h> +#include <linux/bits.h> +#include <linux/device.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/unaligned.h> +#include <linux/wmi.h> + +#include <uapi/linux/input-event-codes.h> + +#define WMI_REDMIBOOK_KEYBOARD_EVENT_GUID "46C93E13-EE9B-4262-8488-563BCA757FEF" + +#define AI_KEY_VALUE_MASK BIT(8) + +static const struct key_entry redmi_wmi_keymap[] = { + {KE_KEY, 0x00000201, {KEY_SELECTIVE_SCREENSHOT}}, + {KE_KEY, 0x00000301, {KEY_ALL_APPLICATIONS}}, + {KE_KEY, 0x00001b01, {KEY_SETUP}}, + + /* AI button has code for each position */ + {KE_KEY, 0x00011801, {KEY_ASSISTANT}}, + {KE_KEY, 0x00011901, {KEY_ASSISTANT}}, + + /* Keyboard backlight */ + {KE_IGNORE, 0x00000501, {}}, + {KE_IGNORE, 0x00800501, {}}, + {KE_IGNORE, 0x00050501, {}}, + {KE_IGNORE, 0x000a0501, {}}, + + {KE_END} +}; + +struct redmi_wmi { + struct input_dev *input_dev; + /* Protects the key event sequence */ + struct mutex key_lock; +}; + +static int redmi_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct redmi_wmi *data; + int err; + + /* Init dev */ + data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + dev_set_drvdata(&wdev->dev, data); + + err = devm_mutex_init(&wdev->dev, &data->key_lock); + if (err) + return err; + + data->input_dev = devm_input_allocate_device(&wdev->dev); + if (!data->input_dev) + return -ENOMEM; + + data->input_dev->name = "Redmibook WMI keys"; + data->input_dev->phys = "wmi/input0"; + + err = sparse_keymap_setup(data->input_dev, redmi_wmi_keymap, NULL); + if (err) + return err; + + return input_register_device(data->input_dev); +} + +static void redmi_wmi_notify(struct wmi_device *wdev, union acpi_object *obj) +{ + struct key_entry *entry; + struct redmi_wmi *data = dev_get_drvdata(&wdev->dev); + bool autorelease = true; + u32 payload; + int value = 1; + + if (obj->type != ACPI_TYPE_BUFFER) { + dev_err(&wdev->dev, "Bad response type %u\n", obj->type); + return; + } + + if (obj->buffer.length < 32) { + dev_err(&wdev->dev, "Invalid buffer length %u\n", obj->buffer.length); + return; + } + + payload = get_unaligned_le32(obj->buffer.pointer); + entry = sparse_keymap_entry_from_scancode(data->input_dev, payload); + + if (!entry) { + dev_dbg(&wdev->dev, "Unknown WMI event with payload %u", payload); + return; + } + + /* AI key quirk */ + if (entry->keycode == KEY_ASSISTANT) { + value = !(payload & AI_KEY_VALUE_MASK); + autorelease = false; + } + + guard(mutex)(&data->key_lock); + sparse_keymap_report_entry(data->input_dev, entry, value, autorelease); +} + +static const struct wmi_device_id redmi_wmi_id_table[] = { + { WMI_REDMIBOOK_KEYBOARD_EVENT_GUID, NULL }, + { } +}; + +static struct wmi_driver redmi_wmi_driver = { + .driver = { + .name = "redmi-wmi", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = redmi_wmi_id_table, + .probe = redmi_wmi_probe, + .notify = redmi_wmi_notify, + .no_singleton = true, +}; +module_wmi_driver(redmi_wmi_driver); + +MODULE_DEVICE_TABLE(wmi, redmi_wmi_id_table); +MODULE_AUTHOR("Gladyshev Ilya <foxido@foxido.dev>"); +MODULE_DESCRIPTION("Redmibook WMI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/x86-android-tablets/Makefile b/drivers/platform/x86/x86-android-tablets/Makefile index 313be30548bc..a2cf8cbdb351 100644 --- a/drivers/platform/x86/x86-android-tablets/Makefile +++ b/drivers/platform/x86/x86-android-tablets/Makefile @@ -6,4 +6,4 @@ obj-$(CONFIG_X86_ANDROID_TABLETS) += vexia_atla10_ec.o obj-$(CONFIG_X86_ANDROID_TABLETS) += x86-android-tablets.o x86-android-tablets-y := core.o dmi.o shared-psy-info.o \ - asus.o lenovo.o other.o + acer.o asus.o lenovo.o other.o diff --git a/drivers/platform/x86/x86-android-tablets/acer.c b/drivers/platform/x86/x86-android-tablets/acer.c new file mode 100644 index 000000000000..d48c70ffd992 --- /dev/null +++ b/drivers/platform/x86/x86-android-tablets/acer.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Board info for Acer X86 tablets which ship with Android as the factory image + * and which have broken DSDT tables. The factory kernels shipped on these + * devices typically have a bunch of things hardcoded, rather than specified + * in their DSDT. + * + * Copyright (C) 2021-2025 Hans de Goede <hansg@kernel.org> + */ + +#include <linux/gpio/machine.h> +#include <linux/gpio/property.h> +#include <linux/platform_device.h> +#include <linux/property.h> + +#include "shared-psy-info.h" +#include "x86-android-tablets.h" + +/* Acer Iconia One 8 A1-840 (non FHD version) */ +static const struct property_entry acer_a1_840_bq24190_props[] = { + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node), + PROPERTY_ENTRY_BOOL("omit-battery-class"), + PROPERTY_ENTRY_BOOL("disable-reset"), + { } +}; + +static const struct software_node acer_a1_840_bq24190_node = { + .properties = acer_a1_840_bq24190_props, +}; + +static const struct property_entry acer_a1_840_touchscreen_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 800), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1280), + PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_LOW), + { } +}; + +static const struct software_node acer_a1_840_touchscreen_node = { + .properties = acer_a1_840_touchscreen_props, +}; + +static const struct x86_i2c_client_info acer_a1_840_i2c_clients[] __initconst = { + { + /* BQ24297 charger IC */ + .board_info = { + .type = "bq24297", + .addr = 0x6b, + .dev_name = "bq24297", + .swnode = &acer_a1_840_bq24190_node, + .platform_data = &bq24190_pdata, + }, + .adapter_path = "\\_SB_.I2C1", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_GPIOINT, + .chip = "INT33FC:02", + .index = 2, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_LOW, + .con_id = "bq24297_irq", + }, + }, { + /* MPU6515 sensors */ + .board_info = { + .type = "mpu6515", + .addr = 0x69, + .dev_name = "mpu6515", + }, + .adapter_path = "\\_SB_.I2C3", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_APIC, + .index = 0x47, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_HIGH, + }, + }, { + /* FT5416 touchscreen controller */ + .board_info = { + .type = "edt-ft5x06", + .addr = 0x38, + .dev_name = "ft5416", + .swnode = &acer_a1_840_touchscreen_node, + }, + .adapter_path = "\\_SB_.I2C4", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_APIC, + .index = 0x45, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_HIGH, + }, + } +}; + +static const struct property_entry acer_a1_840_int3496_props[] __initconst = { + PROPERTY_ENTRY_GPIO("mux-gpios", &baytrail_gpiochip_nodes[2], 1, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 18, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct platform_device_info acer_a1_840_pdevs[] __initconst = { + { + /* For micro USB ID pin handling */ + .name = "intel-int3496", + .id = PLATFORM_DEVID_NONE, + .properties = acer_a1_840_int3496_props, + }, +}; + +/* Properties for the Dollar Cove TI PMIC battery MFD child used as fuel-gauge */ +static const struct property_entry acer_a1_840_fg_props[] = { + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node), + PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", bq24190_psy, 1), + PROPERTY_ENTRY_GPIO("charged-gpios", &baytrail_gpiochip_nodes[2], 10, GPIO_ACTIVE_HIGH), + { } +}; + +static struct device *acer_a1_840_fg_dev; +static struct fwnode_handle *acer_a1_840_fg_node; + +static int __init acer_a1_840_init(struct device *dev) +{ + int ret; + + acer_a1_840_fg_dev = bus_find_device_by_name(&platform_bus_type, NULL, "chtdc_ti_battery"); + if (!acer_a1_840_fg_dev) + return dev_err_probe(dev, -EPROBE_DEFER, "getting chtdc_ti_battery dev\n"); + + acer_a1_840_fg_node = fwnode_create_software_node(acer_a1_840_fg_props, NULL); + if (IS_ERR(acer_a1_840_fg_node)) { + ret = PTR_ERR(acer_a1_840_fg_node); + goto err_put; + } + + ret = device_add_software_node(acer_a1_840_fg_dev, + to_software_node(acer_a1_840_fg_node)); + if (ret) + goto err_put; + + return 0; + +err_put: + fwnode_handle_put(acer_a1_840_fg_node); + acer_a1_840_fg_node = NULL; + put_device(acer_a1_840_fg_dev); + acer_a1_840_fg_dev = NULL; + return ret; +} + +static void acer_a1_840_exit(void) +{ + device_remove_software_node(acer_a1_840_fg_dev); + /* + * Skip fwnode_handle_put(acer_a1_840_fg_node), instead leak the node. + * The intel_dc_ti_battery driver may still reference the strdup-ed + * "supplied-from" string. This string will be free-ed if the node + * is released. + */ + acer_a1_840_fg_node = NULL; + put_device(acer_a1_840_fg_dev); + acer_a1_840_fg_dev = NULL; +} + +static const char * const acer_a1_840_modules[] __initconst = { + "bq24190_charger", /* For the Vbus regulator for intel-int3496 */ + NULL +}; + +const struct x86_dev_info acer_a1_840_info __initconst = { + .i2c_client_info = acer_a1_840_i2c_clients, + .i2c_client_count = ARRAY_SIZE(acer_a1_840_i2c_clients), + .pdev_info = acer_a1_840_pdevs, + .pdev_count = ARRAY_SIZE(acer_a1_840_pdevs), + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, + .swnode_group = generic_lipo_4v2_battery_swnodes, + .modules = acer_a1_840_modules, + .init = acer_a1_840_init, + .exit = acer_a1_840_exit, +}; + +/* Acer Iconia One 7 B1-750 has an Android factory image with everything hardcoded */ +static const char * const acer_b1_750_mount_matrix[] = { + "-1", "0", "0", + "0", "1", "0", + "0", "0", "1" +}; + +static const struct property_entry acer_b1_750_bma250e_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", acer_b1_750_mount_matrix), + { } +}; + +static const struct software_node acer_b1_750_bma250e_node = { + .properties = acer_b1_750_bma250e_props, +}; + +static const struct property_entry acer_b1_750_novatek_props[] = { + PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_LOW), + { } +}; + +static const struct software_node acer_b1_750_novatek_node = { + .properties = acer_b1_750_novatek_props, +}; + +static const struct x86_i2c_client_info acer_b1_750_i2c_clients[] __initconst = { + { + /* Novatek NVT-ts touchscreen */ + .board_info = { + .type = "nt11205-ts", + .addr = 0x34, + .dev_name = "NVT-ts", + .swnode = &acer_b1_750_novatek_node, + }, + .adapter_path = "\\_SB_.I2C4", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_GPIOINT, + .chip = "INT33FC:02", + .index = 3, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_LOW, + .con_id = "NVT-ts_irq", + }, + }, { + /* BMA250E accelerometer */ + .board_info = { + .type = "bma250e", + .addr = 0x18, + .swnode = &acer_b1_750_bma250e_node, + }, + .adapter_path = "\\_SB_.I2C3", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_GPIOINT, + .chip = "INT33FC:02", + .index = 25, + .trigger = ACPI_LEVEL_SENSITIVE, + .polarity = ACPI_ACTIVE_HIGH, + .con_id = "bma250e_irq", + }, + }, +}; + +const struct x86_dev_info acer_b1_750_info __initconst = { + .i2c_client_info = acer_b1_750_i2c_clients, + .i2c_client_count = ARRAY_SIZE(acer_b1_750_i2c_clients), + .pdev_info = int3496_pdevs, + .pdev_count = 1, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, +}; diff --git a/drivers/platform/x86/x86-android-tablets/asus.c b/drivers/platform/x86/x86-android-tablets/asus.c index 97cd14c1fd23..7d29c7654d21 100644 --- a/drivers/platform/x86/x86-android-tablets/asus.c +++ b/drivers/platform/x86/x86-android-tablets/asus.c @@ -5,36 +5,55 @@ * devices typically have a bunch of things hardcoded, rather than specified * in their DSDT. * - * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com> + * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org> */ #include <linux/gpio/machine.h> -#include <linux/input.h> +#include <linux/gpio/property.h> +#include <linux/input-event-codes.h> #include <linux/platform_device.h> #include "shared-psy-info.h" #include "x86-android-tablets.h" /* Asus ME176C and TF103C tablets shared data */ -static struct gpiod_lookup_table int3496_gpo2_pin22_gpios = { - .dev_id = "intel-int3496", - .table = { - GPIO_LOOKUP("INT33FC:02", 22, "id", GPIO_ACTIVE_HIGH), - { } - }, +static const struct property_entry asus_me176c_tf103c_int3496_props[] __initconst = { + PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 22, GPIO_ACTIVE_HIGH), + { } }; -static const struct x86_gpio_button asus_me176c_tf103c_lid __initconst = { - .button = { - .code = SW_LID, - .active_low = true, - .desc = "lid_sw", - .type = EV_SW, - .wakeup = true, - .debounce_interval = 50, +static const struct platform_device_info asus_me176c_tf103c_pdevs[] __initconst = { + { + /* For micro USB ID pin handling */ + .name = "intel-int3496", + .id = PLATFORM_DEVID_NONE, + .properties = asus_me176c_tf103c_int3496_props, }, - .chip = "INT33FC:02", - .pin = 12, +}; + +static const struct software_node asus_me176c_tf103c_gpio_keys_node = { + .name = "lid_sw", +}; + +static const struct property_entry asus_me176c_tf103c_lid_props[] = { + PROPERTY_ENTRY_U32("linux,input-type", EV_SW), + PROPERTY_ENTRY_U32("linux,code", SW_LID), + PROPERTY_ENTRY_STRING("label", "lid_sw"), + PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[2], 12, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_U32("debounce-interval", 50), + PROPERTY_ENTRY_BOOL("wakeup-source"), + { } +}; + +static const struct software_node asus_me176c_tf103c_lid_node = { + .parent = &asus_me176c_tf103c_gpio_keys_node, + .properties = asus_me176c_tf103c_lid_props, +}; + +static const struct software_node *asus_me176c_tf103c_lid_swnodes[] = { + &asus_me176c_tf103c_gpio_keys_node, + &asus_me176c_tf103c_lid_node, + NULL }; /* Asus ME176C tablets have an Android factory image with everything hardcoded */ @@ -77,6 +96,16 @@ static const struct software_node asus_me176c_ug3105_node = { .properties = asus_me176c_ug3105_props, }; +static const struct property_entry asus_me176c_touchscreen_props[] = { + PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[0], 60, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("irq-gpios", &baytrail_gpiochip_nodes[2], 28, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node asus_me176c_touchscreen_node = { + .properties = asus_me176c_touchscreen_props, +}; + static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = { { /* bq24297 battery charger */ @@ -132,6 +161,7 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = .type = "GDIX1001:00", .addr = 0x14, .dev_name = "goodix_ts", + .swnode = &asus_me176c_touchscreen_node, }, .adapter_path = "\\_SB_.I2C6", .irq_data = { @@ -152,33 +182,17 @@ static const struct x86_serdev_info asus_me176c_serdevs[] __initconst = { }, }; -static struct gpiod_lookup_table asus_me176c_goodix_gpios = { - .dev_id = "i2c-goodix_ts", - .table = { - GPIO_LOOKUP("INT33FC:00", 60, "reset", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("INT33FC:02", 28, "irq", GPIO_ACTIVE_HIGH), - { } - }, -}; - -static struct gpiod_lookup_table * const asus_me176c_gpios[] = { - &int3496_gpo2_pin22_gpios, - &asus_me176c_goodix_gpios, - NULL -}; - const struct x86_dev_info asus_me176c_info __initconst = { .i2c_client_info = asus_me176c_i2c_clients, .i2c_client_count = ARRAY_SIZE(asus_me176c_i2c_clients), - .pdev_info = int3496_pdevs, - .pdev_count = 1, + .pdev_info = asus_me176c_tf103c_pdevs, + .pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs), .serdev_info = asus_me176c_serdevs, .serdev_count = ARRAY_SIZE(asus_me176c_serdevs), - .gpio_button = &asus_me176c_tf103c_lid, - .gpio_button_count = 1, - .gpiod_lookup_tables = asus_me176c_gpios, - .bat_swnode = &generic_lipo_hv_4v35_battery_node, + .gpio_button_swnodes = asus_me176c_tf103c_lid_swnodes, + .swnode_group = generic_lipo_hv_4v35_battery_swnodes, .modules = bq24190_modules, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, }; /* Asus TF103C tablets have an Android factory image with everything hardcoded */ @@ -293,19 +307,13 @@ static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = }, }; -static struct gpiod_lookup_table * const asus_tf103c_gpios[] = { - &int3496_gpo2_pin22_gpios, - NULL -}; - const struct x86_dev_info asus_tf103c_info __initconst = { .i2c_client_info = asus_tf103c_i2c_clients, .i2c_client_count = ARRAY_SIZE(asus_tf103c_i2c_clients), - .pdev_info = int3496_pdevs, - .pdev_count = 1, - .gpio_button = &asus_me176c_tf103c_lid, - .gpio_button_count = 1, - .gpiod_lookup_tables = asus_tf103c_gpios, - .bat_swnode = &generic_lipo_4v2_battery_node, + .pdev_info = asus_me176c_tf103c_pdevs, + .pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs), + .gpio_button_swnodes = asus_me176c_tf103c_lid_swnodes, + .swnode_group = generic_lipo_4v2_battery_swnodes, .modules = bq24190_modules, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, }; diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index 2a9c47178505..6588fae30356 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -5,7 +5,7 @@ * devices typically have a bunch of things hardcoded, rather than specified * in their DSDT. * - * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com> + * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org> */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -152,9 +152,9 @@ static struct i2c_client **i2c_clients; static struct spi_device **spi_devs; static struct platform_device **pdevs; static struct serdev_device **serdevs; -static struct gpio_keys_button *buttons; -static struct gpiod_lookup_table * const *gpiod_lookup_tables; -static const struct software_node *bat_swnode; +static const struct software_node **gpio_button_swnodes; +static const struct software_node **swnode_group; +static const struct software_node **gpiochip_node_group; static void (*exit_handler)(void); static __init struct i2c_adapter * @@ -265,8 +265,7 @@ static __init int x86_instantiate_spi_dev(const struct x86_dev_info *dev_info, i spi_devs[idx] = spi_new_device(controller, &board_info); put_device(&controller->dev); if (!spi_devs[idx]) - return dev_err_probe(&controller->dev, -ENOMEM, - "creating SPI-device %d\n", idx); + return -ENOMEM; return 0; } @@ -277,8 +276,10 @@ get_serdev_controller_by_pci_parent(const struct x86_serdev_info *info) struct pci_dev *pdev; pdev = pci_get_domain_bus_and_slot(0, 0, info->ctrl.pci.devfn); - if (!pdev) - return ERR_PTR(-EPROBE_DEFER); + if (!pdev) { + pr_err("error could not get PCI serdev at devfn 0x%02x\n", info->ctrl.pci.devfn); + return ERR_PTR(-ENODEV); + } /* This puts our reference on pdev and returns a ref on the ctrl */ return get_serdev_controller_from_parent(&pdev->dev, 0, info->ctrl_devname); @@ -331,6 +332,34 @@ put_ctrl_dev: return ret; } +const struct software_node baytrail_gpiochip_nodes[] = { + { .name = "INT33FC:00" }, + { .name = "INT33FC:01" }, + { .name = "INT33FC:02" }, +}; + +static const struct software_node *baytrail_gpiochip_node_group[] = { + &baytrail_gpiochip_nodes[0], + &baytrail_gpiochip_nodes[1], + &baytrail_gpiochip_nodes[2], + NULL +}; + +const struct software_node cherryview_gpiochip_nodes[] = { + { .name = "INT33FF:00" }, + { .name = "INT33FF:01" }, + { .name = "INT33FF:02" }, + { .name = "INT33FF:03" }, +}; + +static const struct software_node *cherryview_gpiochip_node_group[] = { + &cherryview_gpiochip_nodes[0], + &cherryview_gpiochip_nodes[1], + &cherryview_gpiochip_nodes[2], + &cherryview_gpiochip_nodes[3], + NULL +}; + static void x86_android_tablet_remove(struct platform_device *pdev) { int i; @@ -346,7 +375,6 @@ static void x86_android_tablet_remove(struct platform_device *pdev) platform_device_unregister(pdevs[i]); kfree(pdevs); - kfree(buttons); for (i = spi_dev_count - 1; i >= 0; i--) spi_unregister_device(spi_devs[i]); @@ -361,10 +389,9 @@ static void x86_android_tablet_remove(struct platform_device *pdev) if (exit_handler) exit_handler(); - for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++) - gpiod_remove_lookup_table(gpiod_lookup_tables[i]); - - software_node_unregister(bat_swnode); + software_node_unregister_node_group(gpio_button_swnodes); + software_node_unregister_node_group(swnode_group); + software_node_unregister_node_group(gpiochip_node_group); } static __init int x86_android_tablet_probe(struct platform_device *pdev) @@ -388,16 +415,28 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev) for (i = 0; dev_info->modules && dev_info->modules[i]; i++) request_module(dev_info->modules[i]); - bat_swnode = dev_info->bat_swnode; - if (bat_swnode) { - ret = software_node_register(bat_swnode); - if (ret) - return ret; + switch (dev_info->gpiochip_type) { + case X86_GPIOCHIP_BAYTRAIL: + gpiochip_node_group = baytrail_gpiochip_node_group; + break; + case X86_GPIOCHIP_CHERRYVIEW: + gpiochip_node_group = cherryview_gpiochip_node_group; + break; + case X86_GPIOCHIP_UNSPECIFIED: + gpiochip_node_group = NULL; + break; } - gpiod_lookup_tables = dev_info->gpiod_lookup_tables; - for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++) - gpiod_add_lookup_table(gpiod_lookup_tables[i]); + ret = software_node_register_node_group(gpiochip_node_group); + if (ret) + return ret; + + ret = software_node_register_node_group(dev_info->swnode_group); + if (ret) { + x86_android_tablet_remove(pdev); + return ret; + } + swnode_group = dev_info->swnode_group; if (dev_info->init) { ret = dev_info->init(&pdev->dev); @@ -470,38 +509,22 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev) } } - if (dev_info->gpio_button_count) { - struct gpio_keys_platform_data pdata = { }; - struct gpio_desc *gpiod; + if (dev_info->gpio_button_swnodes) { + struct platform_device_info button_info = { + .name = "gpio-keys", + .id = PLATFORM_DEVID_AUTO, + }; - buttons = kcalloc(dev_info->gpio_button_count, sizeof(*buttons), GFP_KERNEL); - if (!buttons) { + ret = software_node_register_node_group(dev_info->gpio_button_swnodes); + if (ret < 0) { x86_android_tablet_remove(pdev); - return -ENOMEM; - } - - for (i = 0; i < dev_info->gpio_button_count; i++) { - ret = x86_android_tablet_get_gpiod(dev_info->gpio_button[i].chip, - dev_info->gpio_button[i].pin, - dev_info->gpio_button[i].button.desc, - false, GPIOD_IN, &gpiod); - if (ret < 0) { - x86_android_tablet_remove(pdev); - return ret; - } - - buttons[i] = dev_info->gpio_button[i].button; - buttons[i].gpio = desc_to_gpio(gpiod); - /* Release GPIO descriptor so that gpio-keys can request it */ - devm_gpiod_put(&x86_android_tablet_device->dev, gpiod); + return ret; } - pdata.buttons = buttons; - pdata.nbuttons = dev_info->gpio_button_count; + gpio_button_swnodes = dev_info->gpio_button_swnodes; - pdevs[pdev_count] = platform_device_register_data(&pdev->dev, "gpio-keys", - PLATFORM_DEVID_AUTO, - &pdata, sizeof(pdata)); + button_info.fwnode = software_node_fwnode(dev_info->gpio_button_swnodes[0]); + pdevs[pdev_count] = platform_device_register_full(&button_info); if (IS_ERR(pdevs[pdev_count])) { ret = PTR_ERR(pdevs[pdev_count]); x86_android_tablet_remove(pdev); @@ -537,6 +560,6 @@ static void __exit x86_android_tablet_exit(void) } module_exit(x86_android_tablet_exit); -MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); MODULE_DESCRIPTION("X86 Android tablets DSDT fixups driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/x86-android-tablets/dmi.c b/drivers/platform/x86/x86-android-tablets/dmi.c index 278c6d151dc4..4a5720d6fc1d 100644 --- a/drivers/platform/x86/x86-android-tablets/dmi.c +++ b/drivers/platform/x86/x86-android-tablets/dmi.c @@ -5,7 +5,7 @@ * devices typically have a bunch of things hardcoded, rather than specified * in their DSDT. * - * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com> + * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org> */ #include <linux/dmi.h> @@ -17,6 +17,16 @@ const struct dmi_system_id x86_android_tablet_ids[] __initconst = { { + /* Acer Iconia One 8 A1-840 (non FHD version) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "BayTrail"), + /* Above strings are too generic also match BIOS date */ + DMI_MATCH(DMI_BIOS_DATE, "04/01/2014"), + }, + .driver_data = (void *)&acer_a1_840_info, + }, + { /* Acer Iconia One 7 B1-750 */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index 1241a97cda39..e3d3a8290949 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -5,13 +5,15 @@ * devices typically have a bunch of things hardcoded, rather than specified * in their DSDT. * - * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com> + * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org> */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/efi.h> #include <linux/gpio/machine.h> +#include <linux/gpio/property.h> +#include <linux/input-event-codes.h> #include <linux/mfd/arizona/pdata.h> #include <linux/mfd/arizona/registers.h> #include <linux/mfd/intel_soc_pmic.h> @@ -59,11 +61,30 @@ static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = { .initial_brightness = 128, }; +static const struct software_node arizona_gpiochip_node = { + .name = "arizona", +}; + +static const struct software_node crystalcove_gpiochip_node = { + .name = "gpio_crystalcove", +}; + /* Lenovo Yoga Book X90F / X90L's Android factory image has everything hardcoded */ +static const struct property_entry lenovo_yb1_x90_goodix_props[] = { + PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[1], 53, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("irq-gpios", &cherryview_gpiochip_nodes[1], 56, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node lenovo_yb1_x90_goodix_node = { + .properties = lenovo_yb1_x90_goodix_props, +}; + static const struct property_entry lenovo_yb1_x90_wacom_props[] = { PROPERTY_ENTRY_U32("hid-descr-addr", 0x0001), PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 150), + PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_LOW), { } }; @@ -85,6 +106,7 @@ static const struct property_entry lenovo_yb1_x90_hideep_ts_props[] = { PROPERTY_ENTRY_U32("touchscreen-size-y", 1920), PROPERTY_ENTRY_U32("touchscreen-max-pressure", 16384), PROPERTY_ENTRY_BOOL("hideep,force-native-protocol"), + PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 7, GPIO_ACTIVE_LOW), { } }; @@ -108,6 +130,7 @@ static const struct x86_i2c_client_info lenovo_yb1_x90_i2c_clients[] __initconst .type = "GDIX1001:00", .addr = 0x14, .dev_name = "goodix_ts", + .swnode = &lenovo_yb1_x90_goodix_node, }, .adapter_path = "\\_SB_.PCI0.I2C2", .irq_data = { @@ -185,48 +208,33 @@ static const struct x86_serdev_info lenovo_yb1_x90_serdevs[] __initconst = { }, }; -static const struct x86_gpio_button lenovo_yb1_x90_lid __initconst = { - .button = { - .code = SW_LID, - .active_low = true, - .desc = "lid_sw", - .type = EV_SW, - .wakeup = true, - .debounce_interval = 50, - }, - .chip = "INT33FF:02", - .pin = 19, -}; - -static struct gpiod_lookup_table lenovo_yb1_x90_goodix_gpios = { - .dev_id = "i2c-goodix_ts", - .table = { - GPIO_LOOKUP("INT33FF:01", 53, "reset", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("INT33FF:01", 56, "irq", GPIO_ACTIVE_HIGH), - { } - }, +/* + * Software node attached to gpio-keys device representing the LID and + * serving as a parent to software nodes representing individual keys/buttons + * as required by the device tree binding. + */ +static const struct software_node lenovo_lid_gpio_keys_node = { + .name = "lid_sw", }; -static struct gpiod_lookup_table lenovo_yb1_x90_hideep_gpios = { - .dev_id = "i2c-hideep_ts", - .table = { - GPIO_LOOKUP("INT33FF:00", 7, "reset", GPIO_ACTIVE_LOW), - { } - }, +static const struct property_entry lenovo_yb1_x90_lid_props[] = { + PROPERTY_ENTRY_U32("linux,input-type", EV_SW), + PROPERTY_ENTRY_U32("linux,code", SW_LID), + PROPERTY_ENTRY_STRING("label", "lid_sw"), + PROPERTY_ENTRY_GPIO("gpios", &cherryview_gpiochip_nodes[2], 19, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_U32("debounce-interval", 50), + PROPERTY_ENTRY_BOOL("wakeup-source"), + { } }; -static struct gpiod_lookup_table lenovo_yb1_x90_wacom_gpios = { - .dev_id = "i2c-wacom", - .table = { - GPIO_LOOKUP("INT33FF:00", 82, "reset", GPIO_ACTIVE_LOW), - { } - }, +static const struct software_node lenovo_yb1_x90_lid_node = { + .parent = &lenovo_lid_gpio_keys_node, + .properties = lenovo_yb1_x90_lid_props, }; -static struct gpiod_lookup_table * const lenovo_yb1_x90_gpios[] = { - &lenovo_yb1_x90_hideep_gpios, - &lenovo_yb1_x90_goodix_gpios, - &lenovo_yb1_x90_wacom_gpios, +static const struct software_node *lenovo_yb1_x90_lid_swnodes[] = { + &lenovo_lid_gpio_keys_node, + &lenovo_yb1_x90_lid_node, NULL }; @@ -256,9 +264,8 @@ const struct x86_dev_info lenovo_yogabook_x90_info __initconst = { .pdev_count = ARRAY_SIZE(lenovo_yb1_x90_pdevs), .serdev_info = lenovo_yb1_x90_serdevs, .serdev_count = ARRAY_SIZE(lenovo_yb1_x90_serdevs), - .gpio_button = &lenovo_yb1_x90_lid, - .gpio_button_count = 1, - .gpiod_lookup_tables = lenovo_yb1_x90_gpios, + .gpio_button_swnodes = lenovo_yb1_x90_lid_swnodes, + .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW, .init = lenovo_yb1_x90_init, }; @@ -294,17 +301,25 @@ static const struct software_node lenovo_yoga_tab2_830_1050_bq24190_node = { .properties = lenovo_yoga_tab2_830_1050_bq24190_props, }; -static const struct x86_gpio_button lenovo_yoga_tab2_830_1050_lid __initconst = { - .button = { - .code = SW_LID, - .active_low = true, - .desc = "lid_sw", - .type = EV_SW, - .wakeup = true, - .debounce_interval = 50, - }, - .chip = "INT33FC:02", - .pin = 26, +static const struct property_entry lenovo_yoga_tab2_830_1050_lid_props[] = { + PROPERTY_ENTRY_U32("linux,input-type", EV_SW), + PROPERTY_ENTRY_U32("linux,code", SW_LID), + PROPERTY_ENTRY_STRING("label", "lid_sw"), + PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[2], 26, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_U32("debounce-interval", 50), + PROPERTY_ENTRY_BOOL("wakeup-source"), + { } +}; + +static const struct software_node lenovo_yoga_tab2_830_1050_lid_node = { + .parent = &lenovo_lid_gpio_keys_node, + .properties = lenovo_yoga_tab2_830_1050_lid_props, +}; + +static const struct software_node *lenovo_yoga_tab2_830_1050_lid_swnodes[] = { + &lenovo_lid_gpio_keys_node, + &lenovo_yoga_tab2_830_1050_lid_node, + NULL }; /* This gets filled by lenovo_yoga_tab2_830_1050_init() */ @@ -384,47 +399,65 @@ static struct x86_i2c_client_info lenovo_yoga_tab2_830_1050_i2c_clients[] __init }, }; -static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_int3496_gpios = { - .dev_id = "intel-int3496", - .table = { - GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_LOW), - GPIO_LOOKUP("INT33FC:02", 24, "id", GPIO_ACTIVE_HIGH), - { } +static const struct property_entry lenovo_yoga_tab2_830_1050_int3496_props[] __initconst = { + PROPERTY_ENTRY_GPIO("mux-gpios", &baytrail_gpiochip_nodes[2], 1, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 24, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct platform_device_info lenovo_yoga_tab2_830_1050_pdevs[] __initconst = { + { + /* For micro USB ID pin handling */ + .name = "intel-int3496", + .id = PLATFORM_DEVID_NONE, + .properties = lenovo_yoga_tab2_830_1050_int3496_props, }, }; #define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00" -static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_codec_gpios = { - .dev_id = LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, - .table = { - GPIO_LOOKUP("gpio_crystalcove", 3, "reset", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("INT33FC:01", 23, "wlf,ldoena", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("arizona", 2, "wlf,spkvdd-ena", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("arizona", 4, "wlf,micd-pol", GPIO_ACTIVE_LOW), - { } - }, +static const struct property_entry lenovo_yoga_tab2_830_1050_wm1502_props[] = { + PROPERTY_ENTRY_GPIO("reset-gpios", + &crystalcove_gpiochip_node, 3, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", + &baytrail_gpiochip_nodes[1], 23, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", + &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", + &arizona_gpiochip_node, 4, GPIO_ACTIVE_LOW), + { } +}; + +static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = { + .properties = lenovo_yoga_tab2_830_1050_wm1502_props, }; -static struct gpiod_lookup_table * const lenovo_yoga_tab2_830_1050_gpios[] = { - &lenovo_yoga_tab2_830_1050_int3496_gpios, - &lenovo_yoga_tab2_830_1050_codec_gpios, +static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = { + &crystalcove_gpiochip_node, + &arizona_gpiochip_node, + &lenovo_yoga_tab2_830_1050_wm5102, + &generic_lipo_hv_4v35_battery_node, NULL }; static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev); static void lenovo_yoga_tab2_830_1050_exit(void); +static const char * const lenovo_yoga_tab2_modules[] __initconst = { + "spi_pxa2xx_platform", /* For the SPI codec device */ + "bq24190_charger", /* For the Vbus regulator for int3496/lc824206xa */ + NULL +}; + const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = { .i2c_client_info = lenovo_yoga_tab2_830_1050_i2c_clients, .i2c_client_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients), - .pdev_info = int3496_pdevs, - .pdev_count = 1, - .gpio_button = &lenovo_yoga_tab2_830_1050_lid, - .gpio_button_count = 1, - .gpiod_lookup_tables = lenovo_yoga_tab2_830_1050_gpios, - .bat_swnode = &generic_lipo_hv_4v35_battery_node, - .modules = bq24190_modules, + .pdev_info = lenovo_yoga_tab2_830_1050_pdevs, + .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_pdevs), + .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, + .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, + .modules = lenovo_yoga_tab2_modules, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, .init = lenovo_yoga_tab2_830_1050_init, .exit = lenovo_yoga_tab2_830_1050_exit, }; @@ -481,6 +514,7 @@ static const struct pinctrl_map lenovo_yoga_tab2_830_1050_codec_pinctrl_map = PIN_MAP_MUX_GROUP(LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, "codec_32khz_clk", "INT33FC:02", "pmu_clk2_grp", "pmu_clk"); +static struct device *lenovo_yoga_tab2_830_1050_codec_dev; static struct pinctrl *lenovo_yoga_tab2_830_1050_codec_pinctrl; static struct sys_off_handler *lenovo_yoga_tab2_830_1050_sys_off_handler; @@ -507,12 +541,18 @@ static int __init lenovo_yoga_tab2_830_1050_init_codec(void) goto err_unregister_mappings; } - /* We're done with the codec_dev now */ - put_device(codec_dev); + ret = device_add_software_node(codec_dev, &lenovo_yoga_tab2_830_1050_wm5102); + if (ret) { + ret = dev_err_probe(codec_dev, ret, "adding software node\n"); + goto err_put_pinctrl; + } + lenovo_yoga_tab2_830_1050_codec_dev = codec_dev; lenovo_yoga_tab2_830_1050_codec_pinctrl = pinctrl; return 0; +err_put_pinctrl: + pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl); err_unregister_mappings: pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map); err_put_device: @@ -560,10 +600,10 @@ static void lenovo_yoga_tab2_830_1050_exit(void) { unregister_sys_off_handler(lenovo_yoga_tab2_830_1050_sys_off_handler); - if (lenovo_yoga_tab2_830_1050_codec_pinctrl) { - pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl); - pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map); - } + device_remove_software_node(lenovo_yoga_tab2_830_1050_codec_dev); + pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl); + pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map); + put_device(lenovo_yoga_tab2_830_1050_codec_dev); } /* @@ -718,19 +758,21 @@ static const struct x86_i2c_client_info lenovo_yoga_tab2_1380_i2c_clients[] __in } }; +static const struct property_entry lenovo_yoga_tab2_1380_fc_props[] __initconst = { + PROPERTY_ENTRY_GPIO("uart3_txd-gpios", &baytrail_gpiochip_nodes[0], 57, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("uart3_rxd-gpios", &baytrail_gpiochip_nodes[0], 61, GPIO_ACTIVE_HIGH), + { } +}; + static const struct platform_device_info lenovo_yoga_tab2_1380_pdevs[] __initconst = { { /* For the Tablet 2 Pro 1380's custom fast charging driver */ .name = "lenovo-yoga-tab2-pro-1380-fastcharger", .id = PLATFORM_DEVID_NONE, + .properties = lenovo_yoga_tab2_1380_fc_props, }, }; -static const char * const lenovo_yoga_tab2_1380_modules[] __initconst = { - "bq24190_charger", /* For the Vbus regulator for lc824206xa */ - NULL -}; - static int __init lenovo_yoga_tab2_1380_init(struct device *dev) { int ret; @@ -752,31 +794,15 @@ static int __init lenovo_yoga_tab2_1380_init(struct device *dev) return 0; } -static struct gpiod_lookup_table lenovo_yoga_tab2_1380_fc_gpios = { - .dev_id = "serial0-0", - .table = { - GPIO_LOOKUP("INT33FC:00", 57, "uart3_txd", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("INT33FC:00", 61, "uart3_rxd", GPIO_ACTIVE_HIGH), - { } - }, -}; - -static struct gpiod_lookup_table * const lenovo_yoga_tab2_1380_gpios[] = { - &lenovo_yoga_tab2_830_1050_codec_gpios, - &lenovo_yoga_tab2_1380_fc_gpios, - NULL -}; - const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = { .i2c_client_info = lenovo_yoga_tab2_1380_i2c_clients, .i2c_client_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_i2c_clients), .pdev_info = lenovo_yoga_tab2_1380_pdevs, .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_pdevs), - .gpio_button = &lenovo_yoga_tab2_830_1050_lid, - .gpio_button_count = 1, - .gpiod_lookup_tables = lenovo_yoga_tab2_1380_gpios, - .bat_swnode = &generic_lipo_hv_4v35_battery_node, - .modules = lenovo_yoga_tab2_1380_modules, + .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, + .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, + .modules = lenovo_yoga_tab2_modules, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, .init = lenovo_yoga_tab2_1380_init, .exit = lenovo_yoga_tab2_830_1050_exit, }; @@ -824,6 +850,7 @@ static const struct property_entry lenovo_yt3_hideep_ts_props[] = { PROPERTY_ENTRY_U32("touchscreen-size-x", 1600), PROPERTY_ENTRY_U32("touchscreen-size-y", 2560), PROPERTY_ENTRY_U32("touchscreen-max-pressure", 255), + PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 7, GPIO_ACTIVE_LOW), { } }; @@ -958,12 +985,34 @@ static struct arizona_pdata lenovo_yt3_wm5102_pdata = { }, }; +static const struct property_entry lenovo_yt3_wm1502_props[] = { + PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", + &cherryview_gpiochip_nodes[0], 75, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", + &cherryview_gpiochip_nodes[0], 81, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), + { } +}; + +static const struct software_node lenovo_yt3_wm5102 = { + .properties = lenovo_yt3_wm1502_props, + .name = "wm5102", +}; + +static const struct software_node *lenovo_yt3_swnodes[] = { + &arizona_gpiochip_node, + &lenovo_yt3_wm5102, + NULL +}; + static const struct x86_spi_dev_info lenovo_yt3_spi_devs[] __initconst = { { /* WM5102 codec */ .board_info = { .modalias = "wm5102", .platform_data = &lenovo_yt3_wm5102_pdata, + .swnode = &lenovo_yt3_wm5102, .max_speed_hz = 5000000, }, .ctrl_path = "\\_SB_.PCI0.SPI1", @@ -1013,28 +1062,8 @@ static int __init lenovo_yt3_init(struct device *dev) return 0; } -static struct gpiod_lookup_table lenovo_yt3_hideep_gpios = { - .dev_id = "i2c-hideep_ts", - .table = { - GPIO_LOOKUP("INT33FF:00", 7, "reset", GPIO_ACTIVE_LOW), - { } - }, -}; - -static struct gpiod_lookup_table lenovo_yt3_wm5102_gpios = { - .dev_id = "spi1.0", - .table = { - GPIO_LOOKUP("INT33FF:00", 75, "wlf,spkvdd-ena", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("INT33FF:00", 81, "wlf,ldoena", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("INT33FF:00", 82, "reset", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("arizona", 2, "wlf,micd-pol", GPIO_ACTIVE_HIGH), - { } - }, -}; - -static struct gpiod_lookup_table * const lenovo_yt3_gpios[] = { - &lenovo_yt3_hideep_gpios, - &lenovo_yt3_wm5102_gpios, +static const char * const lenovo_yt3_modules[] __initconst = { + "spi_pxa2xx_platform", /* For the SPI codec device */ NULL }; @@ -1043,6 +1072,8 @@ const struct x86_dev_info lenovo_yt3_info __initconst = { .i2c_client_count = ARRAY_SIZE(lenovo_yt3_i2c_clients), .spi_dev_info = lenovo_yt3_spi_devs, .spi_dev_count = ARRAY_SIZE(lenovo_yt3_spi_devs), - .gpiod_lookup_tables = lenovo_yt3_gpios, + .swnode_group = lenovo_yt3_swnodes, + .modules = lenovo_yt3_modules, + .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW, .init = lenovo_yt3_init, }; diff --git a/drivers/platform/x86/x86-android-tablets/other.c b/drivers/platform/x86/x86-android-tablets/other.c index f7bd9f863c85..7532af2d72d1 100644 --- a/drivers/platform/x86/x86-android-tablets/other.c +++ b/drivers/platform/x86/x86-android-tablets/other.c @@ -5,12 +5,13 @@ * devices typically have a bunch of things hardcoded, rather than specified * in their DSDT. * - * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com> + * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org> */ #include <linux/acpi.h> #include <linux/gpio/machine.h> -#include <linux/input.h> +#include <linux/gpio/property.h> +#include <linux/input-event-codes.h> #include <linux/leds.h> #include <linux/pci.h> #include <linux/platform_device.h> @@ -21,102 +22,38 @@ #include "shared-psy-info.h" #include "x86-android-tablets.h" -/* Acer Iconia One 7 B1-750 has an Android factory image with everything hardcoded */ -static const char * const acer_b1_750_mount_matrix[] = { - "-1", "0", "0", - "0", "1", "0", - "0", "0", "1" +/* + * Advantech MICA-071 + * This is a standard Windows tablet, but it has an extra "quick launch" button + * which is not described in the ACPI tables in anyway. + * Use the x86-android-tablets infra to create a gpio-keys device for this. + */ +static const struct software_node advantech_mica_071_gpio_keys_node = { + .name = "prog1_key", }; -static const struct property_entry acer_b1_750_bma250e_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", acer_b1_750_mount_matrix), +static const struct property_entry advantech_mica_071_prog1_key_props[] = { + PROPERTY_ENTRY_U32("linux,code", KEY_PROG1), + PROPERTY_ENTRY_STRING("label", "prog1_key"), + PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[0], 2, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_U32("debounce-interval", 50), { } }; -static const struct software_node acer_b1_750_bma250e_node = { - .properties = acer_b1_750_bma250e_props, +static const struct software_node advantech_mica_071_prog1_key_node = { + .parent = &advantech_mica_071_gpio_keys_node, + .properties = advantech_mica_071_prog1_key_props, }; -static const struct x86_i2c_client_info acer_b1_750_i2c_clients[] __initconst = { - { - /* Novatek NVT-ts touchscreen */ - .board_info = { - .type = "nt11205-ts", - .addr = 0x34, - .dev_name = "NVT-ts", - }, - .adapter_path = "\\_SB_.I2C4", - .irq_data = { - .type = X86_ACPI_IRQ_TYPE_GPIOINT, - .chip = "INT33FC:02", - .index = 3, - .trigger = ACPI_EDGE_SENSITIVE, - .polarity = ACPI_ACTIVE_LOW, - .con_id = "NVT-ts_irq", - }, - }, { - /* BMA250E accelerometer */ - .board_info = { - .type = "bma250e", - .addr = 0x18, - .swnode = &acer_b1_750_bma250e_node, - }, - .adapter_path = "\\_SB_.I2C3", - .irq_data = { - .type = X86_ACPI_IRQ_TYPE_GPIOINT, - .chip = "INT33FC:02", - .index = 25, - .trigger = ACPI_LEVEL_SENSITIVE, - .polarity = ACPI_ACTIVE_HIGH, - .con_id = "bma250e_irq", - }, - }, -}; - -static struct gpiod_lookup_table acer_b1_750_nvt_ts_gpios = { - .dev_id = "i2c-NVT-ts", - .table = { - GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_LOW), - { } - }, -}; - -static struct gpiod_lookup_table * const acer_b1_750_gpios[] = { - &acer_b1_750_nvt_ts_gpios, - &int3496_reference_gpios, +static const struct software_node *advantech_mica_071_button_swnodes[] = { + &advantech_mica_071_gpio_keys_node, + &advantech_mica_071_prog1_key_node, NULL }; -const struct x86_dev_info acer_b1_750_info __initconst = { - .i2c_client_info = acer_b1_750_i2c_clients, - .i2c_client_count = ARRAY_SIZE(acer_b1_750_i2c_clients), - .pdev_info = int3496_pdevs, - .pdev_count = 1, - .gpiod_lookup_tables = acer_b1_750_gpios, -}; - -/* - * Advantech MICA-071 - * This is a standard Windows tablet, but it has an extra "quick launch" button - * which is not described in the ACPI tables in anyway. - * Use the x86-android-tablets infra to create a gpio-keys device for this. - */ -static const struct x86_gpio_button advantech_mica_071_button __initconst = { - .button = { - .code = KEY_PROG1, - .active_low = true, - .desc = "prog1_key", - .type = EV_KEY, - .wakeup = false, - .debounce_interval = 50, - }, - .chip = "INT33FC:00", - .pin = 2, -}; - const struct x86_dev_info advantech_mica_071_info __initconst = { - .gpio_button = &advantech_mica_071_button, - .gpio_button_count = 1, + .gpio_button_swnodes = advantech_mica_071_button_swnodes, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, }; /* @@ -212,36 +149,46 @@ const struct x86_dev_info chuwi_hi8_info __initconst = { * in the button row with the power + volume-buttons labeled P and F. * Use the x86-android-tablets infra to create a gpio-keys device for these. */ -static const struct x86_gpio_button cyberbook_t116_buttons[] __initconst = { - { - .button = { - .code = KEY_PROG1, - .active_low = true, - .desc = "prog1_key", - .type = EV_KEY, - .wakeup = false, - .debounce_interval = 50, - }, - .chip = "INT33FF:00", - .pin = 30, - }, - { - .button = { - .code = KEY_PROG2, - .active_low = true, - .desc = "prog2_key", - .type = EV_KEY, - .wakeup = false, - .debounce_interval = 50, - }, - .chip = "INT33FF:03", - .pin = 48, - }, +static const struct software_node cyberbook_t116_gpio_keys_node = { + .name = "prog_keys", +}; + +static const struct property_entry cyberbook_t116_prog1_key_props[] = { + PROPERTY_ENTRY_U32("linux,code", KEY_PROG1), + PROPERTY_ENTRY_STRING("label", "prog1_key"), + PROPERTY_ENTRY_GPIO("gpios", &cherryview_gpiochip_nodes[0], 30, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_U32("debounce-interval", 50), + { } +}; + +static const struct software_node cyberbook_t116_prog1_key_node = { + .parent = &cyberbook_t116_gpio_keys_node, + .properties = cyberbook_t116_prog1_key_props, +}; + +static const struct property_entry cyberbook_t116_prog2_key_props[] = { + PROPERTY_ENTRY_U32("linux,code", KEY_PROG2), + PROPERTY_ENTRY_STRING("label", "prog2_key"), + PROPERTY_ENTRY_GPIO("gpios", &cherryview_gpiochip_nodes[3], 48, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_U32("debounce-interval", 50), + { } +}; + +static const struct software_node cyberbook_t116_prog2_key_node = { + .parent = &cyberbook_t116_gpio_keys_node, + .properties = cyberbook_t116_prog2_key_props, +}; + +static const struct software_node *cyberbook_t116_buttons_swnodes[] = { + &cyberbook_t116_gpio_keys_node, + &cyberbook_t116_prog1_key_node, + &cyberbook_t116_prog2_key_node, + NULL }; const struct x86_dev_info cyberbook_t116_info __initconst = { - .gpio_button = cyberbook_t116_buttons, - .gpio_button_count = ARRAY_SIZE(cyberbook_t116_buttons), + .gpio_button_swnodes = cyberbook_t116_buttons_swnodes, + .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW, }; #define CZC_EC_EXTRA_PORT 0x68 @@ -297,6 +244,8 @@ static const struct software_node medion_lifetab_s10346_accel_node = { static const struct property_entry medion_lifetab_s10346_touchscreen_props[] = { PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"), PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), + PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("irq-gpios", &baytrail_gpiochip_nodes[2], 3, GPIO_ACTIVE_HIGH), { } }; @@ -340,24 +289,10 @@ static const struct x86_i2c_client_info medion_lifetab_s10346_i2c_clients[] __in }, }; -static struct gpiod_lookup_table medion_lifetab_s10346_goodix_gpios = { - .dev_id = "i2c-goodix_ts", - .table = { - GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("INT33FC:02", 3, "irq", GPIO_ACTIVE_HIGH), - { } - }, -}; - -static struct gpiod_lookup_table * const medion_lifetab_s10346_gpios[] = { - &medion_lifetab_s10346_goodix_gpios, - NULL -}; - const struct x86_dev_info medion_lifetab_s10346_info __initconst = { .i2c_client_info = medion_lifetab_s10346_i2c_clients, .i2c_client_count = ARRAY_SIZE(medion_lifetab_s10346_i2c_clients), - .gpiod_lookup_tables = medion_lifetab_s10346_gpios, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, }; /* Nextbook Ares 8 (BYT) tablets have an Android factory image with everything hardcoded */ @@ -416,17 +351,12 @@ static const struct x86_i2c_client_info nextbook_ares8_i2c_clients[] __initconst }, }; -static struct gpiod_lookup_table * const nextbook_ares8_gpios[] = { - &int3496_reference_gpios, - NULL -}; - const struct x86_dev_info nextbook_ares8_info __initconst = { .i2c_client_info = nextbook_ares8_i2c_clients, .i2c_client_count = ARRAY_SIZE(nextbook_ares8_i2c_clients), .pdev_info = int3496_pdevs, .pdev_count = 1, - .gpiod_lookup_tables = nextbook_ares8_gpios, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, }; /* Nextbook Ares 8A (CHT) tablets have an Android factory image with everything hardcoded */ @@ -445,6 +375,17 @@ static const struct software_node nextbook_ares8a_accel_node = { .properties = nextbook_ares8a_accel_props, }; +static const struct property_entry nextbook_ares8a_ft5416_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 800), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1280), + PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[1], 25, GPIO_ACTIVE_LOW), + { } +}; + +static const struct software_node nextbook_ares8a_ft5416_node = { + .properties = nextbook_ares8a_ft5416_props, +}; + static const struct x86_i2c_client_info nextbook_ares8a_i2c_clients[] __initconst = { { /* Freescale MMA8653FC accelerometer */ @@ -461,7 +402,7 @@ static const struct x86_i2c_client_info nextbook_ares8a_i2c_clients[] __initcons .type = "edt-ft5x06", .addr = 0x38, .dev_name = "ft5416", - .swnode = &nextbook_ares8_touchscreen_node, + .swnode = &nextbook_ares8a_ft5416_node, }, .adapter_path = "\\_SB_.PCI0.I2C6", .irq_data = { @@ -475,23 +416,10 @@ static const struct x86_i2c_client_info nextbook_ares8a_i2c_clients[] __initcons }, }; -static struct gpiod_lookup_table nextbook_ares8a_ft5416_gpios = { - .dev_id = "i2c-ft5416", - .table = { - GPIO_LOOKUP("INT33FF:01", 25, "reset", GPIO_ACTIVE_LOW), - { } - }, -}; - -static struct gpiod_lookup_table * const nextbook_ares8a_gpios[] = { - &nextbook_ares8a_ft5416_gpios, - NULL -}; - const struct x86_dev_info nextbook_ares8a_info __initconst = { .i2c_client_info = nextbook_ares8a_i2c_clients, .i2c_client_count = ARRAY_SIZE(nextbook_ares8a_i2c_clients), - .gpiod_lookup_tables = nextbook_ares8a_gpios, + .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW, }; /* @@ -500,22 +428,32 @@ const struct x86_dev_info nextbook_ares8a_info __initconst = { * This button has a WMI interface, but that is broken. Instead of trying to * use the broken WMI interface, instantiate a gpio-keys device for this. */ -static const struct x86_gpio_button peaq_c1010_button __initconst = { - .button = { - .code = KEY_SOUND, - .active_low = true, - .desc = "dolby_key", - .type = EV_KEY, - .wakeup = false, - .debounce_interval = 50, - }, - .chip = "INT33FC:00", - .pin = 3, +static const struct software_node peaq_c1010_gpio_keys_node = { + .name = "gpio_keys", +}; + +static const struct property_entry peaq_c1010_dolby_key_props[] = { + PROPERTY_ENTRY_U32("linux,code", KEY_SOUND), + PROPERTY_ENTRY_STRING("label", "dolby_key"), + PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[0], 3, GPIO_ACTIVE_LOW), + PROPERTY_ENTRY_U32("debounce-interval", 50), + { } +}; + +static const struct software_node peaq_c1010_dolby_key_node = { + .parent = &peaq_c1010_gpio_keys_node, + .properties = peaq_c1010_dolby_key_props, +}; + +static const struct software_node *peaq_c1010_button_swnodes[] = { + &peaq_c1010_gpio_keys_node, + &peaq_c1010_dolby_key_node, + NULL }; const struct x86_dev_info peaq_c1010_info __initconst = { - .gpio_button = &peaq_c1010_button, - .gpio_button_count = 1, + .gpio_button_swnodes = peaq_c1010_button_swnodes, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, }; /* @@ -543,6 +481,8 @@ static const struct property_entry whitelabel_tm800a550l_goodix_props[] = { PROPERTY_ENTRY_STRING("firmware-name", "gt912-tm800a550l.fw"), PROPERTY_ENTRY_STRING("goodix,config-name", "gt912-tm800a550l.cfg"), PROPERTY_ENTRY_U32("goodix,main-clk", 54), + PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("irq-gpios", &baytrail_gpiochip_nodes[2], 3, GPIO_ACTIVE_HIGH), { } }; @@ -578,24 +518,10 @@ static const struct x86_i2c_client_info whitelabel_tm800a550l_i2c_clients[] __in }, }; -static struct gpiod_lookup_table whitelabel_tm800a550l_goodix_gpios = { - .dev_id = "i2c-goodix_ts", - .table = { - GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("INT33FC:02", 3, "irq", GPIO_ACTIVE_HIGH), - { } - }, -}; - -static struct gpiod_lookup_table * const whitelabel_tm800a550l_gpios[] = { - &whitelabel_tm800a550l_goodix_gpios, - NULL -}; - const struct x86_dev_info whitelabel_tm800a550l_info __initconst = { .i2c_client_info = whitelabel_tm800a550l_i2c_clients, .i2c_client_count = ARRAY_SIZE(whitelabel_tm800a550l_i2c_clients), - .gpiod_lookup_tables = whitelabel_tm800a550l_gpios, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, }; /* @@ -605,6 +531,7 @@ const struct x86_dev_info whitelabel_tm800a550l_info __initconst = { static const struct property_entry vexia_edu_atla10_5v_touchscreen_props[] = { PROPERTY_ENTRY_U32("hid-descr-addr", 0x0000), PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 120), + PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_LOW), { } }; @@ -639,23 +566,10 @@ static const struct x86_i2c_client_info vexia_edu_atla10_5v_i2c_clients[] __init } }; -static struct gpiod_lookup_table vexia_edu_atla10_5v_ft5416_gpios = { - .dev_id = "i2c-FTSC1000", - .table = { - GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_LOW), - { } - }, -}; - -static struct gpiod_lookup_table * const vexia_edu_atla10_5v_gpios[] = { - &vexia_edu_atla10_5v_ft5416_gpios, - NULL -}; - const struct x86_dev_info vexia_edu_atla10_5v_info __initconst = { .i2c_client_info = vexia_edu_atla10_5v_i2c_clients, .i2c_client_count = ARRAY_SIZE(vexia_edu_atla10_5v_i2c_clients), - .gpiod_lookup_tables = vexia_edu_atla10_5v_gpios, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, }; /* @@ -691,6 +605,7 @@ static const struct software_node vexia_edu_atla10_9v_accel_node = { static const struct property_entry vexia_edu_atla10_9v_touchscreen_props[] = { PROPERTY_ENTRY_U32("hid-descr-addr", 0x0000), PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 120), + PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[0], 60, GPIO_ACTIVE_LOW), { } }; @@ -783,19 +698,6 @@ static const struct x86_serdev_info vexia_edu_atla10_9v_serdevs[] __initconst = }, }; -static struct gpiod_lookup_table vexia_edu_atla10_9v_ft5416_gpios = { - .dev_id = "i2c-FTSC1000", - .table = { - GPIO_LOOKUP("INT33FC:00", 60, "reset", GPIO_ACTIVE_LOW), - { } - }, -}; - -static struct gpiod_lookup_table * const vexia_edu_atla10_9v_gpios[] = { - &vexia_edu_atla10_9v_ft5416_gpios, - NULL -}; - static int __init vexia_edu_atla10_9v_init(struct device *dev) { struct pci_dev *pdev; @@ -809,8 +711,10 @@ static int __init vexia_edu_atla10_9v_init(struct device *dev) /* Reprobe the SDIO controller to enumerate the now enabled Wifi module */ pdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0x11, 0)); - if (!pdev) - return -EPROBE_DEFER; + if (!pdev) { + pr_warn("Could not get PCI SDIO at devfn 0x%02x\n", PCI_DEVFN(0x11, 0)); + return 0; + } ret = device_reprobe(&pdev->dev); if (ret) @@ -825,9 +729,9 @@ const struct x86_dev_info vexia_edu_atla10_9v_info __initconst = { .i2c_client_count = ARRAY_SIZE(vexia_edu_atla10_9v_i2c_clients), .serdev_info = vexia_edu_atla10_9v_serdevs, .serdev_count = ARRAY_SIZE(vexia_edu_atla10_9v_serdevs), - .gpiod_lookup_tables = vexia_edu_atla10_9v_gpios, .init = vexia_edu_atla10_9v_init, .use_pci = true, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, }; /* @@ -923,7 +827,6 @@ static int xiaomi_mipad2_brightness_set(struct led_classdev *led_cdev, static int __init xiaomi_mipad2_init(struct device *dev) { struct led_classdev *led_cdev; - int ret; xiaomi_mipad2_led_pwm = devm_pwm_get(dev, "pwm_soc_lpss_2"); if (IS_ERR(xiaomi_mipad2_led_pwm)) @@ -940,16 +843,7 @@ static int __init xiaomi_mipad2_init(struct device *dev) /* Turn LED off during suspend */ led_cdev->flags = LED_CORE_SUSPENDRESUME; - ret = devm_led_classdev_register(dev, led_cdev); - if (ret) - return dev_err_probe(dev, ret, "registering LED\n"); - - return software_node_register_node_group(ktd2026_node_group); -} - -static void xiaomi_mipad2_exit(void) -{ - software_node_unregister_node_group(ktd2026_node_group); + return devm_led_classdev_register(dev, led_cdev); } /* @@ -984,6 +878,6 @@ static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst const struct x86_dev_info xiaomi_mipad2_info __initconst = { .i2c_client_info = xiaomi_mipad2_i2c_clients, .i2c_client_count = ARRAY_SIZE(xiaomi_mipad2_i2c_clients), + .swnode_group = ktd2026_node_group, .init = xiaomi_mipad2_init, - .exit = xiaomi_mipad2_exit, }; diff --git a/drivers/platform/x86/x86-android-tablets/shared-psy-info.c b/drivers/platform/x86/x86-android-tablets/shared-psy-info.c index fe34cedb6257..29fc466f76fe 100644 --- a/drivers/platform/x86/x86-android-tablets/shared-psy-info.c +++ b/drivers/platform/x86/x86-android-tablets/shared-psy-info.c @@ -5,16 +5,18 @@ * devices typically have a bunch of things hardcoded, rather than specified * in their DSDT. * - * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com> + * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org> */ #include <linux/gpio/machine.h> +#include <linux/gpio/property.h> #include <linux/platform_device.h> #include <linux/power/bq24190_charger.h> #include <linux/property.h> #include <linux/regulator/machine.h> #include "shared-psy-info.h" +#include "x86-android-tablets.h" /* Generic / shared charger / battery settings */ const char * const tusb1211_chg_det_psy[] = { "tusb1211-charger-detect" }; @@ -111,6 +113,11 @@ const struct software_node generic_lipo_4v2_battery_node = { .properties = generic_lipo_4v2_battery_props, }; +const struct software_node *generic_lipo_4v2_battery_swnodes[] = { + &generic_lipo_4v2_battery_node, + NULL +}; + /* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV battery */ static const struct property_entry generic_lipo_hv_4v35_battery_props[] = { PROPERTY_ENTRY_STRING("compatible", "simple-battery"), @@ -131,6 +138,11 @@ const struct software_node generic_lipo_hv_4v35_battery_node = { .properties = generic_lipo_hv_4v35_battery_props, }; +const struct software_node *generic_lipo_hv_4v35_battery_swnodes[] = { + &generic_lipo_hv_4v35_battery_node, + NULL +}; + /* For enabling the bq24190 5V boost based on id-pin */ static struct regulator_consumer_supply intel_int3496_consumer = { .supply = "vbus", @@ -156,21 +168,19 @@ const char * const bq24190_modules[] __initconst = { NULL }; -/* Generic platform device array and GPIO lookup table for micro USB ID pin handling */ +static const struct property_entry int3496_reference_props[] __initconst = { + PROPERTY_ENTRY_GPIO("vbus-gpios", &baytrail_gpiochip_nodes[1], 15, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("mux-gpios", &baytrail_gpiochip_nodes[2], 1, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 18, GPIO_ACTIVE_HIGH), + { } +}; + +/* Generic pdevs array and gpio-lookups for micro USB ID pin handling */ const struct platform_device_info int3496_pdevs[] __initconst = { { /* For micro USB ID pin handling */ .name = "intel-int3496", .id = PLATFORM_DEVID_NONE, - }, -}; - -struct gpiod_lookup_table int3496_reference_gpios = { - .dev_id = "intel-int3496", - .table = { - GPIO_LOOKUP("INT33FC:01", 15, "vbus", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_HIGH), - GPIO_LOOKUP("INT33FC:02", 18, "id", GPIO_ACTIVE_HIGH), - { } + .properties = int3496_reference_props, }, }; diff --git a/drivers/platform/x86/x86-android-tablets/shared-psy-info.h b/drivers/platform/x86/x86-android-tablets/shared-psy-info.h index bcf9845ad275..149befba3330 100644 --- a/drivers/platform/x86/x86-android-tablets/shared-psy-info.h +++ b/drivers/platform/x86/x86-android-tablets/shared-psy-info.h @@ -5,13 +5,12 @@ * devices typically have a bunch of things hardcoded, rather than specified * in their DSDT. * - * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com> + * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org> */ #ifndef __PDX86_SHARED_PSY_INFO_H #define __PDX86_SHARED_PSY_INFO_H struct bq24190_platform_data; -struct gpiod_lookup_table; struct platform_device_info; struct software_node; @@ -21,13 +20,16 @@ extern const char * const bq25890_psy[]; extern const struct software_node fg_bq24190_supply_node; extern const struct software_node fg_bq25890_supply_node; + extern const struct software_node generic_lipo_4v2_battery_node; +extern const struct software_node *generic_lipo_4v2_battery_swnodes[]; + extern const struct software_node generic_lipo_hv_4v35_battery_node; +extern const struct software_node *generic_lipo_hv_4v35_battery_swnodes[]; extern struct bq24190_platform_data bq24190_pdata; extern const char * const bq24190_modules[]; extern const struct platform_device_info int3496_pdevs[]; -extern struct gpiod_lookup_table int3496_reference_gpios; #endif diff --git a/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c b/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c index 5d02af1c5aaa..2f8cd8d9e0ab 100644 --- a/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c +++ b/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c @@ -256,6 +256,6 @@ static struct i2c_driver atla10_ec_driver = { }; module_i2c_driver(atla10_ec_driver); -MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); MODULE_DESCRIPTION("Battery driver for Vexia EDU ATLA 10 tablet EC"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h index dcf8d49e3b5f..2498390958ad 100644 --- a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +++ b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h @@ -5,19 +5,17 @@ * devices typically have a bunch of things hardcoded, rather than specified * in their DSDT. * - * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com> + * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org> */ #ifndef __PDX86_X86_ANDROID_TABLETS_H #define __PDX86_X86_ANDROID_TABLETS_H #include <linux/gpio/consumer.h> -#include <linux/gpio_keys.h> #include <linux/i2c.h> #include <linux/irqdomain_defs.h> #include <linux/spi/spi.h> struct gpio_desc; -struct gpiod_lookup_table; struct platform_device_info; struct software_node; @@ -32,6 +30,12 @@ enum x86_acpi_irq_type { X86_ACPI_IRQ_TYPE_PMIC, }; +enum x86_gpiochip_type { + X86_GPIOCHIP_UNSPECIFIED = 0, + X86_GPIOCHIP_BAYTRAIL, + X86_GPIOCHIP_CHERRYVIEW, +}; + struct x86_acpi_irq_data { char *chip; /* GPIO chip label (GPIOINT) or PMIC ACPI path (PMIC) */ enum x86_acpi_irq_type type; @@ -76,29 +80,22 @@ struct x86_serdev_info { const char *serdev_hid; }; -struct x86_gpio_button { - struct gpio_keys_button button; - const char *chip; - int pin; -}; - struct x86_dev_info { const char * const *modules; - const struct software_node *bat_swnode; - struct gpiod_lookup_table * const *gpiod_lookup_tables; + const struct software_node **swnode_group; const struct x86_i2c_client_info *i2c_client_info; const struct x86_spi_dev_info *spi_dev_info; const struct platform_device_info *pdev_info; const struct x86_serdev_info *serdev_info; - const struct x86_gpio_button *gpio_button; + const struct software_node **gpio_button_swnodes; int i2c_client_count; int spi_dev_count; int pdev_count; int serdev_count; - int gpio_button_count; int (*init)(struct device *dev); void (*exit)(void); bool use_pci; + enum x86_gpiochip_type gpiochip_type; }; int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id, @@ -106,10 +103,15 @@ int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id, struct gpio_desc **desc); int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data); +/* Software nodes representing GPIO chips used by various tablets */ +extern const struct software_node baytrail_gpiochip_nodes[]; +extern const struct software_node cherryview_gpiochip_nodes[]; + /* * Extern declarations of x86_dev_info structs so there can be a single * MODULE_DEVICE_TABLE(dmi, ...), while splitting the board descriptions. */ +extern const struct x86_dev_info acer_a1_840_info; extern const struct x86_dev_info acer_b1_750_info; extern const struct x86_dev_info advantech_mica_071_info; extern const struct x86_dev_info asus_me176c_info; diff --git a/drivers/platform/x86/xiaomi-wmi.c b/drivers/platform/x86/xiaomi-wmi.c index cbed29ca502a..b892007b9863 100644 --- a/drivers/platform/x86/xiaomi-wmi.c +++ b/drivers/platform/x86/xiaomi-wmi.c @@ -26,13 +26,6 @@ struct xiaomi_wmi { unsigned int key_code; }; -static void xiaomi_mutex_destroy(void *data) -{ - struct mutex *lock = data; - - mutex_destroy(lock); -} - static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context) { struct xiaomi_wmi *data; @@ -46,8 +39,7 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context) return -ENOMEM; dev_set_drvdata(&wdev->dev, data); - mutex_init(&data->key_lock); - ret = devm_add_action_or_reset(&wdev->dev, xiaomi_mutex_destroy, &data->key_lock); + ret = devm_mutex_init(&wdev->dev, &data->key_lock); if (ret < 0) return ret; diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c index 92d1b62ea239..e9389876229e 100644 --- a/drivers/pps/kapi.c +++ b/drivers/pps/kapi.c @@ -109,16 +109,13 @@ struct pps_device *pps_register_source(struct pps_source_info *info, if (err < 0) { pr_err("%s: unable to create char device\n", info->name); - goto kfree_pps; + goto pps_register_source_exit; } dev_dbg(&pps->dev, "new PPS source %s\n", info->name); return pps; -kfree_pps: - kfree(pps); - pps_register_source_exit: pr_err("%s: unable to register source\n", info->name); diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c index 9463232af8d2..c6b8b6478276 100644 --- a/drivers/pps/pps.c +++ b/drivers/pps/pps.c @@ -374,6 +374,7 @@ int pps_register_cdev(struct pps_device *pps) pps->info.name); err = -EBUSY; } + kfree(pps); goto out_unlock; } pps->id = err; @@ -383,13 +384,11 @@ int pps_register_cdev(struct pps_device *pps) pps->dev.devt = MKDEV(pps_major, pps->id); dev_set_drvdata(&pps->dev, pps); dev_set_name(&pps->dev, "pps%d", pps->id); + pps->dev.release = pps_device_destruct; err = device_register(&pps->dev); if (err) goto free_idr; - /* Override the release function with our own */ - pps->dev.release = pps_device_destruct; - pr_debug("source %s got cdev (%d:%d)\n", pps->info.name, pps_major, pps->id); diff --git a/drivers/remoteproc/da8xx_remoteproc.c b/drivers/remoteproc/da8xx_remoteproc.c index 93031f0867d1..e418a2bf5d2e 100644 --- a/drivers/remoteproc/da8xx_remoteproc.c +++ b/drivers/remoteproc/da8xx_remoteproc.c @@ -233,6 +233,13 @@ static int da8xx_rproc_get_internal_memories(struct platform_device *pdev, return 0; } +static void da8xx_rproc_mem_release(void *data) +{ + struct device *dev = data; + + of_reserved_mem_device_release(dev); +} + static int da8xx_rproc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -274,14 +281,13 @@ static int da8xx_rproc_probe(struct platform_device *pdev) ret = of_reserved_mem_device_init(dev); if (ret) return dev_err_probe(dev, ret, "device does not have specific CMA pool\n"); + devm_add_action_or_reset(&pdev->dev, da8xx_rproc_mem_release, &pdev->dev); } - rproc = rproc_alloc(dev, "dsp", &da8xx_rproc_ops, da8xx_fw_name, - sizeof(*drproc)); - if (!rproc) { - ret = -ENOMEM; - goto free_mem; - } + rproc = devm_rproc_alloc(dev, "dsp", &da8xx_rproc_ops, da8xx_fw_name, + sizeof(*drproc)); + if (!rproc) + return -ENOMEM; /* error recovery is not supported at present */ rproc->recovery_disabled = true; @@ -294,9 +300,7 @@ static int da8xx_rproc_probe(struct platform_device *pdev) ret = da8xx_rproc_get_internal_memories(pdev, drproc); if (ret) - goto free_rproc; - - platform_set_drvdata(pdev, rproc); + return ret; /* everything the ISR needs is now setup, so hook it up */ ret = devm_request_threaded_irq(dev, irq, da8xx_rproc_callback, @@ -304,7 +308,7 @@ static int da8xx_rproc_probe(struct platform_device *pdev) rproc); if (ret) { dev_err(dev, "devm_request_threaded_irq error: %d\n", ret); - goto free_rproc; + return ret; } /* @@ -314,7 +318,7 @@ static int da8xx_rproc_probe(struct platform_device *pdev) */ ret = reset_control_assert(dsp_reset); if (ret) - goto free_rproc; + return ret; drproc->chipsig = chipsig; drproc->bootreg = bootreg; @@ -322,39 +326,13 @@ static int da8xx_rproc_probe(struct platform_device *pdev) drproc->irq_data = irq_data; drproc->irq = irq; - ret = rproc_add(rproc); + ret = devm_rproc_add(dev, rproc); if (ret) { dev_err(dev, "rproc_add failed: %d\n", ret); - goto free_rproc; + return ret; } return 0; - -free_rproc: - rproc_free(rproc); -free_mem: - if (dev->of_node) - of_reserved_mem_device_release(dev); - return ret; -} - -static void da8xx_rproc_remove(struct platform_device *pdev) -{ - struct rproc *rproc = platform_get_drvdata(pdev); - struct da8xx_rproc *drproc = rproc->priv; - struct device *dev = &pdev->dev; - - /* - * The devm subsystem might end up releasing things before - * freeing the irq, thus allowing an interrupt to sneak in while - * the device is being removed. This should prevent that. - */ - disable_irq(drproc->irq); - - rproc_del(rproc); - rproc_free(rproc); - if (dev->of_node) - of_reserved_mem_device_release(dev); } static const struct of_device_id davinci_rproc_of_match[] __maybe_unused = { @@ -365,7 +343,6 @@ MODULE_DEVICE_TABLE(of, davinci_rproc_of_match); static struct platform_driver da8xx_rproc_driver = { .probe = da8xx_rproc_probe, - .remove = da8xx_rproc_remove, .driver = { .name = "davinci-rproc", .of_match_table = of_match_ptr(davinci_rproc_of_match), diff --git a/drivers/remoteproc/imx_dsp_rproc.c b/drivers/remoteproc/imx_dsp_rproc.c index 5ee622bf5352..6e78a01755c7 100644 --- a/drivers/remoteproc/imx_dsp_rproc.c +++ b/drivers/remoteproc/imx_dsp_rproc.c @@ -774,7 +774,6 @@ static int imx_dsp_rproc_prepare(struct rproc *rproc) { struct imx_dsp_rproc *priv = rproc->priv; struct device *dev = rproc->dev.parent; - struct rproc_mem_entry *carveout; int ret; ret = imx_dsp_rproc_add_carveout(priv); @@ -785,15 +784,6 @@ static int imx_dsp_rproc_prepare(struct rproc *rproc) pm_runtime_get_sync(dev); - /* - * Clear buffers after pm rumtime for internal ocram is not - * accessible if power and clock are not enabled. - */ - list_for_each_entry(carveout, &rproc->carveouts, node) { - if (carveout->va) - memset(carveout->va, 0, carveout->len); - } - return 0; } @@ -1022,13 +1012,39 @@ static int imx_dsp_rproc_parse_fw(struct rproc *rproc, const struct firmware *fw return 0; } +static int imx_dsp_rproc_load(struct rproc *rproc, const struct firmware *fw) +{ + struct imx_dsp_rproc *priv = rproc->priv; + const struct imx_dsp_rproc_dcfg *dsp_dcfg = priv->dsp_dcfg; + struct rproc_mem_entry *carveout; + int ret; + + /* Reset DSP if needed */ + if (dsp_dcfg->reset) + dsp_dcfg->reset(priv); + /* + * Clear buffers after pm rumtime for internal ocram is not + * accessible if power and clock are not enabled. + */ + list_for_each_entry(carveout, &rproc->carveouts, node) { + if (carveout->va) + memset(carveout->va, 0, carveout->len); + } + + ret = imx_dsp_rproc_elf_load_segments(rproc, fw); + if (ret) + return ret; + + return 0; +} + static const struct rproc_ops imx_dsp_rproc_ops = { .prepare = imx_dsp_rproc_prepare, .unprepare = imx_dsp_rproc_unprepare, .start = imx_dsp_rproc_start, .stop = imx_dsp_rproc_stop, .kick = imx_dsp_rproc_kick, - .load = imx_dsp_rproc_elf_load_segments, + .load = imx_dsp_rproc_load, .parse_fw = imx_dsp_rproc_parse_fw, .handle_rsc = imx_dsp_rproc_handle_rsc, .find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table, @@ -1189,6 +1205,8 @@ static int imx_dsp_rproc_probe(struct platform_device *pdev) goto err_detach_domains; } + rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_XTENSA); + pm_runtime_enable(dev); return 0; @@ -1214,7 +1232,6 @@ static int imx_dsp_runtime_resume(struct device *dev) { struct rproc *rproc = dev_get_drvdata(dev); struct imx_dsp_rproc *priv = rproc->priv; - const struct imx_dsp_rproc_dcfg *dsp_dcfg = priv->dsp_dcfg; int ret; /* @@ -1235,10 +1252,6 @@ static int imx_dsp_runtime_resume(struct device *dev) return ret; } - /* Reset DSP if needed */ - if (dsp_dcfg->reset) - dsp_dcfg->reset(priv); - return 0; } diff --git a/drivers/remoteproc/imx_rproc.c b/drivers/remoteproc/imx_rproc.c index a6eef0080ca9..bb25221a4a89 100644 --- a/drivers/remoteproc/imx_rproc.c +++ b/drivers/remoteproc/imx_rproc.c @@ -285,161 +285,101 @@ static const struct imx_rproc_att imx_rproc_att_imx6sx[] = { { 0x80000000, 0x80000000, 0x60000000, 0 }, }; -static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mn_mmio = { - .src_reg = IMX7D_SRC_SCR, - .src_mask = IMX7D_M4_RST_MASK, - .src_start = IMX7D_M4_START, - .src_stop = IMX8M_M7_STOP, - .gpr_reg = IMX8M_GPR22, - .gpr_wait = IMX8M_GPR22_CM7_CPUWAIT, - .att = imx_rproc_att_imx8mn, - .att_size = ARRAY_SIZE(imx_rproc_att_imx8mn), - .method = IMX_RPROC_MMIO, -}; - -static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mn = { - .att = imx_rproc_att_imx8mn, - .att_size = ARRAY_SIZE(imx_rproc_att_imx8mn), - .method = IMX_RPROC_SMC, -}; - -static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mq = { - .src_reg = IMX7D_SRC_SCR, - .src_mask = IMX7D_M4_RST_MASK, - .src_start = IMX7D_M4_START, - .src_stop = IMX7D_M4_STOP, - .att = imx_rproc_att_imx8mq, - .att_size = ARRAY_SIZE(imx_rproc_att_imx8mq), - .method = IMX_RPROC_MMIO, -}; +static int imx_rproc_arm_smc_start(struct rproc *rproc) +{ + struct arm_smccc_res res; -static const struct imx_rproc_dcfg imx_rproc_cfg_imx8qm = { - .att = imx_rproc_att_imx8qm, - .att_size = ARRAY_SIZE(imx_rproc_att_imx8qm), - .method = IMX_RPROC_SCU_API, -}; + arm_smccc_smc(IMX_SIP_RPROC, IMX_SIP_RPROC_START, 0, 0, 0, 0, 0, 0, &res); -static const struct imx_rproc_dcfg imx_rproc_cfg_imx8qxp = { - .att = imx_rproc_att_imx8qxp, - .att_size = ARRAY_SIZE(imx_rproc_att_imx8qxp), - .method = IMX_RPROC_SCU_API, -}; + return res.a0; +} -static const struct imx_rproc_dcfg imx_rproc_cfg_imx8ulp = { - .att = imx_rproc_att_imx8ulp, - .att_size = ARRAY_SIZE(imx_rproc_att_imx8ulp), - .method = IMX_RPROC_NONE, -}; +static int imx_rproc_mmio_start(struct rproc *rproc) +{ + struct imx_rproc *priv = rproc->priv; + const struct imx_rproc_dcfg *dcfg = priv->dcfg; -static const struct imx_rproc_dcfg imx_rproc_cfg_imx7ulp = { - .att = imx_rproc_att_imx7ulp, - .att_size = ARRAY_SIZE(imx_rproc_att_imx7ulp), - .method = IMX_RPROC_NONE, - .flags = IMX_RPROC_NEED_SYSTEM_OFF, -}; + if (priv->gpr) + return regmap_clear_bits(priv->gpr, dcfg->gpr_reg, dcfg->gpr_wait); -static const struct imx_rproc_dcfg imx_rproc_cfg_imx7d = { - .src_reg = IMX7D_SRC_SCR, - .src_mask = IMX7D_M4_RST_MASK, - .src_start = IMX7D_M4_START, - .src_stop = IMX7D_M4_STOP, - .att = imx_rproc_att_imx7d, - .att_size = ARRAY_SIZE(imx_rproc_att_imx7d), - .method = IMX_RPROC_MMIO, -}; + return regmap_update_bits(priv->regmap, dcfg->src_reg, dcfg->src_mask, dcfg->src_start); +} -static const struct imx_rproc_dcfg imx_rproc_cfg_imx6sx = { - .src_reg = IMX6SX_SRC_SCR, - .src_mask = IMX6SX_M4_RST_MASK, - .src_start = IMX6SX_M4_START, - .src_stop = IMX6SX_M4_STOP, - .att = imx_rproc_att_imx6sx, - .att_size = ARRAY_SIZE(imx_rproc_att_imx6sx), - .method = IMX_RPROC_MMIO, -}; +static int imx_rproc_scu_api_start(struct rproc *rproc) +{ + struct imx_rproc *priv = rproc->priv; -static const struct imx_rproc_dcfg imx_rproc_cfg_imx93 = { - .att = imx_rproc_att_imx93, - .att_size = ARRAY_SIZE(imx_rproc_att_imx93), - .method = IMX_RPROC_SMC, -}; + return imx_sc_pm_cpu_start(priv->ipc_handle, priv->rsrc_id, true, priv->entry); +} static int imx_rproc_start(struct rproc *rproc) { struct imx_rproc *priv = rproc->priv; const struct imx_rproc_dcfg *dcfg = priv->dcfg; struct device *dev = priv->dev; - struct arm_smccc_res res; int ret; ret = imx_rproc_xtr_mbox_init(rproc, true); if (ret) return ret; - switch (dcfg->method) { - case IMX_RPROC_MMIO: - if (priv->gpr) { - ret = regmap_clear_bits(priv->gpr, dcfg->gpr_reg, - dcfg->gpr_wait); - } else { - ret = regmap_update_bits(priv->regmap, dcfg->src_reg, - dcfg->src_mask, - dcfg->src_start); - } - break; - case IMX_RPROC_SMC: - arm_smccc_smc(IMX_SIP_RPROC, IMX_SIP_RPROC_START, 0, 0, 0, 0, 0, 0, &res); - ret = res.a0; - break; - case IMX_RPROC_SCU_API: - ret = imx_sc_pm_cpu_start(priv->ipc_handle, priv->rsrc_id, true, priv->entry); - break; - default: + if (!dcfg->ops || !dcfg->ops->start) return -EOPNOTSUPP; - } + ret = dcfg->ops->start(rproc); if (ret) dev_err(dev, "Failed to enable remote core!\n"); return ret; } -static int imx_rproc_stop(struct rproc *rproc) +static int imx_rproc_arm_smc_stop(struct rproc *rproc) { struct imx_rproc *priv = rproc->priv; - const struct imx_rproc_dcfg *dcfg = priv->dcfg; - struct device *dev = priv->dev; struct arm_smccc_res res; + + arm_smccc_smc(IMX_SIP_RPROC, IMX_SIP_RPROC_STOP, 0, 0, 0, 0, 0, 0, &res); + if (res.a1) + dev_info(priv->dev, "Not in wfi, force stopped\n"); + + return res.a0; +} + +static int imx_rproc_mmio_stop(struct rproc *rproc) +{ + struct imx_rproc *priv = rproc->priv; + const struct imx_rproc_dcfg *dcfg = priv->dcfg; int ret; - switch (dcfg->method) { - case IMX_RPROC_MMIO: - if (priv->gpr) { - ret = regmap_set_bits(priv->gpr, dcfg->gpr_reg, - dcfg->gpr_wait); - if (ret) { - dev_err(priv->dev, - "Failed to quiescence M4 platform!\n"); - return ret; - } + if (priv->gpr) { + ret = regmap_set_bits(priv->gpr, dcfg->gpr_reg, dcfg->gpr_wait); + if (ret) { + dev_err(priv->dev, "Failed to quiescence M4 platform!\n"); + return ret; } + } + + return regmap_update_bits(priv->regmap, dcfg->src_reg, dcfg->src_mask, dcfg->src_stop); +} + +static int imx_rproc_scu_api_stop(struct rproc *rproc) +{ + struct imx_rproc *priv = rproc->priv; + + return imx_sc_pm_cpu_start(priv->ipc_handle, priv->rsrc_id, false, priv->entry); +} + +static int imx_rproc_stop(struct rproc *rproc) +{ + struct imx_rproc *priv = rproc->priv; + const struct imx_rproc_dcfg *dcfg = priv->dcfg; + struct device *dev = priv->dev; + int ret; - ret = regmap_update_bits(priv->regmap, dcfg->src_reg, dcfg->src_mask, - dcfg->src_stop); - break; - case IMX_RPROC_SMC: - arm_smccc_smc(IMX_SIP_RPROC, IMX_SIP_RPROC_STOP, 0, 0, 0, 0, 0, 0, &res); - ret = res.a0; - if (res.a1) - dev_info(dev, "Not in wfi, force stopped\n"); - break; - case IMX_RPROC_SCU_API: - ret = imx_sc_pm_cpu_start(priv->ipc_handle, priv->rsrc_id, false, priv->entry); - break; - default: + if (!dcfg->ops || !dcfg->ops->stop) return -EOPNOTSUPP; - } + ret = dcfg->ops->stop(rproc); if (ret) dev_err(dev, "Failed to stop remote core\n"); else @@ -922,84 +862,27 @@ static int imx_rproc_attach_pd(struct imx_rproc *priv) return 0; } -static int imx_rproc_detect_mode(struct imx_rproc *priv) +static int imx_rproc_arm_smc_detect_mode(struct rproc *rproc) { - struct regmap_config config = { .name = "imx-rproc" }; - const struct imx_rproc_dcfg *dcfg = priv->dcfg; - struct device *dev = priv->dev; - struct regmap *regmap; + struct imx_rproc *priv = rproc->priv; struct arm_smccc_res res; - int ret; - u32 val; - u8 pt; - switch (dcfg->method) { - case IMX_RPROC_NONE: + arm_smccc_smc(IMX_SIP_RPROC, IMX_SIP_RPROC_STARTED, 0, 0, 0, 0, 0, 0, &res); + if (res.a0) priv->rproc->state = RPROC_DETACHED; - return 0; - case IMX_RPROC_SMC: - arm_smccc_smc(IMX_SIP_RPROC, IMX_SIP_RPROC_STARTED, 0, 0, 0, 0, 0, 0, &res); - if (res.a0) - priv->rproc->state = RPROC_DETACHED; - return 0; - case IMX_RPROC_SCU_API: - ret = imx_scu_get_handle(&priv->ipc_handle); - if (ret) - return ret; - ret = of_property_read_u32(dev->of_node, "fsl,resource-id", &priv->rsrc_id); - if (ret) { - dev_err(dev, "No fsl,resource-id property\n"); - return ret; - } - - if (priv->rsrc_id == IMX_SC_R_M4_1_PID0) - priv->core_index = 1; - else - priv->core_index = 0; - /* - * If Mcore resource is not owned by Acore partition, It is kicked by ROM, - * and Linux could only do IPC with Mcore and nothing else. - */ - if (imx_sc_rm_is_resource_owned(priv->ipc_handle, priv->rsrc_id)) { - if (of_property_read_u32(dev->of_node, "fsl,entry-address", &priv->entry)) - return -EINVAL; - - return imx_rproc_attach_pd(priv); - } - - priv->rproc->state = RPROC_DETACHED; - priv->rproc->recovery_disabled = false; - rproc_set_feature(priv->rproc, RPROC_FEAT_ATTACH_ON_RECOVERY); - - /* Get partition id and enable irq in SCFW */ - ret = imx_sc_rm_get_resource_owner(priv->ipc_handle, priv->rsrc_id, &pt); - if (ret) { - dev_err(dev, "not able to get resource owner\n"); - return ret; - } - - priv->rproc_pt = pt; - priv->rproc_nb.notifier_call = imx_rproc_partition_notify; - - ret = imx_scu_irq_register_notifier(&priv->rproc_nb); - if (ret) { - dev_err(dev, "register scu notifier failed, %d\n", ret); - return ret; - } - - ret = imx_scu_irq_group_enable(IMX_SC_IRQ_GROUP_REBOOTED, BIT(priv->rproc_pt), - true); - if (ret) { - imx_scu_irq_unregister_notifier(&priv->rproc_nb); - dev_err(dev, "Enable irq failed, %d\n", ret); - return ret; - } + return 0; +} - return 0; - default: - break; - } +static int imx_rproc_mmio_detect_mode(struct rproc *rproc) +{ + const struct regmap_config config = { .name = "imx-rproc" }; + struct imx_rproc *priv = rproc->priv; + const struct imx_rproc_dcfg *dcfg = priv->dcfg; + struct device *dev = priv->dev; + struct regmap *regmap; + u32 val; + int ret; priv->gpr = syscon_regmap_lookup_by_phandle(dev->of_node, "fsl,iomuxc-gpr"); if (IS_ERR(priv->gpr)) @@ -1039,6 +922,85 @@ static int imx_rproc_detect_mode(struct imx_rproc *priv) return 0; } +static int imx_rproc_scu_api_detect_mode(struct rproc *rproc) +{ + struct imx_rproc *priv = rproc->priv; + struct device *dev = priv->dev; + int ret; + u8 pt; + + ret = imx_scu_get_handle(&priv->ipc_handle); + if (ret) + return ret; + ret = of_property_read_u32(dev->of_node, "fsl,resource-id", &priv->rsrc_id); + if (ret) { + dev_err(dev, "No fsl,resource-id property\n"); + return ret; + } + + if (priv->rsrc_id == IMX_SC_R_M4_1_PID0) + priv->core_index = 1; + else + priv->core_index = 0; + + /* + * If Mcore resource is not owned by Acore partition, It is kicked by ROM, + * and Linux could only do IPC with Mcore and nothing else. + */ + if (imx_sc_rm_is_resource_owned(priv->ipc_handle, priv->rsrc_id)) { + if (of_property_read_u32(dev->of_node, "fsl,entry-address", &priv->entry)) + return -EINVAL; + + return imx_rproc_attach_pd(priv); + } + + priv->rproc->state = RPROC_DETACHED; + priv->rproc->recovery_disabled = false; + rproc_set_feature(priv->rproc, RPROC_FEAT_ATTACH_ON_RECOVERY); + + /* Get partition id and enable irq in SCFW */ + ret = imx_sc_rm_get_resource_owner(priv->ipc_handle, priv->rsrc_id, &pt); + if (ret) { + dev_err(dev, "not able to get resource owner\n"); + return ret; + } + + priv->rproc_pt = pt; + priv->rproc_nb.notifier_call = imx_rproc_partition_notify; + + ret = imx_scu_irq_register_notifier(&priv->rproc_nb); + if (ret) { + dev_err(dev, "register scu notifier failed, %d\n", ret); + return ret; + } + + ret = imx_scu_irq_group_enable(IMX_SC_IRQ_GROUP_REBOOTED, BIT(priv->rproc_pt), + true); + if (ret) { + imx_scu_irq_unregister_notifier(&priv->rproc_nb); + dev_err(dev, "Enable irq failed, %d\n", ret); + return ret; + } + + return 0; +} + +static int imx_rproc_detect_mode(struct imx_rproc *priv) +{ + const struct imx_rproc_dcfg *dcfg = priv->dcfg; + + /* + * To i.MX{7,8} ULP, Linux is under control of RTOS, no need + * dcfg->ops or dcfg->ops->detect_mode, it is state RPROC_DETACHED. + */ + if (!dcfg->ops || !dcfg->ops->detect_mode) { + priv->rproc->state = RPROC_DETACHED; + return 0; + } + + return dcfg->ops->detect_mode(priv->rproc); +} + static int imx_rproc_clk_enable(struct imx_rproc *priv) { const struct imx_rproc_dcfg *dcfg = priv->dcfg; @@ -1207,6 +1169,111 @@ static void imx_rproc_remove(struct platform_device *pdev) destroy_workqueue(priv->workqueue); } +static const struct imx_rproc_plat_ops imx_rproc_ops_arm_smc = { + .start = imx_rproc_arm_smc_start, + .stop = imx_rproc_arm_smc_stop, + .detect_mode = imx_rproc_arm_smc_detect_mode, +}; + +static const struct imx_rproc_plat_ops imx_rproc_ops_mmio = { + .start = imx_rproc_mmio_start, + .stop = imx_rproc_mmio_stop, + .detect_mode = imx_rproc_mmio_detect_mode, +}; + +static const struct imx_rproc_plat_ops imx_rproc_ops_scu_api = { + .start = imx_rproc_scu_api_start, + .stop = imx_rproc_scu_api_stop, + .detect_mode = imx_rproc_scu_api_detect_mode, +}; + +static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mn_mmio = { + .src_reg = IMX7D_SRC_SCR, + .src_mask = IMX7D_M4_RST_MASK, + .src_start = IMX7D_M4_START, + .src_stop = IMX8M_M7_STOP, + .gpr_reg = IMX8M_GPR22, + .gpr_wait = IMX8M_GPR22_CM7_CPUWAIT, + .att = imx_rproc_att_imx8mn, + .att_size = ARRAY_SIZE(imx_rproc_att_imx8mn), + .method = IMX_RPROC_MMIO, + .ops = &imx_rproc_ops_mmio, +}; + +static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mn = { + .att = imx_rproc_att_imx8mn, + .att_size = ARRAY_SIZE(imx_rproc_att_imx8mn), + .method = IMX_RPROC_SMC, + .ops = &imx_rproc_ops_arm_smc, +}; + +static const struct imx_rproc_dcfg imx_rproc_cfg_imx8mq = { + .src_reg = IMX7D_SRC_SCR, + .src_mask = IMX7D_M4_RST_MASK, + .src_start = IMX7D_M4_START, + .src_stop = IMX7D_M4_STOP, + .att = imx_rproc_att_imx8mq, + .att_size = ARRAY_SIZE(imx_rproc_att_imx8mq), + .method = IMX_RPROC_MMIO, + .ops = &imx_rproc_ops_mmio, +}; + +static const struct imx_rproc_dcfg imx_rproc_cfg_imx8qm = { + .att = imx_rproc_att_imx8qm, + .att_size = ARRAY_SIZE(imx_rproc_att_imx8qm), + .method = IMX_RPROC_SCU_API, + .ops = &imx_rproc_ops_scu_api, +}; + +static const struct imx_rproc_dcfg imx_rproc_cfg_imx8qxp = { + .att = imx_rproc_att_imx8qxp, + .att_size = ARRAY_SIZE(imx_rproc_att_imx8qxp), + .method = IMX_RPROC_SCU_API, + .ops = &imx_rproc_ops_scu_api, +}; + +static const struct imx_rproc_dcfg imx_rproc_cfg_imx8ulp = { + .att = imx_rproc_att_imx8ulp, + .att_size = ARRAY_SIZE(imx_rproc_att_imx8ulp), + .method = IMX_RPROC_NONE, +}; + +static const struct imx_rproc_dcfg imx_rproc_cfg_imx7ulp = { + .att = imx_rproc_att_imx7ulp, + .att_size = ARRAY_SIZE(imx_rproc_att_imx7ulp), + .method = IMX_RPROC_NONE, + .flags = IMX_RPROC_NEED_SYSTEM_OFF, +}; + +static const struct imx_rproc_dcfg imx_rproc_cfg_imx7d = { + .src_reg = IMX7D_SRC_SCR, + .src_mask = IMX7D_M4_RST_MASK, + .src_start = IMX7D_M4_START, + .src_stop = IMX7D_M4_STOP, + .att = imx_rproc_att_imx7d, + .att_size = ARRAY_SIZE(imx_rproc_att_imx7d), + .method = IMX_RPROC_MMIO, + .ops = &imx_rproc_ops_mmio, +}; + +static const struct imx_rproc_dcfg imx_rproc_cfg_imx6sx = { + .src_reg = IMX6SX_SRC_SCR, + .src_mask = IMX6SX_M4_RST_MASK, + .src_start = IMX6SX_M4_START, + .src_stop = IMX6SX_M4_STOP, + .att = imx_rproc_att_imx6sx, + .att_size = ARRAY_SIZE(imx_rproc_att_imx6sx), + .method = IMX_RPROC_MMIO, + .ops = &imx_rproc_ops_mmio, +}; + +static const struct imx_rproc_dcfg imx_rproc_cfg_imx93 = { + .att = imx_rproc_att_imx93, + .att_size = ARRAY_SIZE(imx_rproc_att_imx93), + .method = IMX_RPROC_SMC, + .ops = &imx_rproc_ops_arm_smc, +}; + static const struct of_device_id imx_rproc_of_match[] = { { .compatible = "fsl,imx7ulp-cm4", .data = &imx_rproc_cfg_imx7ulp }, { .compatible = "fsl,imx7d-cm4", .data = &imx_rproc_cfg_imx7d }, diff --git a/drivers/remoteproc/imx_rproc.h b/drivers/remoteproc/imx_rproc.h index cfd38d37e146..3a9adaaf048b 100644 --- a/drivers/remoteproc/imx_rproc.h +++ b/drivers/remoteproc/imx_rproc.h @@ -31,6 +31,12 @@ enum imx_rproc_method { /* dcfg flags */ #define IMX_RPROC_NEED_SYSTEM_OFF BIT(0) +struct imx_rproc_plat_ops { + int (*start)(struct rproc *rproc); + int (*stop)(struct rproc *rproc); + int (*detect_mode)(struct rproc *rproc); +}; + struct imx_rproc_dcfg { u32 src_reg; u32 src_mask; @@ -42,6 +48,7 @@ struct imx_rproc_dcfg { size_t att_size; enum imx_rproc_method method; u32 flags; + const struct imx_rproc_plat_ops *ops; }; #endif /* _IMX_RPROC_H */ diff --git a/drivers/remoteproc/keystone_remoteproc.c b/drivers/remoteproc/keystone_remoteproc.c index 7b41b4547fa8..4d6550b48567 100644 --- a/drivers/remoteproc/keystone_remoteproc.c +++ b/drivers/remoteproc/keystone_remoteproc.c @@ -349,6 +349,20 @@ static int keystone_rproc_of_get_dev_syscon(struct platform_device *pdev, return 0; } +static void keystone_rproc_mem_release(void *data) +{ + struct device *dev = data; + + of_reserved_mem_device_release(dev); +} + +static void keystone_rproc_pm_runtime_put(void *data) +{ + struct device *dev = data; + + pm_runtime_put_sync(dev); +} + static int keystone_rproc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -394,80 +408,58 @@ static int keystone_rproc_probe(struct platform_device *pdev) return PTR_ERR(ksproc->reset); /* enable clock for accessing DSP internal memories */ - pm_runtime_enable(dev); + ret = devm_pm_runtime_enable(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + ret = pm_runtime_resume_and_get(dev); - if (ret < 0) { - dev_err(dev, "failed to enable clock, status = %d\n", ret); - goto disable_rpm; - } + if (ret < 0) + return dev_err_probe(dev, ret, "failed to enable clock\n"); + + ret = devm_add_action_or_reset(dev, keystone_rproc_pm_runtime_put, dev); + if (ret) + return dev_err_probe(dev, ret, "failed to add disable pm devm action\n"); ret = keystone_rproc_of_get_memories(pdev, ksproc); if (ret) - goto disable_clk; + return ret; ksproc->irq_ring = platform_get_irq_byname(pdev, "vring"); - if (ksproc->irq_ring < 0) { - ret = ksproc->irq_ring; - goto disable_clk; - } + if (ksproc->irq_ring < 0) + return ksproc->irq_ring; ksproc->irq_fault = platform_get_irq_byname(pdev, "exception"); - if (ksproc->irq_fault < 0) { - ret = ksproc->irq_fault; - goto disable_clk; - } + if (ksproc->irq_fault < 0) + return ksproc->irq_fault; - ksproc->kick_gpio = gpiod_get(dev, "kick", GPIOD_ASIS); + ksproc->kick_gpio = devm_gpiod_get(dev, "kick", GPIOD_ASIS); ret = PTR_ERR_OR_ZERO(ksproc->kick_gpio); - if (ret) { - dev_err(dev, "failed to get gpio for virtio kicks, status = %d\n", - ret); - goto disable_clk; - } + if (ret) + return dev_err_probe(dev, ret, "failed to get gpio for virtio kicks\n"); - if (of_reserved_mem_device_init(dev)) + ret = of_reserved_mem_device_init(dev); + if (ret) { dev_warn(dev, "device does not have specific CMA pool\n"); + } else { + ret = devm_add_action_or_reset(dev, keystone_rproc_mem_release, dev); + if (ret) + return ret; + } /* ensure the DSP is in reset before loading firmware */ ret = reset_control_status(ksproc->reset); if (ret < 0) { - dev_err(dev, "failed to get reset status, status = %d\n", ret); - goto release_mem; + return dev_err_probe(dev, ret, "failed to get reset status\n"); } else if (ret == 0) { WARN(1, "device is not in reset\n"); keystone_rproc_dsp_reset(ksproc); } - ret = rproc_add(rproc); - if (ret) { - dev_err(dev, "failed to add register device with remoteproc core, status = %d\n", - ret); - goto release_mem; - } - - platform_set_drvdata(pdev, ksproc); + ret = devm_rproc_add(dev, rproc); + if (ret) + return dev_err_probe(dev, ret, "failed to register device with remoteproc core\n"); return 0; - -release_mem: - of_reserved_mem_device_release(dev); - gpiod_put(ksproc->kick_gpio); -disable_clk: - pm_runtime_put_sync(dev); -disable_rpm: - pm_runtime_disable(dev); - return ret; -} - -static void keystone_rproc_remove(struct platform_device *pdev) -{ - struct keystone_rproc *ksproc = platform_get_drvdata(pdev); - - rproc_del(ksproc->rproc); - gpiod_put(ksproc->kick_gpio); - pm_runtime_put_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); - of_reserved_mem_device_release(&pdev->dev); } static const struct of_device_id keystone_rproc_of_match[] = { @@ -481,7 +473,6 @@ MODULE_DEVICE_TABLE(of, keystone_rproc_of_match); static struct platform_driver keystone_rproc_driver = { .probe = keystone_rproc_probe, - .remove = keystone_rproc_remove, .driver = { .name = "keystone-rproc", .of_match_table = keystone_rproc_of_match, diff --git a/drivers/remoteproc/pru_rproc.c b/drivers/remoteproc/pru_rproc.c index 842e4b6cc5f9..5e3eb7b86a0e 100644 --- a/drivers/remoteproc/pru_rproc.c +++ b/drivers/remoteproc/pru_rproc.c @@ -340,7 +340,7 @@ EXPORT_SYMBOL_GPL(pru_rproc_put); */ int pru_rproc_set_ctable(struct rproc *rproc, enum pru_ctable_idx c, u32 addr) { - struct pru_rproc *pru = rproc->priv; + struct pru_rproc *pru; unsigned int reg; u32 mask, set; u16 idx; @@ -352,6 +352,7 @@ int pru_rproc_set_ctable(struct rproc *rproc, enum pru_ctable_idx c, u32 addr) if (!rproc->dev.parent || !is_pru_rproc(rproc->dev.parent)) return -ENODEV; + pru = rproc->priv; /* pointer is 16 bit and index is 8-bit so mask out the rest */ idx_mask = (c >= PRU_C28) ? 0xFFFF : 0xFF; diff --git a/drivers/remoteproc/qcom_q6v5.c b/drivers/remoteproc/qcom_q6v5.c index 4ee5e67a9f03..58d5b85e58cd 100644 --- a/drivers/remoteproc/qcom_q6v5.c +++ b/drivers/remoteproc/qcom_q6v5.c @@ -156,9 +156,6 @@ int qcom_q6v5_wait_for_start(struct qcom_q6v5 *q6v5, int timeout) int ret; ret = wait_for_completion_timeout(&q6v5->start_done, timeout); - if (!ret) - disable_irq(q6v5->handover_irq); - return !ret ? -ETIMEDOUT : 0; } EXPORT_SYMBOL_GPL(qcom_q6v5_wait_for_start); @@ -167,6 +164,11 @@ static irqreturn_t q6v5_handover_interrupt(int irq, void *data) { struct qcom_q6v5 *q6v5 = data; + if (q6v5->handover_issued) { + dev_err(q6v5->dev, "Handover signaled, but it already happened\n"); + return IRQ_HANDLED; + } + if (q6v5->handover) q6v5->handover(q6v5); diff --git a/drivers/remoteproc/qcom_q6v5_mss.c b/drivers/remoteproc/qcom_q6v5_mss.c index 0c0199fb0e68..3087d895b87f 100644 --- a/drivers/remoteproc/qcom_q6v5_mss.c +++ b/drivers/remoteproc/qcom_q6v5_mss.c @@ -498,6 +498,8 @@ static void q6v5_debug_policy_load(struct q6v5 *qproc, void *mba_region) release_firmware(dp_fw); } +#define MSM8974_B00_OFFSET 0x1000 + static int q6v5_load(struct rproc *rproc, const struct firmware *fw) { struct q6v5 *qproc = rproc->priv; @@ -516,7 +518,14 @@ static int q6v5_load(struct rproc *rproc, const struct firmware *fw) return -EBUSY; } - memcpy(mba_region, fw->data, fw->size); + if ((qproc->version == MSS_MSM8974 || + qproc->version == MSS_MSM8226 || + qproc->version == MSS_MSM8926) && + fw->size > MSM8974_B00_OFFSET && + !memcmp(fw->data, ELFMAG, SELFMAG)) + memcpy(mba_region, fw->data + MSM8974_B00_OFFSET, fw->size - MSM8974_B00_OFFSET); + else + memcpy(mba_region, fw->data, fw->size); q6v5_debug_policy_load(qproc, mba_region); memunmap(mba_region); diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index 55a7da801183..158bcd6cc85c 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -42,6 +42,7 @@ struct qcom_pas_data { int pas_id; int dtb_pas_id; int lite_pas_id; + int lite_dtb_pas_id; unsigned int minidump_id; bool auto_boot; bool decrypt_shutdown; @@ -80,6 +81,7 @@ struct qcom_pas { int pas_id; int dtb_pas_id; int lite_pas_id; + int lite_dtb_pas_id; unsigned int minidump_id; int crash_reason_smem; unsigned int smem_host_id; @@ -225,7 +227,9 @@ static int qcom_pas_load(struct rproc *rproc, const struct firmware *fw) pas->firmware = fw; if (pas->lite_pas_id) - ret = qcom_scm_pas_shutdown(pas->lite_pas_id); + qcom_scm_pas_shutdown(pas->lite_pas_id); + if (pas->lite_dtb_pas_id) + qcom_scm_pas_shutdown(pas->lite_dtb_pas_id); if (pas->dtb_pas_id) { ret = request_firmware(&pas->dtb_firmware, pas->dtb_firmware_name, pas->dev); @@ -721,6 +725,7 @@ static int qcom_pas_probe(struct platform_device *pdev) pas->minidump_id = desc->minidump_id; pas->pas_id = desc->pas_id; pas->lite_pas_id = desc->lite_pas_id; + pas->lite_dtb_pas_id = desc->lite_dtb_pas_id; pas->info_name = desc->sysmon_name; pas->smem_host_id = desc->smem_host_id; pas->decrypt_shutdown = desc->decrypt_shutdown; @@ -1084,6 +1089,7 @@ static const struct qcom_pas_data x1e80100_adsp_resource = { .pas_id = 1, .dtb_pas_id = 0x24, .lite_pas_id = 0x1f, + .lite_dtb_pas_id = 0x29, .minidump_id = 5, .auto_boot = true, .proxy_pd_names = (char*[]){ @@ -1255,6 +1261,26 @@ static const struct qcom_pas_data sdx55_mpss_resource = { .ssctl_id = 0x22, }; +static const struct qcom_pas_data milos_cdsp_resource = { + .crash_reason_smem = 601, + .firmware_name = "cdsp.mbn", + .dtb_firmware_name = "cdsp_dtb.mbn", + .pas_id = 18, + .dtb_pas_id = 0x25, + .minidump_id = 7, + .auto_boot = true, + .proxy_pd_names = (char*[]){ + "cx", + "mx", + NULL + }, + .load_state = "cdsp", + .ssr_name = "cdsp", + .sysmon_name = "cdsp", + .ssctl_id = 0x17, + .smem_host_id = 5, +}; + static const struct qcom_pas_data sm8450_mpss_resource = { .crash_reason_smem = 421, .firmware_name = "modem.mdt", @@ -1429,6 +1455,10 @@ static const struct qcom_pas_data sm8750_mpss_resource = { }; static const struct of_device_id qcom_pas_of_match[] = { + { .compatible = "qcom,milos-adsp-pas", .data = &sm8550_adsp_resource}, + { .compatible = "qcom,milos-cdsp-pas", .data = &milos_cdsp_resource}, + { .compatible = "qcom,milos-mpss-pas", .data = &sm8450_mpss_resource}, + { .compatible = "qcom,milos-wpss-pas", .data = &sc7280_wpss_resource}, { .compatible = "qcom,msm8226-adsp-pil", .data = &msm8996_adsp_resource}, { .compatible = "qcom,msm8953-adsp-pil", .data = &msm8996_adsp_resource}, { .compatible = "qcom,msm8974-adsp-pil", .data = &adsp_resource_init}, diff --git a/drivers/remoteproc/ti_k3_common.c b/drivers/remoteproc/ti_k3_common.c index d4f20900f33b..56b71652e449 100644 --- a/drivers/remoteproc/ti_k3_common.c +++ b/drivers/remoteproc/ti_k3_common.c @@ -155,6 +155,13 @@ int k3_rproc_release(struct k3_rproc *kproc) } EXPORT_SYMBOL_GPL(k3_rproc_release); +static void k3_rproc_free_channel(void *data) +{ + struct k3_rproc *kproc = data; + + mbox_free_channel(kproc->mbox); +} + int k3_rproc_request_mbox(struct rproc *rproc) { struct k3_rproc *kproc = rproc->priv; @@ -173,19 +180,9 @@ int k3_rproc_request_mbox(struct rproc *rproc) return dev_err_probe(dev, PTR_ERR(kproc->mbox), "mbox_request_channel failed\n"); - /* - * Ping the remote processor, this is only for sanity-sake for now; - * there is no functional effect whatsoever. - * - * Note that the reply will _not_ arrive immediately: this message - * will wait in the mailbox fifo until the remote processor is booted. - */ - ret = mbox_send_message(kproc->mbox, (void *)RP_MBOX_ECHO_REQUEST); - if (ret < 0) { - dev_err(dev, "mbox_send_message failed (%pe)\n", ERR_PTR(ret)); - mbox_free_channel(kproc->mbox); + ret = devm_add_action_or_reset(dev, k3_rproc_free_channel, kproc); + if (ret) return ret; - } return 0; } diff --git a/drivers/remoteproc/ti_k3_dsp_remoteproc.c b/drivers/remoteproc/ti_k3_dsp_remoteproc.c index 7a72933bd403..d6ceea6dc920 100644 --- a/drivers/remoteproc/ti_k3_dsp_remoteproc.c +++ b/drivers/remoteproc/ti_k3_dsp_remoteproc.c @@ -175,8 +175,6 @@ static void k3_dsp_rproc_remove(struct platform_device *pdev) if (ret) dev_err(dev, "failed to detach proc (%pe)\n", ERR_PTR(ret)); } - - mbox_free_channel(kproc->mbox); } static const struct k3_rproc_mem_data c66_mems[] = { diff --git a/drivers/remoteproc/ti_k3_r5_remoteproc.c b/drivers/remoteproc/ti_k3_r5_remoteproc.c index ca5ff280d2dc..04f23295ffc1 100644 --- a/drivers/remoteproc/ti_k3_r5_remoteproc.c +++ b/drivers/remoteproc/ti_k3_r5_remoteproc.c @@ -1206,8 +1206,6 @@ static void k3_r5_cluster_rproc_exit(void *data) return; } } - - mbox_free_channel(kproc->mbox); } } diff --git a/drivers/remoteproc/wkup_m3_rproc.c b/drivers/remoteproc/wkup_m3_rproc.c index d8be21e71721..2d5bfbefcacc 100644 --- a/drivers/remoteproc/wkup_m3_rproc.c +++ b/drivers/remoteproc/wkup_m3_rproc.c @@ -125,6 +125,13 @@ static const struct of_device_id wkup_m3_rproc_of_match[] = { }; MODULE_DEVICE_TABLE(of, wkup_m3_rproc_of_match); +static void wkup_m3_rproc_pm_runtime_put(void *data) +{ + struct device *dev = data; + + pm_runtime_put_sync(dev); +} + static int wkup_m3_rproc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -148,19 +155,20 @@ static int wkup_m3_rproc_probe(struct platform_device *pdev) return -ENODEV; } - pm_runtime_enable(&pdev->dev); + ret = devm_pm_runtime_enable(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); ret = pm_runtime_get_sync(&pdev->dev); - if (ret < 0) { - dev_err(&pdev->dev, "pm_runtime_get_sync() failed\n"); - goto err; - } + if (ret < 0) + return dev_err_probe(dev, ret, "pm_runtime_get_sync() failed\n"); + ret = devm_add_action_or_reset(dev, wkup_m3_rproc_pm_runtime_put, dev); + if (ret) + return dev_err_probe(dev, ret, "failed to add disable pm devm action\n"); - rproc = rproc_alloc(dev, "wkup_m3", &wkup_m3_rproc_ops, - fw_name, sizeof(*wkupm3)); - if (!rproc) { - ret = -ENOMEM; - goto err; - } + rproc = devm_rproc_alloc(dev, "wkup_m3", &wkup_m3_rproc_ops, + fw_name, sizeof(*wkupm3)); + if (!rproc) + return -ENOMEM; rproc->auto_boot = false; rproc->sysfs_read_only = true; @@ -175,9 +183,7 @@ static int wkup_m3_rproc_probe(struct platform_device *pdev) if (!wkupm3->rsts) { if (!(pdata && pdata->deassert_reset && pdata->assert_reset && pdata->reset_name)) { - dev_err(dev, "Platform data missing!\n"); - ret = -ENODEV; - goto err_put_rproc; + return dev_err_probe(dev, -ENODEV, "Platform data missing!\n"); } } @@ -185,12 +191,9 @@ static int wkup_m3_rproc_probe(struct platform_device *pdev) res = platform_get_resource_byname(pdev, IORESOURCE_MEM, mem_names[i]); wkupm3->mem[i].cpu_addr = devm_ioremap_resource(dev, res); - if (IS_ERR(wkupm3->mem[i].cpu_addr)) { - dev_err(&pdev->dev, "devm_ioremap_resource failed for resource %d\n", - i); - ret = PTR_ERR(wkupm3->mem[i].cpu_addr); - goto err_put_rproc; - } + if (IS_ERR(wkupm3->mem[i].cpu_addr)) + return dev_err_probe(dev, PTR_ERR(wkupm3->mem[i].cpu_addr), + "devm_ioremap_resource failed for resource %d\n", i); wkupm3->mem[i].bus_addr = res->start; wkupm3->mem[i].size = resource_size(res); addrp = of_get_address(dev->of_node, i, &size, NULL); @@ -207,30 +210,11 @@ static int wkup_m3_rproc_probe(struct platform_device *pdev) dev_set_drvdata(dev, rproc); - ret = rproc_add(rproc); - if (ret) { - dev_err(dev, "rproc_add failed\n"); - goto err_put_rproc; - } + ret = devm_rproc_add(dev, rproc); + if (ret) + return dev_err_probe(dev, ret, "rproc_add failed\n"); return 0; - -err_put_rproc: - rproc_free(rproc); -err: - pm_runtime_put_noidle(dev); - pm_runtime_disable(dev); - return ret; -} - -static void wkup_m3_rproc_remove(struct platform_device *pdev) -{ - struct rproc *rproc = platform_get_drvdata(pdev); - - rproc_del(rproc); - rproc_free(rproc); - pm_runtime_put_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); } #ifdef CONFIG_PM @@ -251,7 +235,6 @@ static const struct dev_pm_ops wkup_m3_rproc_pm_ops = { static struct platform_driver wkup_m3_rproc_driver = { .probe = wkup_m3_rproc_probe, - .remove = wkup_m3_rproc_remove, .driver = { .name = "wkup_m3_rproc", .of_match_table = wkup_m3_rproc_of_match, diff --git a/drivers/rpmsg/qcom_glink_native.c b/drivers/rpmsg/qcom_glink_native.c index a2f9d85c7156..820a6ca5b1d7 100644 --- a/drivers/rpmsg/qcom_glink_native.c +++ b/drivers/rpmsg/qcom_glink_native.c @@ -1663,7 +1663,7 @@ static int qcom_glink_rx_open(struct qcom_glink *glink, unsigned int rcid, } rpdev->ept = &channel->ept; - strscpy_pad(rpdev->id.name, name, RPMSG_NAME_SIZE); + strscpy(rpdev->id.name, name); rpdev->src = RPMSG_ADDR_ANY; rpdev->dst = RPMSG_ADDR_ANY; rpdev->ops = &glink_device_ops; diff --git a/drivers/rpmsg/qcom_smd.c b/drivers/rpmsg/qcom_smd.c index 87c944d4b4f3..42594f5ee438 100644 --- a/drivers/rpmsg/qcom_smd.c +++ b/drivers/rpmsg/qcom_smd.c @@ -1089,7 +1089,7 @@ static int qcom_smd_create_device(struct qcom_smd_channel *channel) /* Assign public information to the rpmsg_device */ rpdev = &qsdev->rpdev; - strscpy_pad(rpdev->id.name, channel->name, RPMSG_NAME_SIZE); + strscpy(rpdev->id.name, channel->name); rpdev->src = RPMSG_ADDR_ANY; rpdev->dst = RPMSG_ADDR_ANY; @@ -1368,7 +1368,7 @@ static int qcom_smd_parse_edge(struct device *dev, edge->mbox_client.knows_txdone = true; edge->mbox_chan = mbox_request_channel(&edge->mbox_client, 0); if (IS_ERR(edge->mbox_chan)) { - if (PTR_ERR(edge->mbox_chan) != -ENODEV) { + if (PTR_ERR(edge->mbox_chan) != -ENOENT) { ret = dev_err_probe(dev, PTR_ERR(edge->mbox_chan), "failed to acquire IPC mailbox\n"); goto put_node; diff --git a/drivers/rpmsg/rpmsg_char.c b/drivers/rpmsg/rpmsg_char.c index eec7642d2686..96fcdd2d7093 100644 --- a/drivers/rpmsg/rpmsg_char.c +++ b/drivers/rpmsg/rpmsg_char.c @@ -522,8 +522,10 @@ static void rpmsg_chrdev_remove(struct rpmsg_device *rpdev) static struct rpmsg_device_id rpmsg_chrdev_id_table[] = { { .name = "rpmsg-raw" }, + { .name = "rpmsg_chrdev" }, { }, }; +MODULE_DEVICE_TABLE(rpmsg, rpmsg_chrdev_id_table); static struct rpmsg_driver rpmsg_chrdev_driver = { .probe = rpmsg_chrdev_probe, @@ -565,6 +567,5 @@ static void rpmsg_chrdev_exit(void) } module_exit(rpmsg_chrdev_exit); -MODULE_ALIAS("rpmsg:rpmsg_chrdev"); MODULE_DESCRIPTION("RPMSG device interface"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/rpmsg/rpmsg_core.c b/drivers/rpmsg/rpmsg_core.c index bece5e635ee9..5d661681a9b6 100644 --- a/drivers/rpmsg/rpmsg_core.c +++ b/drivers/rpmsg/rpmsg_core.c @@ -479,7 +479,8 @@ static int rpmsg_dev_probe(struct device *dev) struct rpmsg_endpoint *ept = NULL; int err; - err = dev_pm_domain_attach(dev, PD_FLAG_ATTACH_POWER_ON); + err = dev_pm_domain_attach(dev, PD_FLAG_ATTACH_POWER_ON | + PD_FLAG_DETACH_POWER_OFF); if (err) goto out; @@ -538,8 +539,6 @@ static void rpmsg_dev_remove(struct device *dev) if (rpdrv->remove) rpdrv->remove(rpdev); - dev_pm_domain_detach(dev, true); - if (rpdev->ept) rpmsg_destroy_ept(rpdev->ept); } diff --git a/drivers/s390/char/con3270.c b/drivers/s390/char/con3270.c index b78b86e8f281..a367f95c7c53 100644 --- a/drivers/s390/char/con3270.c +++ b/drivers/s390/char/con3270.c @@ -970,8 +970,6 @@ static void tty3270_resize(struct raw3270_view *view, char **old_rcl_lines, **new_rcl_lines; char *old_prompt, *new_prompt; char *old_input, *new_input; - struct tty_struct *tty; - struct winsize ws; size_t prompt_sz; int new_allocated, old_allocated = tp->allocated_lines; @@ -1023,14 +1021,14 @@ static void tty3270_resize(struct raw3270_view *view, kfree(old_prompt); tty3270_free_recall(old_rcl_lines); tty3270_set_timer(tp, 1); - /* Informat tty layer about new size */ - tty = tty_port_tty_get(&tp->port); - if (!tty) - return; - ws.ws_row = tty3270_tty_rows(tp); - ws.ws_col = tp->view.cols; - tty_do_resize(tty, &ws); - tty_kref_put(tty); + /* Inform the tty layer about new size */ + scoped_guard(tty_port_tty, &tp->port) { + struct winsize ws = { + .ws_row = tty3270_tty_rows(tp), + .ws_col = tp->view.cols, + }; + tty_do_resize(scoped_tty(), &ws); + } return; out_screen: tty3270_free_screen(screen, new_rows); diff --git a/drivers/siox/siox-bus-gpio.c b/drivers/siox/siox-bus-gpio.c index d6f936464063..413d5f92311c 100644 --- a/drivers/siox/siox-bus-gpio.c +++ b/drivers/siox/siox-bus-gpio.c @@ -93,8 +93,7 @@ static int siox_gpio_probe(struct platform_device *pdev) smaster = devm_siox_master_alloc(dev, sizeof(*ddata)); if (!smaster) - return dev_err_probe(dev, -ENOMEM, - "failed to allocate siox master\n"); + return -ENOMEM; platform_set_drvdata(pdev, smaster); ddata = siox_master_get_devdata(smaster); diff --git a/drivers/slimbus/Kconfig b/drivers/slimbus/Kconfig index a0fdf9d792cb..60b0dcbc0ebb 100644 --- a/drivers/slimbus/Kconfig +++ b/drivers/slimbus/Kconfig @@ -13,13 +13,6 @@ menuconfig SLIMBUS if SLIMBUS # SLIMbus controllers -config SLIM_QCOM_CTRL - tristate "Qualcomm SLIMbus Manager Component" - depends on HAS_IOMEM - help - Select driver if Qualcomm's SLIMbus Manager Component is - programmed using Linux kernel. - config SLIM_QCOM_NGD_CTRL tristate "Qualcomm SLIMbus Satellite Non-Generic Device Component" depends on HAS_IOMEM && DMA_ENGINE && NET diff --git a/drivers/slimbus/Makefile b/drivers/slimbus/Makefile index d9aa011b6804..3cfb41c3b592 100644 --- a/drivers/slimbus/Makefile +++ b/drivers/slimbus/Makefile @@ -6,8 +6,5 @@ obj-$(CONFIG_SLIMBUS) += slimbus.o slimbus-y := core.o messaging.o sched.o stream.o #Controllers -obj-$(CONFIG_SLIM_QCOM_CTRL) += slim-qcom-ctrl.o -slim-qcom-ctrl-y := qcom-ctrl.o - obj-$(CONFIG_SLIM_QCOM_NGD_CTRL) += slim-qcom-ngd-ctrl.o slim-qcom-ngd-ctrl-y := qcom-ngd-ctrl.o diff --git a/drivers/slimbus/messaging.c b/drivers/slimbus/messaging.c index 6f01d944f9c6..e2dbe4a66b70 100644 --- a/drivers/slimbus/messaging.c +++ b/drivers/slimbus/messaging.c @@ -143,8 +143,6 @@ int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn) if (!txn->msg->comp) txn->comp = &done; - else - txn->comp = txn->comp; } ret = ctrl->xfer_msg(ctrl, txn); @@ -224,7 +222,7 @@ static u16 slim_slicesize(int code) /** * slim_xfer_msg() - Transfer a value info message on slim device * - * @sbdev: slim device to which this msg has to be transfered + * @sbdev: slim device to which this msg has to be transferred * @msg: value info message pointer * @mc: message code of the message * diff --git a/drivers/slimbus/qcom-ctrl.c b/drivers/slimbus/qcom-ctrl.c deleted file mode 100644 index ab344f7472f2..000000000000 --- a/drivers/slimbus/qcom-ctrl.c +++ /dev/null @@ -1,735 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (c) 2011-2017, The Linux Foundation - */ - -#include <linux/irq.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/slab.h> -#include <linux/io.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <linux/delay.h> -#include <linux/clk.h> -#include <linux/of.h> -#include <linux/pm_runtime.h> -#include "slimbus.h" - -/* Manager registers */ -#define MGR_CFG 0x200 -#define MGR_STATUS 0x204 -#define MGR_INT_EN 0x210 -#define MGR_INT_STAT 0x214 -#define MGR_INT_CLR 0x218 -#define MGR_TX_MSG 0x230 -#define MGR_RX_MSG 0x270 -#define MGR_IE_STAT 0x2F0 -#define MGR_VE_STAT 0x300 -#define MGR_CFG_ENABLE 1 - -/* Framer registers */ -#define FRM_CFG 0x400 -#define FRM_STAT 0x404 -#define FRM_INT_EN 0x410 -#define FRM_INT_STAT 0x414 -#define FRM_INT_CLR 0x418 -#define FRM_WAKEUP 0x41C -#define FRM_CLKCTL_DONE 0x420 -#define FRM_IE_STAT 0x430 -#define FRM_VE_STAT 0x440 - -/* Interface registers */ -#define INTF_CFG 0x600 -#define INTF_STAT 0x604 -#define INTF_INT_EN 0x610 -#define INTF_INT_STAT 0x614 -#define INTF_INT_CLR 0x618 -#define INTF_IE_STAT 0x630 -#define INTF_VE_STAT 0x640 - -/* Interrupt status bits */ -#define MGR_INT_TX_NACKED_2 BIT(25) -#define MGR_INT_MSG_BUF_CONTE BIT(26) -#define MGR_INT_RX_MSG_RCVD BIT(30) -#define MGR_INT_TX_MSG_SENT BIT(31) - -/* Framer config register settings */ -#define FRM_ACTIVE 1 -#define CLK_GEAR 7 -#define ROOT_FREQ 11 -#define REF_CLK_GEAR 15 -#define INTR_WAKE 19 - -#define SLIM_MSG_ASM_FIRST_WORD(l, mt, mc, dt, ad) \ - ((l) | ((mt) << 5) | ((mc) << 8) | ((dt) << 15) | ((ad) << 16)) - -#define SLIM_ROOT_FREQ 24576000 -#define QCOM_SLIM_AUTOSUSPEND 1000 - -/* MAX message size over control channel */ -#define SLIM_MSGQ_BUF_LEN 40 -#define QCOM_TX_MSGS 2 -#define QCOM_RX_MSGS 8 -#define QCOM_BUF_ALLOC_RETRIES 10 - -#define CFG_PORT(r, v) ((v) ? CFG_PORT_V2(r) : CFG_PORT_V1(r)) - -/* V2 Component registers */ -#define CFG_PORT_V2(r) ((r ## _V2)) -#define COMP_CFG_V2 4 -#define COMP_TRUST_CFG_V2 0x3000 - -/* V1 Component registers */ -#define CFG_PORT_V1(r) ((r ## _V1)) -#define COMP_CFG_V1 0 -#define COMP_TRUST_CFG_V1 0x14 - -/* Resource group info for manager, and non-ported generic device-components */ -#define EE_MGR_RSC_GRP (1 << 10) -#define EE_NGD_2 (2 << 6) -#define EE_NGD_1 0 - -struct slim_ctrl_buf { - void *base; - spinlock_t lock; - int head; - int tail; - int sl_sz; - int n; -}; - -struct qcom_slim_ctrl { - struct slim_controller ctrl; - struct slim_framer framer; - struct device *dev; - void __iomem *base; - void __iomem *slew_reg; - - struct slim_ctrl_buf rx; - struct slim_ctrl_buf tx; - - struct completion **wr_comp; - int irq; - struct workqueue_struct *rxwq; - struct work_struct wd; - struct clk *rclk; - struct clk *hclk; -}; - -static void qcom_slim_queue_tx(struct qcom_slim_ctrl *ctrl, void *buf, - u8 len, u32 tx_reg) -{ - int count = (len + 3) >> 2; - - __iowrite32_copy(ctrl->base + tx_reg, buf, count); - - /* Ensure Oder of subsequent writes */ - mb(); -} - -static void *slim_alloc_rxbuf(struct qcom_slim_ctrl *ctrl) -{ - unsigned long flags; - int idx; - - spin_lock_irqsave(&ctrl->rx.lock, flags); - if ((ctrl->rx.tail + 1) % ctrl->rx.n == ctrl->rx.head) { - spin_unlock_irqrestore(&ctrl->rx.lock, flags); - dev_err(ctrl->dev, "RX QUEUE full!"); - return NULL; - } - idx = ctrl->rx.tail; - ctrl->rx.tail = (ctrl->rx.tail + 1) % ctrl->rx.n; - spin_unlock_irqrestore(&ctrl->rx.lock, flags); - - return ctrl->rx.base + (idx * ctrl->rx.sl_sz); -} - -static void slim_ack_txn(struct qcom_slim_ctrl *ctrl, int err) -{ - struct completion *comp; - unsigned long flags; - int idx; - - spin_lock_irqsave(&ctrl->tx.lock, flags); - idx = ctrl->tx.head; - ctrl->tx.head = (ctrl->tx.head + 1) % ctrl->tx.n; - spin_unlock_irqrestore(&ctrl->tx.lock, flags); - - comp = ctrl->wr_comp[idx]; - ctrl->wr_comp[idx] = NULL; - - complete(comp); -} - -static irqreturn_t qcom_slim_handle_tx_irq(struct qcom_slim_ctrl *ctrl, - u32 stat) -{ - int err = 0; - - if (stat & MGR_INT_TX_MSG_SENT) - writel_relaxed(MGR_INT_TX_MSG_SENT, - ctrl->base + MGR_INT_CLR); - - if (stat & MGR_INT_TX_NACKED_2) { - u32 mgr_stat = readl_relaxed(ctrl->base + MGR_STATUS); - u32 mgr_ie_stat = readl_relaxed(ctrl->base + MGR_IE_STAT); - u32 frm_stat = readl_relaxed(ctrl->base + FRM_STAT); - u32 frm_cfg = readl_relaxed(ctrl->base + FRM_CFG); - u32 frm_intr_stat = readl_relaxed(ctrl->base + FRM_INT_STAT); - u32 frm_ie_stat = readl_relaxed(ctrl->base + FRM_IE_STAT); - u32 intf_stat = readl_relaxed(ctrl->base + INTF_STAT); - u32 intf_intr_stat = readl_relaxed(ctrl->base + INTF_INT_STAT); - u32 intf_ie_stat = readl_relaxed(ctrl->base + INTF_IE_STAT); - - writel_relaxed(MGR_INT_TX_NACKED_2, ctrl->base + MGR_INT_CLR); - - dev_err(ctrl->dev, "TX Nack MGR:int:0x%x, stat:0x%x\n", - stat, mgr_stat); - dev_err(ctrl->dev, "TX Nack MGR:ie:0x%x\n", mgr_ie_stat); - dev_err(ctrl->dev, "TX Nack FRM:int:0x%x, stat:0x%x\n", - frm_intr_stat, frm_stat); - dev_err(ctrl->dev, "TX Nack FRM:cfg:0x%x, ie:0x%x\n", - frm_cfg, frm_ie_stat); - dev_err(ctrl->dev, "TX Nack INTF:intr:0x%x, stat:0x%x\n", - intf_intr_stat, intf_stat); - dev_err(ctrl->dev, "TX Nack INTF:ie:0x%x\n", - intf_ie_stat); - err = -ENOTCONN; - } - - slim_ack_txn(ctrl, err); - - return IRQ_HANDLED; -} - -static irqreturn_t qcom_slim_handle_rx_irq(struct qcom_slim_ctrl *ctrl, - u32 stat) -{ - u32 *rx_buf, pkt[10]; - bool q_rx = false; - u8 mc, mt, len; - - pkt[0] = readl_relaxed(ctrl->base + MGR_RX_MSG); - mt = SLIM_HEADER_GET_MT(pkt[0]); - len = SLIM_HEADER_GET_RL(pkt[0]); - mc = SLIM_HEADER_GET_MC(pkt[0]>>8); - - /* - * this message cannot be handled by ISR, so - * let work-queue handle it - */ - if (mt == SLIM_MSG_MT_CORE && mc == SLIM_MSG_MC_REPORT_PRESENT) { - rx_buf = (u32 *)slim_alloc_rxbuf(ctrl); - if (!rx_buf) { - dev_err(ctrl->dev, "dropping RX:0x%x due to RX full\n", - pkt[0]); - goto rx_ret_irq; - } - rx_buf[0] = pkt[0]; - - } else { - rx_buf = pkt; - } - - __ioread32_copy(rx_buf + 1, ctrl->base + MGR_RX_MSG + 4, - DIV_ROUND_UP(len, 4)); - - switch (mc) { - - case SLIM_MSG_MC_REPORT_PRESENT: - q_rx = true; - break; - case SLIM_MSG_MC_REPLY_INFORMATION: - case SLIM_MSG_MC_REPLY_VALUE: - slim_msg_response(&ctrl->ctrl, (u8 *)(rx_buf + 1), - (u8)(*rx_buf >> 24), (len - 4)); - break; - default: - dev_err(ctrl->dev, "unsupported MC,%x MT:%x\n", - mc, mt); - break; - } -rx_ret_irq: - writel(MGR_INT_RX_MSG_RCVD, ctrl->base + - MGR_INT_CLR); - if (q_rx) - queue_work(ctrl->rxwq, &ctrl->wd); - - return IRQ_HANDLED; -} - -static irqreturn_t qcom_slim_interrupt(int irq, void *d) -{ - struct qcom_slim_ctrl *ctrl = d; - u32 stat = readl_relaxed(ctrl->base + MGR_INT_STAT); - int ret = IRQ_NONE; - - if (stat & MGR_INT_TX_MSG_SENT || stat & MGR_INT_TX_NACKED_2) - ret = qcom_slim_handle_tx_irq(ctrl, stat); - - if (stat & MGR_INT_RX_MSG_RCVD) - ret = qcom_slim_handle_rx_irq(ctrl, stat); - - return ret; -} - -static int qcom_clk_pause_wakeup(struct slim_controller *sctrl) -{ - struct qcom_slim_ctrl *ctrl = dev_get_drvdata(sctrl->dev); - - clk_prepare_enable(ctrl->hclk); - clk_prepare_enable(ctrl->rclk); - enable_irq(ctrl->irq); - - writel_relaxed(1, ctrl->base + FRM_WAKEUP); - /* Make sure framer wakeup write goes through before ISR fires */ - mb(); - /* - * HW Workaround: Currently, slave is reporting lost-sync messages - * after SLIMbus comes out of clock pause. - * Transaction with slave fail before slave reports that message - * Give some time for that report to come - * SLIMbus wakes up in clock gear 10 at 24.576MHz. With each superframe - * being 250 usecs, we wait for 5-10 superframes here to ensure - * we get the message - */ - usleep_range(1250, 2500); - return 0; -} - -static void *slim_alloc_txbuf(struct qcom_slim_ctrl *ctrl, - struct slim_msg_txn *txn, - struct completion *done) -{ - unsigned long flags; - int idx; - - spin_lock_irqsave(&ctrl->tx.lock, flags); - if (((ctrl->tx.head + 1) % ctrl->tx.n) == ctrl->tx.tail) { - spin_unlock_irqrestore(&ctrl->tx.lock, flags); - dev_err(ctrl->dev, "controller TX buf unavailable"); - return NULL; - } - idx = ctrl->tx.tail; - ctrl->wr_comp[idx] = done; - ctrl->tx.tail = (ctrl->tx.tail + 1) % ctrl->tx.n; - - spin_unlock_irqrestore(&ctrl->tx.lock, flags); - - return ctrl->tx.base + (idx * ctrl->tx.sl_sz); -} - - -static int qcom_xfer_msg(struct slim_controller *sctrl, - struct slim_msg_txn *txn) -{ - struct qcom_slim_ctrl *ctrl = dev_get_drvdata(sctrl->dev); - DECLARE_COMPLETION_ONSTACK(done); - void *pbuf = slim_alloc_txbuf(ctrl, txn, &done); - unsigned long ms = txn->rl + HZ; - u8 *puc; - int ret = 0, retries = QCOM_BUF_ALLOC_RETRIES; - unsigned long time_left; - u8 la = txn->la; - u32 *head; - /* HW expects length field to be excluded */ - txn->rl--; - - /* spin till buffer is made available */ - if (!pbuf) { - while (retries--) { - usleep_range(10000, 15000); - pbuf = slim_alloc_txbuf(ctrl, txn, &done); - if (pbuf) - break; - } - } - - if (retries < 0 && !pbuf) - return -ENOMEM; - - puc = (u8 *)pbuf; - head = (u32 *)pbuf; - - if (txn->dt == SLIM_MSG_DEST_LOGICALADDR) { - *head = SLIM_MSG_ASM_FIRST_WORD(txn->rl, txn->mt, - txn->mc, 0, la); - puc += 3; - } else { - *head = SLIM_MSG_ASM_FIRST_WORD(txn->rl, txn->mt, - txn->mc, 1, la); - puc += 2; - } - - if (slim_tid_txn(txn->mt, txn->mc)) - *(puc++) = txn->tid; - - if (slim_ec_txn(txn->mt, txn->mc)) { - *(puc++) = (txn->ec & 0xFF); - *(puc++) = (txn->ec >> 8) & 0xFF; - } - - if (txn->msg && txn->msg->wbuf) - memcpy(puc, txn->msg->wbuf, txn->msg->num_bytes); - - qcom_slim_queue_tx(ctrl, head, txn->rl, MGR_TX_MSG); - time_left = wait_for_completion_timeout(&done, msecs_to_jiffies(ms)); - - if (!time_left) { - dev_err(ctrl->dev, "TX timed out:MC:0x%x,mt:0x%x", txn->mc, - txn->mt); - ret = -ETIMEDOUT; - } - - return ret; - -} - -static int qcom_set_laddr(struct slim_controller *sctrl, - struct slim_eaddr *ead, u8 laddr) -{ - struct qcom_slim_ctrl *ctrl = dev_get_drvdata(sctrl->dev); - struct { - __be16 manf_id; - __be16 prod_code; - u8 dev_index; - u8 instance; - u8 laddr; - } __packed p; - struct slim_val_inf msg = {0}; - DEFINE_SLIM_EDEST_TXN(txn, SLIM_MSG_MC_ASSIGN_LOGICAL_ADDRESS, - 10, laddr, &msg); - int ret; - - p.manf_id = cpu_to_be16(ead->manf_id); - p.prod_code = cpu_to_be16(ead->prod_code); - p.dev_index = ead->dev_index; - p.instance = ead->instance; - p.laddr = laddr; - - msg.wbuf = (void *)&p; - msg.num_bytes = 7; - ret = slim_do_transfer(&ctrl->ctrl, &txn); - - if (ret) - dev_err(ctrl->dev, "set LA:0x%x failed:ret:%d\n", - laddr, ret); - return ret; -} - -static int slim_get_current_rxbuf(struct qcom_slim_ctrl *ctrl, void *buf) -{ - unsigned long flags; - - spin_lock_irqsave(&ctrl->rx.lock, flags); - if (ctrl->rx.tail == ctrl->rx.head) { - spin_unlock_irqrestore(&ctrl->rx.lock, flags); - return -ENODATA; - } - memcpy(buf, ctrl->rx.base + (ctrl->rx.head * ctrl->rx.sl_sz), - ctrl->rx.sl_sz); - - ctrl->rx.head = (ctrl->rx.head + 1) % ctrl->rx.n; - spin_unlock_irqrestore(&ctrl->rx.lock, flags); - - return 0; -} - -static void qcom_slim_rxwq(struct work_struct *work) -{ - u8 buf[SLIM_MSGQ_BUF_LEN]; - u8 mc, mt; - int ret; - struct qcom_slim_ctrl *ctrl = container_of(work, struct qcom_slim_ctrl, - wd); - - while ((slim_get_current_rxbuf(ctrl, buf)) != -ENODATA) { - mt = SLIM_HEADER_GET_MT(buf[0]); - mc = SLIM_HEADER_GET_MC(buf[1]); - if (mt == SLIM_MSG_MT_CORE && - mc == SLIM_MSG_MC_REPORT_PRESENT) { - struct slim_eaddr ea; - u8 laddr; - - ea.manf_id = be16_to_cpup((__be16 *)&buf[2]); - ea.prod_code = be16_to_cpup((__be16 *)&buf[4]); - ea.dev_index = buf[6]; - ea.instance = buf[7]; - - ret = slim_device_report_present(&ctrl->ctrl, &ea, - &laddr); - if (ret < 0) - dev_err(ctrl->dev, "assign laddr failed:%d\n", - ret); - } else { - dev_err(ctrl->dev, "unexpected message:mc:%x, mt:%x\n", - mc, mt); - } - } -} - -static void qcom_slim_prg_slew(struct platform_device *pdev, - struct qcom_slim_ctrl *ctrl) -{ - if (!ctrl->slew_reg) { - /* SLEW RATE register for this SLIMbus */ - ctrl->slew_reg = devm_platform_ioremap_resource_byname(pdev, "slew"); - if (IS_ERR(ctrl->slew_reg)) - return; - } - - writel_relaxed(1, ctrl->slew_reg); - /* Make sure SLIMbus-slew rate enabling goes through */ - wmb(); -} - -static int qcom_slim_probe(struct platform_device *pdev) -{ - struct qcom_slim_ctrl *ctrl; - struct slim_controller *sctrl; - int ret, ver; - - ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL); - if (!ctrl) - return -ENOMEM; - - ctrl->hclk = devm_clk_get(&pdev->dev, "iface"); - if (IS_ERR(ctrl->hclk)) - return PTR_ERR(ctrl->hclk); - - ctrl->rclk = devm_clk_get(&pdev->dev, "core"); - if (IS_ERR(ctrl->rclk)) - return PTR_ERR(ctrl->rclk); - - ret = clk_set_rate(ctrl->rclk, SLIM_ROOT_FREQ); - if (ret) { - dev_err(&pdev->dev, "ref-clock set-rate failed:%d\n", ret); - return ret; - } - - ctrl->irq = platform_get_irq(pdev, 0); - if (ctrl->irq < 0) - return ctrl->irq; - - sctrl = &ctrl->ctrl; - sctrl->dev = &pdev->dev; - ctrl->dev = &pdev->dev; - platform_set_drvdata(pdev, ctrl); - dev_set_drvdata(ctrl->dev, ctrl); - - ctrl->base = devm_platform_ioremap_resource_byname(pdev, "ctrl"); - if (IS_ERR(ctrl->base)) - return PTR_ERR(ctrl->base); - - sctrl->set_laddr = qcom_set_laddr; - sctrl->xfer_msg = qcom_xfer_msg; - sctrl->wakeup = qcom_clk_pause_wakeup; - ctrl->tx.n = QCOM_TX_MSGS; - ctrl->tx.sl_sz = SLIM_MSGQ_BUF_LEN; - ctrl->rx.n = QCOM_RX_MSGS; - ctrl->rx.sl_sz = SLIM_MSGQ_BUF_LEN; - ctrl->wr_comp = kcalloc(QCOM_TX_MSGS, sizeof(struct completion *), - GFP_KERNEL); - if (!ctrl->wr_comp) - return -ENOMEM; - - spin_lock_init(&ctrl->rx.lock); - spin_lock_init(&ctrl->tx.lock); - INIT_WORK(&ctrl->wd, qcom_slim_rxwq); - ctrl->rxwq = create_singlethread_workqueue("qcom_slim_rx"); - if (!ctrl->rxwq) { - dev_err(ctrl->dev, "Failed to start Rx WQ\n"); - return -ENOMEM; - } - - ctrl->framer.rootfreq = SLIM_ROOT_FREQ / 8; - ctrl->framer.superfreq = - ctrl->framer.rootfreq / SLIM_CL_PER_SUPERFRAME_DIV8; - sctrl->a_framer = &ctrl->framer; - sctrl->clkgear = SLIM_MAX_CLK_GEAR; - - qcom_slim_prg_slew(pdev, ctrl); - - ret = devm_request_irq(&pdev->dev, ctrl->irq, qcom_slim_interrupt, - IRQF_TRIGGER_HIGH, "qcom_slim_irq", ctrl); - if (ret) { - dev_err(&pdev->dev, "request IRQ failed\n"); - goto err_request_irq_failed; - } - - ret = clk_prepare_enable(ctrl->hclk); - if (ret) - goto err_hclk_enable_failed; - - ret = clk_prepare_enable(ctrl->rclk); - if (ret) - goto err_rclk_enable_failed; - - ctrl->tx.base = devm_kcalloc(&pdev->dev, ctrl->tx.n, ctrl->tx.sl_sz, - GFP_KERNEL); - if (!ctrl->tx.base) { - ret = -ENOMEM; - goto err; - } - - ctrl->rx.base = devm_kcalloc(&pdev->dev,ctrl->rx.n, ctrl->rx.sl_sz, - GFP_KERNEL); - if (!ctrl->rx.base) { - ret = -ENOMEM; - goto err; - } - - /* Register with framework before enabling frame, clock */ - ret = slim_register_controller(&ctrl->ctrl); - if (ret) { - dev_err(ctrl->dev, "error adding controller\n"); - goto err; - } - - ver = readl_relaxed(ctrl->base); - /* Version info in 16 MSbits */ - ver >>= 16; - /* Component register initialization */ - writel(1, ctrl->base + CFG_PORT(COMP_CFG, ver)); - writel((EE_MGR_RSC_GRP | EE_NGD_2 | EE_NGD_1), - ctrl->base + CFG_PORT(COMP_TRUST_CFG, ver)); - - writel((MGR_INT_TX_NACKED_2 | - MGR_INT_MSG_BUF_CONTE | MGR_INT_RX_MSG_RCVD | - MGR_INT_TX_MSG_SENT), ctrl->base + MGR_INT_EN); - writel(1, ctrl->base + MGR_CFG); - /* Framer register initialization */ - writel((1 << INTR_WAKE) | (0xA << REF_CLK_GEAR) | - (0xA << CLK_GEAR) | (1 << ROOT_FREQ) | (1 << FRM_ACTIVE) | 1, - ctrl->base + FRM_CFG); - writel(MGR_CFG_ENABLE, ctrl->base + MGR_CFG); - writel(1, ctrl->base + INTF_CFG); - writel(1, ctrl->base + CFG_PORT(COMP_CFG, ver)); - - pm_runtime_use_autosuspend(&pdev->dev); - pm_runtime_set_autosuspend_delay(&pdev->dev, QCOM_SLIM_AUTOSUSPEND); - pm_runtime_set_active(&pdev->dev); - pm_runtime_mark_last_busy(&pdev->dev); - pm_runtime_enable(&pdev->dev); - - dev_dbg(ctrl->dev, "QCOM SB controller is up:ver:0x%x!\n", ver); - return 0; - -err: - clk_disable_unprepare(ctrl->rclk); -err_rclk_enable_failed: - clk_disable_unprepare(ctrl->hclk); -err_hclk_enable_failed: -err_request_irq_failed: - destroy_workqueue(ctrl->rxwq); - return ret; -} - -static void qcom_slim_remove(struct platform_device *pdev) -{ - struct qcom_slim_ctrl *ctrl = platform_get_drvdata(pdev); - - pm_runtime_disable(&pdev->dev); - slim_unregister_controller(&ctrl->ctrl); - clk_disable_unprepare(ctrl->rclk); - clk_disable_unprepare(ctrl->hclk); - destroy_workqueue(ctrl->rxwq); -} - -/* - * If PM_RUNTIME is not defined, these 2 functions become helper - * functions to be called from system suspend/resume. - */ -#ifdef CONFIG_PM -static int qcom_slim_runtime_suspend(struct device *device) -{ - struct qcom_slim_ctrl *ctrl = dev_get_drvdata(device); - int ret; - - dev_dbg(device, "pm_runtime: suspending...\n"); - ret = slim_ctrl_clk_pause(&ctrl->ctrl, false, SLIM_CLK_UNSPECIFIED); - if (ret) { - dev_err(device, "clk pause not entered:%d", ret); - } else { - disable_irq(ctrl->irq); - clk_disable_unprepare(ctrl->hclk); - clk_disable_unprepare(ctrl->rclk); - } - return ret; -} - -static int qcom_slim_runtime_resume(struct device *device) -{ - struct qcom_slim_ctrl *ctrl = dev_get_drvdata(device); - int ret = 0; - - dev_dbg(device, "pm_runtime: resuming...\n"); - ret = slim_ctrl_clk_pause(&ctrl->ctrl, true, 0); - if (ret) - dev_err(device, "clk pause not exited:%d", ret); - return ret; -} -#endif - -#ifdef CONFIG_PM_SLEEP -static int qcom_slim_suspend(struct device *dev) -{ - int ret = 0; - - if (!pm_runtime_enabled(dev) || - (!pm_runtime_suspended(dev))) { - dev_dbg(dev, "system suspend"); - ret = qcom_slim_runtime_suspend(dev); - } - - return ret; -} - -static int qcom_slim_resume(struct device *dev) -{ - if (!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev)) { - int ret; - - dev_dbg(dev, "system resume"); - ret = qcom_slim_runtime_resume(dev); - if (!ret) { - pm_runtime_mark_last_busy(dev); - pm_request_autosuspend(dev); - } - return ret; - - } - return 0; -} -#endif /* CONFIG_PM_SLEEP */ - -static const struct dev_pm_ops qcom_slim_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(qcom_slim_suspend, qcom_slim_resume) - SET_RUNTIME_PM_OPS( - qcom_slim_runtime_suspend, - qcom_slim_runtime_resume, - NULL - ) -}; - -static const struct of_device_id qcom_slim_dt_match[] = { - { .compatible = "qcom,slim", }, - {} -}; -MODULE_DEVICE_TABLE(of, qcom_slim_dt_match); - -static struct platform_driver qcom_slim_driver = { - .probe = qcom_slim_probe, - .remove = qcom_slim_remove, - .driver = { - .name = "qcom_slim_ctrl", - .of_match_table = qcom_slim_dt_match, - .pm = &qcom_slim_dev_pm_ops, - }, -}; -module_platform_driver(qcom_slim_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Qualcomm SLIMbus Controller"); diff --git a/drivers/staging/axis-fifo/axis-fifo.c b/drivers/staging/axis-fifo/axis-fifo.c index 57ed58065eba..e8aa632e0a31 100644 --- a/drivers/staging/axis-fifo/axis-fifo.c +++ b/drivers/staging/axis-fifo/axis-fifo.c @@ -107,6 +107,8 @@ static long read_timeout = 1000; /* ms to wait before read() times out */ static long write_timeout = 1000; /* ms to wait before write() times out */ +static DEFINE_IDA(axis_fifo_ida); + /* ---------------------------- * module command-line arguments * ---------------------------- @@ -123,6 +125,7 @@ MODULE_PARM_DESC(write_timeout, "ms to wait before blocking write() timing out; */ struct axis_fifo { + int id; int irq; /* interrupt */ void __iomem *base_addr; /* kernel space memory */ @@ -693,17 +696,11 @@ static int axis_fifo_probe(struct platform_device *pdev) /* get iospace for the device and request physical memory */ fifo->base_addr = devm_platform_get_and_ioremap_resource(pdev, 0, &r_mem); - if (IS_ERR(fifo->base_addr)) { - rc = PTR_ERR(fifo->base_addr); - goto err_initial; - } + if (IS_ERR(fifo->base_addr)) + return PTR_ERR(fifo->base_addr); dev_dbg(fifo->dt_device, "remapped memory to 0x%p\n", fifo->base_addr); - /* create unique device name */ - snprintf(device_name, 32, "%s_%pa", DRIVER_NAME, &r_mem->start); - dev_dbg(fifo->dt_device, "device name [%s]\n", device_name); - /* ---------------------------- * init IP * ---------------------------- @@ -711,7 +708,7 @@ static int axis_fifo_probe(struct platform_device *pdev) rc = axis_fifo_parse_dt(fifo); if (rc) - goto err_initial; + return rc; reset_ip_core(fifo); @@ -723,7 +720,7 @@ static int axis_fifo_probe(struct platform_device *pdev) /* get IRQ resource */ rc = platform_get_irq(pdev, 0); if (rc < 0) - goto err_initial; + return rc; /* request IRQ */ fifo->irq = rc; @@ -732,13 +729,18 @@ static int axis_fifo_probe(struct platform_device *pdev) if (rc) { dev_err(fifo->dt_device, "couldn't allocate interrupt %i\n", fifo->irq); - goto err_initial; + return rc; } /* ---------------------------- * init char device * ---------------------------- */ + fifo->id = ida_alloc(&axis_fifo_ida, GFP_KERNEL); + if (fifo->id < 0) + return fifo->id; + + snprintf(device_name, 32, "%s%d", DRIVER_NAME, fifo->id); /* create character device */ fifo->miscdev.fops = &fops; @@ -746,16 +748,14 @@ static int axis_fifo_probe(struct platform_device *pdev) fifo->miscdev.name = device_name; fifo->miscdev.parent = dev; rc = misc_register(&fifo->miscdev); - if (rc < 0) - goto err_initial; + if (rc < 0) { + ida_free(&axis_fifo_ida, fifo->id); + return rc; + } axis_fifo_debugfs_init(fifo); return 0; - -err_initial: - dev_set_drvdata(dev, NULL); - return rc; } static void axis_fifo_remove(struct platform_device *pdev) @@ -765,7 +765,7 @@ static void axis_fifo_remove(struct platform_device *pdev) debugfs_remove(fifo->debugfs_dir); misc_deregister(&fifo->miscdev); - dev_set_drvdata(dev, NULL); + ida_free(&axis_fifo_ida, fifo->id); } static const struct of_device_id axis_fifo_of_match[] = { @@ -805,6 +805,7 @@ module_init(axis_fifo_init); static void __exit axis_fifo_exit(void) { platform_driver_unregister(&axis_fifo_driver); + ida_destroy(&axis_fifo_ida); } module_exit(axis_fifo_exit); diff --git a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c index b923dc606d1d..77c8e549b208 100644 --- a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c +++ b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c @@ -449,8 +449,8 @@ static int agilent_82357a_read(struct gpib_board *board, u8 *buffer, size_t leng if (!out_data) return -ENOMEM; out_data[i++] = DATA_PIPE_CMD_READ; - out_data[i++] = 0; //primary address when ARF_NO_ADDR is not set - out_data[i++] = 0; //secondary address when ARF_NO_ADDR is not set + out_data[i++] = 0; // primary address when ARF_NO_ADDR is not set + out_data[i++] = 0; // secondary address when ARF_NO_ADDR is not set out_data[i] = ARF_NO_ADDRESS | ARF_END_ON_EOI; if (a_priv->eos_mode & REOS) out_data[i] |= ARF_END_ON_EOS_CHAR; @@ -532,7 +532,7 @@ static int agilent_82357a_read(struct gpib_board *board, u8 *buffer, size_t leng */ agilent_82357a_take_control_internal(board, 0); - //FIXME check trailing flags for error + // FIXME check trailing flags for error return retval; } @@ -966,7 +966,7 @@ static int agilent_82357a_parallel_poll(struct gpib_board *board, u8 *result) dev_err(&usb_dev->dev, "write_registers() returned error\n"); return retval; } - udelay(2); //silly, since usb write will take way longer + udelay(2); // silly, since usb write will take way longer read.address = CPTR; retval = agilent_82357a_read_registers(a_priv, &read, 1, 1); if (retval) { @@ -989,31 +989,31 @@ static int agilent_82357a_parallel_poll(struct gpib_board *board, u8 *result) static void agilent_82357a_parallel_poll_configure(struct gpib_board *board, u8 config) { - //board can only be system controller + // board can only be system controller return;// 0; } static void agilent_82357a_parallel_poll_response(struct gpib_board *board, int ist) { - //board can only be system controller + // board can only be system controller return;// 0; } static void agilent_82357a_serial_poll_response(struct gpib_board *board, u8 status) { - //board can only be system controller + // board can only be system controller return;// 0; } static u8 agilent_82357a_serial_poll_status(struct gpib_board *board) { - //board can only be system controller + // board can only be system controller return 0; } static void agilent_82357a_return_to_local(struct gpib_board *board) { - //board can only be system controller + // board can only be system controller return;// 0; } diff --git a/drivers/staging/gpib/agilent_82357a/agilent_82357a.h b/drivers/staging/gpib/agilent_82357a/agilent_82357a.h index 23aa4799eb86..33ac558e5552 100644 --- a/drivers/staging/gpib/agilent_82357a/agilent_82357a.h +++ b/drivers/staging/gpib/agilent_82357a/agilent_82357a.h @@ -20,7 +20,7 @@ enum usb_vendor_ids { enum usb_device_ids { USB_DEVICE_ID_AGILENT_82357A = 0x0107, USB_DEVICE_ID_AGILENT_82357A_PREINIT = 0x0007, // device id before firmware is loaded - USB_DEVICE_ID_AGILENT_82357B = 0x0718, // device id before firmware is loaded + USB_DEVICE_ID_AGILENT_82357B = 0x0718, // device id before firmware is loaded USB_DEVICE_ID_AGILENT_82357B_PREINIT = 0x0518, // device id before firmware is loaded }; @@ -129,10 +129,10 @@ struct agilent_82357a_priv { struct urb *bulk_urb; struct urb *interrupt_urb; u8 *interrupt_buffer; - struct mutex bulk_transfer_lock; // bulk transfer lock - struct mutex bulk_alloc_lock; // bulk transfer allocation lock - struct mutex interrupt_alloc_lock; // interrupt allocation lock - struct mutex control_alloc_lock; // control message allocation lock + struct mutex bulk_transfer_lock; // bulk transfer lock + struct mutex bulk_alloc_lock; // bulk transfer allocation lock + struct mutex interrupt_alloc_lock; // interrupt allocation lock + struct mutex control_alloc_lock; // control message allocation lock struct timer_list bulk_timer; struct agilent_82357a_urb_ctx context; unsigned int bulk_out_endpoint; diff --git a/drivers/staging/gpib/cb7210/cb7210.h b/drivers/staging/gpib/cb7210/cb7210.h index 13f127563ab3..ddc841ff87ae 100644 --- a/drivers/staging/gpib/cb7210/cb7210.h +++ b/drivers/staging/gpib/cb7210/cb7210.h @@ -56,10 +56,10 @@ enum cb7210_page_in { }; enum hs_regs { - //write registers + // write registers HS_MODE = 0x8, /* HS_MODE register */ HS_INT_LEVEL = 0x9, /* HS_INT_LEVEL register */ - //read registers + // read registers HS_STATUS = 0x8, /* HS_STATUS register */ }; diff --git a/drivers/staging/gpib/cec/cec_gpib.c b/drivers/staging/gpib/cec/cec_gpib.c index 0c9d10ee7cd2..dbf9b95baabc 100644 --- a/drivers/staging/gpib/cec/cec_gpib.c +++ b/drivers/staging/gpib/cec/cec_gpib.c @@ -206,7 +206,7 @@ static struct gpib_interface cec_pci_interface = { .parallel_poll_configure = cec_parallel_poll_configure, .parallel_poll_response = cec_parallel_poll_response, .local_parallel_poll_mode = NULL, // XXX - .line_status = NULL, //XXX + .line_status = NULL, // XXX .update_status = cec_update_status, .primary_address = cec_primary_address, .secondary_address = cec_secondary_address, diff --git a/drivers/staging/gpib/common/gpib_os.c b/drivers/staging/gpib/common/gpib_os.c index 2a0465ce16c4..9dbbac8b8436 100644 --- a/drivers/staging/gpib/common/gpib_os.c +++ b/drivers/staging/gpib/common/gpib_os.c @@ -326,7 +326,7 @@ static int setup_serial_poll(struct gpib_board *board, unsigned int usec_timeout cmd_string[i++] = MLA(board->pad); /* controller's listen address */ if (board->sad >= 0) cmd_string[i++] = MSA(board->sad); - cmd_string[i++] = SPE; //serial poll enable + cmd_string[i++] = SPE; // serial poll enable ret = board->interface->command(board, cmd_string, i, &bytes_written); if (ret < 0 || bytes_written < i) { diff --git a/drivers/staging/gpib/common/iblib.c b/drivers/staging/gpib/common/iblib.c index 549280d9a6e9..7cbb6a467177 100644 --- a/drivers/staging/gpib/common/iblib.c +++ b/drivers/staging/gpib/common/iblib.c @@ -608,7 +608,7 @@ static int wait_satisfied(struct wait_info *winfo, struct gpib_status_queue *sta *status = temp_status; return 1; } -//XXX does wait for END work? +// XXX does wait for END work? return 0; } diff --git a/drivers/staging/gpib/eastwood/fluke_gpib.c b/drivers/staging/gpib/eastwood/fluke_gpib.c index 491356433249..3ae848e3f738 100644 --- a/drivers/staging/gpib/eastwood/fluke_gpib.c +++ b/drivers/staging/gpib/eastwood/fluke_gpib.c @@ -507,7 +507,7 @@ static int fluke_accel_write(struct gpib_board *board, u8 *buffer, size_t length } if (retval < 0) return retval; - //handle sending of last byte with eoi + // handle sending of last byte with eoi if (send_eoi) { size_t num_bytes; diff --git a/drivers/staging/gpib/fmh_gpib/fmh_gpib.c b/drivers/staging/gpib/fmh_gpib/fmh_gpib.c index 4138f3d2bae7..164dcfc3c9ef 100644 --- a/drivers/staging/gpib/fmh_gpib/fmh_gpib.c +++ b/drivers/staging/gpib/fmh_gpib/fmh_gpib.c @@ -523,7 +523,7 @@ static int fmh_gpib_accel_write(struct gpib_board *board, u8 *buffer, } if (retval < 0) return retval; - //handle sending of last byte with eoi + // handle sending of last byte with eoi if (send_eoi) { size_t num_bytes; diff --git a/drivers/staging/gpib/gpio/gpib_bitbang.c b/drivers/staging/gpib/gpio/gpib_bitbang.c index 17884810fd69..374cd61355e9 100644 --- a/drivers/staging/gpib/gpio/gpib_bitbang.c +++ b/drivers/staging/gpib/gpio/gpib_bitbang.c @@ -277,8 +277,8 @@ struct bb_priv { int ndac_mode; /* nrfd interrupt mode 0/1 -> edge/levels */ int dav_tx; /* keep trace of DAV status while sending */ int dav_rx; /* keep trace of DAV status while receiving */ - u8 eos; // eos character - short eos_flags; // eos mode + u8 eos; /* eos character */ + short eos_flags; /* eos mode */ short eos_check; /* eos check required in current operation ... */ short eos_check_8; /* ... with byte comparison */ short eos_mask_7; /* ... with 7 bit masked character */ @@ -290,14 +290,14 @@ struct bb_priv { u8 *rbuf; u8 *wbuf; int end_flag; - int r_busy; /* 0==idle 1==busy */ + int r_busy; /* 0==idle 1==busy */ int w_busy; int write_done; - int cmd; /* 1 = cmd write in progress */ + int cmd; /* 1 = cmd write in progress */ size_t w_cnt; size_t length; u8 *w_buf; - spinlock_t rw_lock; // protect mods to rw_lock + spinlock_t rw_lock; /* protect mods to rw_lock */ int phase; int ndac_idle; int ndac_seq; @@ -726,7 +726,7 @@ static irqreturn_t bb_SRQ_interrupt(int irq, void *arg) static int bb_command(struct gpib_board *board, u8 *buffer, size_t length, size_t *bytes_written) { - size_t ret; + int ret; struct bb_priv *priv = board->private_data; int i; @@ -1462,8 +1462,8 @@ static inline void SET_DIR_READ(struct bb_priv *priv) gpiod_set_value(TE, 0); /* set NDAC and NRFD to transmit and DAV to receive */ } - gpiod_direction_output(NRFD, 0); // hold off the talker - gpiod_direction_output(NDAC, 0); // data not accepted + gpiod_direction_output(NRFD, 0); /* hold off the talker */ + gpiod_direction_output(NDAC, 0); /* data not accepted */ priv->direction = DIR_READ; } diff --git a/drivers/staging/gpib/hp_82341/hp_82341.c b/drivers/staging/gpib/hp_82341/hp_82341.c index e5c1997ce7d9..1a2ad0560e14 100644 --- a/drivers/staging/gpib/hp_82341/hp_82341.c +++ b/drivers/staging/gpib/hp_82341/hp_82341.c @@ -38,7 +38,7 @@ static int hp_82341_accel_read(struct gpib_board *board, u8 *buffer, size_t leng unsigned short event_status; int i; int num_fifo_bytes; - //hardware doesn't support checking for end-of-string character when using fifo + // hardware doesn't support checking for end-of-string character when using fifo if (tms_priv->eos_flags & REOS) return tms9914_read(board, tms_priv, buffer, length, end, bytes_read); @@ -49,7 +49,7 @@ static int hp_82341_accel_read(struct gpib_board *board, u8 *buffer, size_t leng *bytes_read = 0; if (length == 0) return 0; - //disable fifo for the moment + // disable fifo for the moment outb(DIRECTION_GPIB_TO_HOST_BIT, hp_priv->iobase[3] + BUFFER_CONTROL_REG); /* * Handle corner case of board not in holdoff and one byte has slipped in already. @@ -154,7 +154,7 @@ static int restart_write_fifo(struct gpib_board *board, struct hp_82341_priv *hp while (1) { int status; - //restart doesn't work if data holdoff is in effect + // restart doesn't work if data holdoff is in effect status = tms9914_line_status(board, tms_priv); if ((status & BUS_NRFD) == 0) { outb(RESTART_STREAM_BIT, hp_priv->iobase[0] + STREAM_STATUS_REG); @@ -764,7 +764,7 @@ static int hp_82341_attach(struct gpib_board *board, const struct gpib_board_con ENABLE_TI_INTERRUPT_EVENT_BIT, hp_priv->iobase[0] + EVENT_ENABLE_REG); outb(ENABLE_BUFFER_END_INTERRUPT_BIT | ENABLE_TERMINAL_COUNT_INTERRUPT_BIT | ENABLE_TI_INTERRUPT_BIT, hp_priv->iobase[0] + INTERRUPT_ENABLE_REG); - //write clear event register + // write clear event register outb((TI_INTERRUPT_EVENT_BIT | POINTERS_EQUAL_EVENT_BIT | BUFFER_END_EVENT_BIT | TERMINAL_COUNT_EVENT_BIT), hp_priv->iobase[0] + EVENT_STATUS_REG); @@ -867,7 +867,7 @@ static irqreturn_t hp_82341_interrupt(int irq, void *arg) event_status = inb(hp_priv->iobase[0] + EVENT_STATUS_REG); if (event_status & INTERRUPT_PENDING_EVENT_BIT) retval = IRQ_HANDLED; - //write-clear status bits + // write-clear status bits if (event_status & (TI_INTERRUPT_EVENT_BIT | POINTERS_EQUAL_EVENT_BIT | BUFFER_END_EVENT_BIT | TERMINAL_COUNT_EVENT_BIT)) { outb(event_status & (TI_INTERRUPT_EVENT_BIT | POINTERS_EQUAL_EVENT_BIT | @@ -901,7 +901,7 @@ static void set_transfer_counter(struct hp_82341_priv *hp_priv, int count) outb(complement & 0xff, hp_priv->iobase[1] + TRANSFER_COUNT_LOW_REG); outb((complement >> 8) & 0xff, hp_priv->iobase[1] + TRANSFER_COUNT_MID_REG); - //I don't think the hi count reg is even used, but oh well + // I don't think the hi count reg is even used, but oh well outb((complement >> 16) & 0xf, hp_priv->iobase[1] + TRANSFER_COUNT_HIGH_REG); } diff --git a/drivers/staging/gpib/hp_82341/hp_82341.h b/drivers/staging/gpib/hp_82341/hp_82341.h index 370a3d4576eb..859ef2899acb 100644 --- a/drivers/staging/gpib/hp_82341/hp_82341.h +++ b/drivers/staging/gpib/hp_82341/hp_82341.h @@ -65,7 +65,7 @@ enum config_control_status_bits { IRQ_SELECT_MASK = 0x7, DMA_CONFIG_MASK = 0x18, ENABLE_DMA_CONFIG_BIT = 0x20, - XILINX_READY_BIT = 0x40, //read only + XILINX_READY_BIT = 0x40, // read only DONE_PGL_BIT = 0x80 }; @@ -94,7 +94,7 @@ static inline unsigned int IRQ_SELECT_BITS(int irq) }; enum mode_control_status_bits { - SLOT8_BIT = 0x1, // read only + SLOT8_BIT = 0x1, // read only ACTIVE_CONTROLLER_BIT = 0x2, // read only ENABLE_DMA_BIT = 0x4, SYSTEM_CONTROLLER_BIT = 0x8, @@ -106,12 +106,12 @@ enum mode_control_status_bits { enum monitor_bits { MONITOR_INTERRUPT_PENDING_BIT = 0x1, // read only MONITOR_CLEAR_HOLDOFF_BIT = 0x2, // write only - MONITOR_PPOLL_BIT = 0x4, // write clear - MONITOR_SRQ_BIT = 0x8, // write clear - MONITOR_IFC_BIT = 0x10, // write clear - MONITOR_REN_BIT = 0x20, // write clear - MONITOR_END_BIT = 0x40, // write clear - MONITOR_DAV_BIT = 0x80 // write clear + MONITOR_PPOLL_BIT = 0x4, // write clear + MONITOR_SRQ_BIT = 0x8, // write clear + MONITOR_IFC_BIT = 0x10, // write clear + MONITOR_REN_BIT = 0x20, // write clear + MONITOR_END_BIT = 0x40, // write clear + MONITOR_DAV_BIT = 0x80 // write clear }; enum interrupt_enable_bits { @@ -123,36 +123,36 @@ enum interrupt_enable_bits { }; enum event_status_bits { - TI_INTERRUPT_EVENT_BIT = 0x1, //write clear + TI_INTERRUPT_EVENT_BIT = 0x1, // write clear INTERRUPT_PENDING_EVENT_BIT = 0x2, // read only - POINTERS_EQUAL_EVENT_BIT = 0x4, //write clear - BUFFER_END_EVENT_BIT = 0x10, //write clear + POINTERS_EQUAL_EVENT_BIT = 0x4, // write clear + BUFFER_END_EVENT_BIT = 0x10, // write clear TERMINAL_COUNT_EVENT_BIT = 0x20, // write clear DMA_TERMINAL_COUNT_EVENT_BIT = 0x80, // write clear }; enum event_enable_bits { - ENABLE_TI_INTERRUPT_EVENT_BIT = 0x1, //write clear - ENABLE_POINTERS_EQUAL_EVENT_BIT = 0x4, //write clear - ENABLE_BUFFER_END_EVENT_BIT = 0x10, //write clear - ENABLE_TERMINAL_COUNT_EVENT_BIT = 0x20, // write clear + ENABLE_TI_INTERRUPT_EVENT_BIT = 0x1, // write clear + ENABLE_POINTERS_EQUAL_EVENT_BIT = 0x4, // write clear + ENABLE_BUFFER_END_EVENT_BIT = 0x10, // write clear + ENABLE_TERMINAL_COUNT_EVENT_BIT = 0x20, // write clear ENABLE_DMA_TERMINAL_COUNT_EVENT_BIT = 0x80, // write clear }; enum stream_status_bits { - HALTED_STATUS_BIT = 0x1, //read - RESTART_STREAM_BIT = 0x1 //write + HALTED_STATUS_BIT = 0x1, // read + RESTART_STREAM_BIT = 0x1 // write }; enum buffer_control_bits { DIRECTION_GPIB_TO_HOST_BIT = 0x20, // transfer direction (set for gpib to host) - ENABLE_TI_BUFFER_BIT = 0x40, //enable fifo - FAST_WR_EN_BIT = 0x80, // 350 ns t1 delay? + ENABLE_TI_BUFFER_BIT = 0x40, // enable fifo + FAST_WR_EN_BIT = 0x80, // 350 ns t1 delay? }; // registers accessible through isapnp chip on 82341d enum hp_82341d_pnp_registers { - PIO_DATA_REG = 0x20, //read/write pio data lines + PIO_DATA_REG = 0x20, // read/write pio data lines PIO_DIRECTION_REG = 0x21, // set pio data line directions (set for input) }; diff --git a/drivers/staging/gpib/include/amccs5933.h b/drivers/staging/gpib/include/amccs5933.h index 4de0f6797458..d7f63c795096 100644 --- a/drivers/staging/gpib/include/amccs5933.h +++ b/drivers/staging/gpib/include/amccs5933.h @@ -24,7 +24,7 @@ extern inline int INCOMING_MAILBOX_REG(unsigned int mailbox) enum { OUTBOX_EMPTY_INTR_BIT = 0x10, // enable outbox empty interrupt INBOX_FULL_INTR_BIT = 0x1000, // enable inbox full interrupt - INBOX_INTR_CS_BIT = 0x20000, // read, or write clear inbox full interrupt + INBOX_INTR_CS_BIT = 0x20000, // read, or write clear inbox full interrupt INTR_ASSERTED_BIT = 0x800000, // read only, interrupt asserted }; @@ -52,7 +52,7 @@ extern inline int OUTBOX_SELECT_BITS(unsigned int mailbox) return (mailbox & 0x3) << 2; }; -//BMCSR bits +// BMCSR bits enum { MBOX_FLAGS_RESET_BIT = 0x08000000, // resets mailbox empty/full flags }; diff --git a/drivers/staging/gpib/include/gpib_types.h b/drivers/staging/gpib/include/gpib_types.h index db040c80d778..998abb379749 100644 --- a/drivers/staging/gpib/include/gpib_types.h +++ b/drivers/staging/gpib/include/gpib_types.h @@ -273,7 +273,8 @@ struct gpib_board { struct mutex big_gpib_mutex; /* pid of last process to lock the board mutex */ pid_t locking_pid; - spinlock_t locking_pid_spinlock; // lock for setting locking pid + /* lock for setting locking pid */ + spinlock_t locking_pid_spinlock; /* Spin lock for dealing with races with the interrupt handler */ spinlock_t spinlock; /* Watchdog timer to enable timeouts */ diff --git a/drivers/staging/gpib/include/nec7210.h b/drivers/staging/gpib/include/nec7210.h index 312217b4580e..9835aa5ef4ff 100644 --- a/drivers/staging/gpib/include/nec7210.h +++ b/drivers/staging/gpib/include/nec7210.h @@ -22,18 +22,18 @@ struct nec7210_priv { u32 iobase; #endif void __iomem *mmiobase; - unsigned int offset; // offset between successive nec7210 io addresses + unsigned int offset; // offset between successive nec7210 io addresses unsigned int dma_channel; u8 *dma_buffer; unsigned int dma_buffer_length; // length of dma buffer dma_addr_t dma_buffer_addr; // bus address of board->buffer for use with dma // software copy of bits written to registers u8 reg_bits[8]; - u8 auxa_bits; // bits written to auxiliary register A - u8 auxb_bits; // bits written to auxiliary register B + u8 auxa_bits; // bits written to auxiliary register A + u8 auxb_bits; // bits written to auxiliary register B // used to keep track of board's state, bit definitions given below unsigned long state; - /* lock for chips that extend the nec7210 registers by paging in alternate regs */ + // lock for chips that extend the nec7210 registers by paging in alternate regs spinlock_t register_page_lock; // wrappers for outb, inb, readb, or writeb u8 (*read_byte)(struct nec7210_priv *priv, unsigned int register_number); @@ -64,17 +64,17 @@ static inline void write_byte(struct nec7210_priv *priv, u8 byte, unsigned int r // struct nec7210_priv.state bit numbers enum { - PIO_IN_PROGRESS_BN, // pio transfer in progress + PIO_IN_PROGRESS_BN, // pio transfer in progress DMA_READ_IN_PROGRESS_BN, // dma read transfer in progress DMA_WRITE_IN_PROGRESS_BN, // dma write transfer in progress - READ_READY_BN, // board has data byte available to read - WRITE_READY_BN, // board is ready to send a data byte - COMMAND_READY_BN, // board is ready to send a command byte - RECEIVED_END_BN, // received END - BUS_ERROR_BN, // output error has occurred - RFD_HOLDOFF_BN, // rfd holdoff in effect - DEV_CLEAR_BN, // device clear received - ADR_CHANGE_BN, // address state change occurred + READ_READY_BN, // board has data byte available to read + WRITE_READY_BN, // board is ready to send a data byte + COMMAND_READY_BN, // board is ready to send a command byte + RECEIVED_END_BN, // received END + BUS_ERROR_BN, // output error has occurred + RFD_HOLDOFF_BN, // rfd holdoff in effect + DEV_CLEAR_BN, // device clear received + ADR_CHANGE_BN, // address state change occurred }; // interface functions diff --git a/drivers/staging/gpib/include/nec7210_registers.h b/drivers/staging/gpib/include/nec7210_registers.h index 97c53ac8e805..067983d7a07f 100644 --- a/drivers/staging/gpib/include/nec7210_registers.h +++ b/drivers/staging/gpib/include/nec7210_registers.h @@ -11,7 +11,7 @@ enum nec7210_chipset { NEC7210, // The original TNT4882, // NI NAT4882, // NI - CB7210, // measurement computing + CB7210, // measurement computing IOT7210, // iotech IGPIB7210, // Ines TNT5004, // NI (minor differences to TNT4882) @@ -48,7 +48,7 @@ enum nec7210_read_regs { ADR1, // address 2 }; -//bit definitions common to nec-7210 compatible registers +// bit definitions common to nec-7210 compatible registers // ISR1: interrupt status register 1 enum isr1_bits { diff --git a/drivers/staging/gpib/include/plx9050.h b/drivers/staging/gpib/include/plx9050.h index 66c56335f5c0..c911b285a0ca 100644 --- a/drivers/staging/gpib/include/plx9050.h +++ b/drivers/staging/gpib/include/plx9050.h @@ -23,10 +23,10 @@ enum plx9050_intcsr_bits { PLX9050_LINTR2_STATUS_BIT = 0x20, PLX9050_PCI_INTR_EN_BIT = 0x40, PLX9050_SOFT_INTR_BIT = 0x80, - PLX9050_LINTR1_SELECT_ENABLE_BIT = 0x100, //9052 extension - PLX9050_LINTR2_SELECT_ENABLE_BIT = 0x200, //9052 extension - PLX9050_LINTR1_EDGE_CLEAR_BIT = 0x400, //9052 extension - PLX9050_LINTR2_EDGE_CLEAR_BIT = 0x800, //9052 extension + PLX9050_LINTR1_SELECT_ENABLE_BIT = 0x100, // 9052 extension + PLX9050_LINTR2_SELECT_ENABLE_BIT = 0x200, // 9052 extension + PLX9050_LINTR1_EDGE_CLEAR_BIT = 0x400, // 9052 extension + PLX9050_LINTR2_EDGE_CLEAR_BIT = 0x800, // 9052 extension }; enum plx9050_cntrl_bits { diff --git a/drivers/staging/gpib/include/tms9914.h b/drivers/staging/gpib/include/tms9914.h index 50a9d3b22619..e66b75e0fda8 100644 --- a/drivers/staging/gpib/include/tms9914.h +++ b/drivers/staging/gpib/include/tms9914.h @@ -30,10 +30,10 @@ struct tms9914_priv { u8 imr0_bits, imr1_bits; // bits written to address mode register u8 admr_bits; - u8 auxa_bits; // bits written to auxiliary register A + u8 auxa_bits; // bits written to auxiliary register A // used to keep track of board's state, bit definitions given below unsigned long state; - u8 eos; // eos character + u8 eos; // eos character short eos_flags; u8 spoll_status; enum tms9914_holdoff_mode holdoff_mode; @@ -67,15 +67,15 @@ static inline void write_byte(struct tms9914_priv *priv, u8 byte, unsigned int r // struct tms9914_priv.state bit numbers enum { - PIO_IN_PROGRESS_BN, // pio transfer in progress + PIO_IN_PROGRESS_BN, // pio transfer in progress DMA_READ_IN_PROGRESS_BN, // dma read transfer in progress DMA_WRITE_IN_PROGRESS_BN, // dma write transfer in progress - READ_READY_BN, // board has data byte available to read - WRITE_READY_BN, // board is ready to send a data byte - COMMAND_READY_BN, // board is ready to send a command byte - RECEIVED_END_BN, // received END - BUS_ERROR_BN, // bus error - DEV_CLEAR_BN, // device clear received + READ_READY_BN, // board has data byte available to read + WRITE_READY_BN, // board is ready to send a data byte + COMMAND_READY_BN, // board is ready to send a command byte + RECEIVED_END_BN, // received END + BUS_ERROR_BN, // bus error + DEV_CLEAR_BN, // device clear received }; // interface functions @@ -150,23 +150,23 @@ enum { IMR0 = 0, /* interrupt mask 0 */ IMR1 = 1, /* interrupt mask 1 */ AUXCR = 3, /* auxiliary command */ - ADR = 4, // address register - SPMR = 5, // serial poll mode register + ADR = 4, /* address register */ + SPMR = 5, /* serial poll mode register */ PPR = 6, /* parallel poll */ CDOR = 7, /* data out register */ }; // read registers enum { - ISR0 = 0, /* interrupt status 0 */ - ISR1 = 1, /* interrupt status 1 */ - ADSR = 2, /* address status */ - BSR = 3, /* bus status */ - CPTR = 6, /* command pass thru */ - DIR = 7, /* data in register */ + ISR0 = 0, /* interrupt status 0 */ + ISR1 = 1, /* interrupt status 1 */ + ADSR = 2, /* address status */ + BSR = 3, /* bus status */ + CPTR = 6, /* command pass thru */ + DIR = 7, /* data in register */ }; -//bit definitions common to tms9914 compatible registers +// bit definitions common to tms9914 compatible registers /* ISR0 - Register bits */ enum isr0_bits { @@ -248,33 +248,33 @@ enum bus_status_bits { /*---------------------------------------------------------*/ enum aux_cmd_bits { - AUX_CS = 0x80, /* set bit instead of clearing it, used with commands marked 'd' below */ - AUX_CHIP_RESET = 0x0, /* d Chip reset */ - AUX_INVAL = 0x1, // release dac holdoff, invalid command byte - AUX_VAL = (AUX_INVAL | AUX_CS), // release dac holdoff, valid command byte - AUX_RHDF = 0x2, /* X Release RFD holdoff */ - AUX_HLDA = 0x3, /* d holdoff on all data */ - AUX_HLDE = 0x4, /* d holdoff on EOI only */ - AUX_NBAF = 0x5, /* X Set new byte available false */ - AUX_FGET = 0x6, /* d force GET */ - AUX_RTL = 0x7, /* d return to local */ - AUX_SEOI = 0x8, /* X send EOI with next byte */ - AUX_LON = 0x9, /* d Listen only */ - AUX_TON = 0xa, /* d Talk only */ - AUX_GTS = 0xb, /* X goto standby */ - AUX_TCA = 0xc, /* X take control asynchronously */ - AUX_TCS = 0xd, /* X take " synchronously */ - AUX_RPP = 0xe, /* d Request parallel poll */ - AUX_SIC = 0xf, /* d send interface clear */ - AUX_SRE = 0x10, /* d send remote enable */ - AUX_RQC = 0x11, /* X request control */ - AUX_RLC = 0x12, /* X release control */ - AUX_DAI = 0x13, /* d disable all interrupts */ - AUX_PTS = 0x14, /* X pass through next secondary */ - AUX_STDL = 0x15, /* d short T1 delay */ - AUX_SHDW = 0x16, /* d shadow handshake */ - AUX_VSTDL = 0x17, /* d very short T1 delay (smj9914 extension) */ - AUX_RSV2 = 0x18, /* d request service bit 2 (smj9914 extension) */ + AUX_CS = 0x80, /* set bit instead of clearing it, used with commands marked 'd' below */ + AUX_CHIP_RESET = 0x0, /* d Chip reset */ + AUX_INVAL = 0x1, /* release dac holdoff, invalid command byte */ + AUX_VAL = (AUX_INVAL | AUX_CS), /* release dac holdoff, valid command byte */ + AUX_RHDF = 0x2, /* X Release RFD holdoff */ + AUX_HLDA = 0x3, /* d holdoff on all data */ + AUX_HLDE = 0x4, /* d holdoff on EOI only */ + AUX_NBAF = 0x5, /* X Set new byte available false */ + AUX_FGET = 0x6, /* d force GET */ + AUX_RTL = 0x7, /* d return to local */ + AUX_SEOI = 0x8, /* X send EOI with next byte */ + AUX_LON = 0x9, /* d Listen only */ + AUX_TON = 0xa, /* d Talk only */ + AUX_GTS = 0xb, /* X goto standby */ + AUX_TCA = 0xc, /* X take control asynchronously */ + AUX_TCS = 0xd, /* X take " synchronously */ + AUX_RPP = 0xe, /* d Request parallel poll */ + AUX_SIC = 0xf, /* d send interface clear */ + AUX_SRE = 0x10, /* d send remote enable */ + AUX_RQC = 0x11, /* X request control */ + AUX_RLC = 0x12, /* X release control */ + AUX_DAI = 0x13, /* d disable all interrupts */ + AUX_PTS = 0x14, /* X pass through next secondary */ + AUX_STDL = 0x15, /* d short T1 delay */ + AUX_SHDW = 0x16, /* d shadow handshake */ + AUX_VSTDL = 0x17, /* d very short T1 delay (smj9914 extension) */ + AUX_RSV2 = 0x18, /* d request service bit 2 (smj9914 extension) */ }; #endif //_TMS9914_H diff --git a/drivers/staging/gpib/include/tnt4882_registers.h b/drivers/staging/gpib/include/tnt4882_registers.h index 1b1441cd03d5..d54c4cc61168 100644 --- a/drivers/staging/gpib/include/tnt4882_registers.h +++ b/drivers/staging/gpib/include/tnt4882_registers.h @@ -32,11 +32,11 @@ enum { CMDR = 0x1c, // command register TIMER = 0x1e, // timer register - STS1 = 0x10, /* T488 Status Register 1 */ - STS2 = 0x1c, /* T488 Status Register 2 */ + STS1 = 0x10, // T488 Status Register 1 + STS2 = 0x1c, // T488 Status Register 2 ISR0 = IMR0, - ISR3 = 0x1a, /* T488 Interrupt Status Register 3 */ - BCR = 0x1f, /* bus control/status register */ + ISR3 = 0x1a, // T488 Interrupt Status Register 3 + BCR = 0x1f, // bus control/status register BSR = BCR, }; @@ -107,11 +107,11 @@ enum imr0_bits { /* ISR0 -- Interrupt Status Register 0 */ enum isr0_bits { - TNT_SYNC_BIT = 0x1, /* handshake sync */ - TNT_TO_BIT = 0x2, /* timeout */ - TNT_ATNI_BIT = 0x4, /* ATN interrupt */ + TNT_SYNC_BIT = 0x1, /* handshake sync */ + TNT_TO_BIT = 0x2, /* timeout */ + TNT_ATNI_BIT = 0x4, /* ATN interrupt */ TNT_IFCI_BIT = 0x8, /* interface clear interrupt */ - TNT_EOS_BIT = 0x10, /* end of string */ + TNT_EOS_BIT = 0x10, /* end of string */ TNT_NL_BIT = 0x20, /* new line receive */ TNT_STBO_BIT = 0x40, /* status byte out */ TNT_NBA_BIT = 0x80, /* new byte available */ @@ -129,7 +129,7 @@ enum isr3_bits { }; enum keyreg_bits { - MSTD = 0x20, // enable 350ns T1 delay + MSTD = 0x20, /* enable 350ns T1 delay */ }; /* STS1 -- Status Register 1 (read only) */ @@ -157,7 +157,7 @@ enum tnt4882_aux_cmds { AUX_9914 = 0x15, // switch to 9914 mode AUX_REQT = 0x18, AUX_REQF = 0x19, - AUX_PAGEIN = 0x50, /* page in alternate registers */ + AUX_PAGEIN = 0x50, // page in alternate registers AUX_HLDI = 0x51, // rfd holdoff immediately AUX_CLEAR_END = 0x55, AUX_7210 = 0x99, // switch to 7210 mode @@ -183,7 +183,7 @@ enum auxi_bits { enum sasr_bits { ACRDY_BIT = 0x4, /* acceptor ready state */ - ADHS_BIT = 0x8, /* acceptor data holdoff state */ + ADHS_BIT = 0x8, /* acceptor data holdoff state */ ANHS2_BIT = 0x10, /* acceptor not ready holdoff immediately state */ ANHS1_BIT = 0x20, /* acceptor not ready holdoff state */ AEHS_BIT = 0x40, /* acceptor end holdoff state */ diff --git a/drivers/staging/gpib/ines/ines.h b/drivers/staging/gpib/ines/ines.h index f0210ce2470d..6ad57e9a1216 100644 --- a/drivers/staging/gpib/ines/ines.h +++ b/drivers/staging/gpib/ines/ines.h @@ -97,9 +97,9 @@ enum extend_mode_bits { TR3_TRIG_ENABLE_BIT = 0x1, // enable generation of trigger pulse T/R3 pin // clear message available status bit when chip writes byte with EOI true MAV_ENABLE_BIT = 0x2, - EOS1_ENABLE_BIT = 0x4, // enable eos register 1 - EOS2_ENABLE_BIT = 0x8, // enable eos register 2 - EOIDIS_BIT = 0x10, // disable EOI interrupt when doing rfd holdoff on end? + EOS1_ENABLE_BIT = 0x4, // enable eos register 1 + EOS2_ENABLE_BIT = 0x8, // enable eos register 2 + EOIDIS_BIT = 0x10, // disable EOI interrupt when doing rfd holdoff on end? XFER_COUNTER_ENABLE_BIT = 0x20, XFER_COUNTER_OUTPUT_BIT = 0x40, // use counter for output, clear for input // when xfer counter hits 0, assert EOI on write or RFD holdoff on read @@ -121,10 +121,10 @@ enum ines_admr_bits { }; enum xdma_control_bits { - DMA_OUTPUT_BIT = 0x1, // use dma for output, clear for input + DMA_OUTPUT_BIT = 0x1, // use dma for output, clear for input ENABLE_SYNC_DMA_BIT = 0x2, - DMA_ACCESS_EVERY_CYCLE = 0x4,// dma accesses fifo every cycle, clear for every other cycle - DMA_16BIT = 0x8, // clear for 8 bit transfers + DMA_ACCESS_EVERY_CYCLE = 0x4, // dma accesses fifo every cycle, clear for every other cycle + DMA_16BIT = 0x8, // clear for 8 bit transfers }; enum bus_control_monitor_bits { diff --git a/drivers/staging/gpib/ines/ines_gpib.c b/drivers/staging/gpib/ines/ines_gpib.c index c851fd014f48..a3cf846fd0f9 100644 --- a/drivers/staging/gpib/ines/ines_gpib.c +++ b/drivers/staging/gpib/ines/ines_gpib.c @@ -152,7 +152,7 @@ static int ines_accel_read(struct gpib_board *board, u8 *buffer, write_byte(nec_priv, INES_RFD_HLD_IMMEDIATE, AUXMR); - //clear in fifo + // clear in fifo nec7210_set_reg_bits(nec_priv, ADMR, IN_FIFO_ENABLE_BIT, 0); nec7210_set_reg_bits(nec_priv, ADMR, IN_FIFO_ENABLE_BIT, IN_FIFO_ENABLE_BIT); @@ -225,7 +225,7 @@ static int ines_accel_write(struct gpib_board *board, u8 *buffer, size_t length, unsigned int num_bytes, i; *bytes_written = 0; - //clear out fifo + // clear out fifo nec7210_set_reg_bits(nec_priv, ADMR, OUT_FIFO_ENABLE_BIT, 0); nec7210_set_reg_bits(nec_priv, ADMR, OUT_FIFO_ENABLE_BIT, OUT_FIFO_ENABLE_BIT); diff --git a/drivers/staging/gpib/nec7210/nec7210.c b/drivers/staging/gpib/nec7210/nec7210.c index 34a1cae4f486..bbf39367f5e4 100644 --- a/drivers/staging/gpib/nec7210/nec7210.c +++ b/drivers/staging/gpib/nec7210/nec7210.c @@ -779,10 +779,10 @@ int nec7210_write(struct gpib_board *board, struct nec7210_priv *priv, *bytes_written = 0; - clear_bit(DEV_CLEAR_BN, &priv->state); //XXX + clear_bit(DEV_CLEAR_BN, &priv->state); // XXX if (send_eoi) - length-- ; /* save the last byte for sending EOI */ + length-- ; // save the last byte for sending EOI if (length > 0) { // isa dma transfer @@ -1005,7 +1005,7 @@ void nec7210_board_online(struct nec7210_priv *priv, const struct gpib_board *bo nec7210_primary_address(board, priv, board->pad); nec7210_secondary_address(board, priv, board->sad, board->sad >= 0); - // enable interrupts + /* enable interrupts */ priv->reg_bits[IMR1] = HR_ERRIE | HR_DECIE | HR_ENDIE | HR_DETIE | HR_CPTIE | HR_DOIE | HR_DIIE; priv->reg_bits[IMR2] = IMR2_ENABLE_INTR_MASK; diff --git a/drivers/staging/gpib/ni_usb/ni_usb_gpib.c b/drivers/staging/gpib/ni_usb/ni_usb_gpib.c index 73ea72f34c0a..4dec87d12687 100644 --- a/drivers/staging/gpib/ni_usb/ni_usb_gpib.c +++ b/drivers/staging/gpib/ni_usb/ni_usb_gpib.c @@ -29,7 +29,7 @@ static void ni_usb_stop(struct ni_usb_priv *ni_priv); static DEFINE_MUTEX(ni_usb_hotplug_lock); -//calculates a reasonable timeout in that can be passed to usb functions +// calculates a reasonable timeout in that can be passed to usb functions static inline unsigned long ni_usb_timeout_msecs(unsigned int usec) { if (usec == 0) @@ -327,7 +327,7 @@ static void ni_usb_soft_update_status(struct gpib_board *board, unsigned int ni_ board->status &= ~clear_mask; board->status &= ~ni_usb_ibsta_mask; board->status |= ni_usb_ibsta & ni_usb_ibsta_mask; - //FIXME should generate events on DTAS and DCAS + // FIXME should generate events on DTAS and DCAS spin_lock_irqsave(&board->spinlock, flags); /* remove set status bits from monitored set why ?***/ @@ -569,7 +569,7 @@ static int ni_usb_write_registers(struct ni_usb_priv *ni_priv, mutex_unlock(&ni_priv->addressed_transfer_lock); ni_usb_parse_reg_write_status_block(in_data, &status, ®_writes_completed); - //FIXME parse extra 09 status bits and termination + // FIXME parse extra 09 status bits and termination kfree(in_data); if (status.id != NIUSB_REG_WRITE_ID) { dev_err(&usb_dev->dev, "parse error, id=0x%x != NIUSB_REG_WRITE_ID\n", status.id); @@ -1106,7 +1106,7 @@ static int ni_usb_request_system_control(struct gpib_board *board, int request_c return 0; } -//FIXME maybe the interface should have a "pulse interface clear" function that can return an error? +// FIXME maybe the interface should have a "pulse interface clear" function that can return an error? static void ni_usb_interface_clear(struct gpib_board *board, int assert) { int retval; @@ -1363,7 +1363,7 @@ static int ni_usb_parallel_poll(struct gpib_board *board, u8 *result) return -ENOMEM; out_data[i++] = NIUSB_IBRPP_ID; - out_data[i++] = 0xf0; //FIXME: this should be the parallel poll timeout code + out_data[i++] = 0xf0; // FIXME: this should be the parallel poll timeout code out_data[i++] = 0x0; out_data[i++] = 0x0; i += ni_usb_bulk_termination(&out_data[i]); diff --git a/drivers/staging/gpib/ni_usb/ni_usb_gpib.h b/drivers/staging/gpib/ni_usb/ni_usb_gpib.h index b011e131201c..688f5e08792f 100644 --- a/drivers/staging/gpib/ni_usb/ni_usb_gpib.h +++ b/drivers/staging/gpib/ni_usb/ni_usb_gpib.h @@ -72,10 +72,10 @@ struct ni_usb_priv { struct urb *bulk_urb; struct urb *interrupt_urb; u8 interrupt_buffer[0x11]; - struct mutex addressed_transfer_lock; // protect transfer lock - struct mutex bulk_transfer_lock; // protect bulk message sends - struct mutex control_transfer_lock; // protect control messages - struct mutex interrupt_transfer_lock; // protect interrupt messages + struct mutex addressed_transfer_lock; // protect transfer lock + struct mutex bulk_transfer_lock; // protect bulk message sends + struct mutex control_transfer_lock; // protect control messages + struct mutex interrupt_transfer_lock; // protect interrupt messages struct timer_list bulk_timer; struct ni_usb_urb_ctx context; int product_id; @@ -145,7 +145,7 @@ enum ni_usb_error_codes { * CIC with no listener */ NIUSB_NO_LISTENER_ERROR = 8, - // get NIUSB_TIMEOUT_ERROR on board read/write timeout + /* get NIUSB_TIMEOUT_ERROR on board read/write timeout */ NIUSB_TIMEOUT_ERROR = 10, }; diff --git a/drivers/staging/gpib/pc2/pc2_gpib.c b/drivers/staging/gpib/pc2/pc2_gpib.c index 2282492025b7..9f3943d1df66 100644 --- a/drivers/staging/gpib/pc2/pc2_gpib.c +++ b/drivers/staging/gpib/pc2/pc2_gpib.c @@ -36,7 +36,7 @@ static const int pc2_2a_iosize = 16; static const int pc2a_reg_offset = 0x400; static const int pc2_reg_offset = 1; -//interrupt service routine +// interrupt service routine static irqreturn_t pc2_interrupt(int irq, void *arg); static irqreturn_t pc2a_interrupt(int irq, void *arg); @@ -593,7 +593,7 @@ static struct gpib_interface pc2a_cb7210_interface = { .parallel_poll_configure = pc2_parallel_poll_configure, .parallel_poll_response = pc2_parallel_poll_response, .local_parallel_poll_mode = NULL, // XXX - .line_status = NULL, //XXX + .line_status = NULL, // XXX .update_status = pc2_update_status, .primary_address = pc2_primary_address, .secondary_address = pc2_secondary_address, diff --git a/drivers/staging/gpib/tms9914/tms9914.c b/drivers/staging/gpib/tms9914/tms9914.c index 04d57108efc7..0d11b80bb982 100644 --- a/drivers/staging/gpib/tms9914/tms9914.c +++ b/drivers/staging/gpib/tms9914/tms9914.c @@ -647,7 +647,7 @@ static void check_my_address_state(struct gpib_board *board, } else if (cmd_byte == MTA(board->pad)) { priv->primary_talk_addressed = 1; if (board->sad < 0) - //make active talker + // make active talker write_byte(priv, AUX_TON | AUX_CS, AUXCR); } else if (board->sad >= 0 && priv->primary_talk_addressed && cmd_byte == MSA(board->sad)) { @@ -730,7 +730,7 @@ irqreturn_t tms9914_interrupt_have_status(struct gpib_board *board, struct tms99 if (status0 & HR_SPAS) { priv->spoll_status &= ~request_service_bit; write_byte(priv, priv->spoll_status, SPMR); - //FIXME: set SPOLL status bit + // FIXME: set SPOLL status bit } // record service request in status if (status1 & HR_SRQ) @@ -841,7 +841,7 @@ void tms9914_board_reset(struct tms9914_priv *priv) /* parallel poll unconfigure */ write_byte(priv, 0, PPR); - // request for data holdoff + /* request for data holdoff */ tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_ALL); } EXPORT_SYMBOL_GPL(tms9914_board_reset); @@ -852,7 +852,7 @@ void tms9914_online(struct gpib_board *board, struct tms9914_priv *priv) tms9914_primary_address(board, priv, board->pad); tms9914_secondary_address(board, priv, board->sad, board->sad >= 0); - // enable tms9914 interrupts + /* enable tms9914 interrupts */ priv->imr0_bits |= HR_MACIE | HR_RLCIE | HR_ENDIE | HR_BOIE | HR_BIIE | HR_SPASIE; priv->imr1_bits |= HR_MAIE | HR_SRQIE | HR_UNCIE | HR_ERRIE | HR_IFCIE | @@ -861,7 +861,7 @@ void tms9914_online(struct gpib_board *board, struct tms9914_priv *priv) write_byte(priv, priv->imr1_bits, IMR1); write_byte(priv, AUX_DAI, AUXCR); - // turn off reset state + /* turn off reset state */ write_byte(priv, AUX_CHIP_RESET, AUXCR); } EXPORT_SYMBOL_GPL(tms9914_online); diff --git a/drivers/staging/gpib/tnt4882/mite.h b/drivers/staging/gpib/tnt4882/mite.h index 522d6b56cb7d..a1fdba9672a0 100644 --- a/drivers/staging/gpib/tnt4882/mite.h +++ b/drivers/staging/gpib/tnt4882/mite.h @@ -219,15 +219,15 @@ void mite_list_devices(void); #define MITE_AMHOST_A24_BLOCK 0x3b enum mite_registers { - MITE_IODWBSR = 0xc0, //IO Device Window Base Size Register - MITE_CSIGR = 0x460, //chip signature - MITE_IODWBSR_1 = 0xc4, // IO Device Window Base Size Register 1 (used by 6602 boards) + MITE_IODWBSR = 0xc0, // IO Device Window Base Size Register + MITE_CSIGR = 0x460, // chip signature + MITE_IODWBSR_1 = 0xc4, // IO Device Window Base Size Register 1 (used by 6602 boards) MITE_IODWCR_1 = 0xf4 }; enum MITE_IODWBSR_bits { - WENAB = 0x80, // window enable - WENAB_6602 = 0x8c // window enable for 6602 boards + WENAB = 0x80, // window enable + WENAB_6602 = 0x8c // window enable for 6602 boards }; #endif diff --git a/drivers/staging/gpib/tnt4882/tnt4882_gpib.c b/drivers/staging/gpib/tnt4882/tnt4882_gpib.c index a17b69e34986..c03a976b7380 100644 --- a/drivers/staging/gpib/tnt4882/tnt4882_gpib.c +++ b/drivers/staging/gpib/tnt4882/tnt4882_gpib.c @@ -570,7 +570,7 @@ static irqreturn_t tnt4882_internal_interrupt(struct gpib_board *board) if (isr0_bits & TNT_IFCI_BIT) push_gpib_event(board, EVENT_IFC); - //XXX don't need this wakeup, one below should do? + // XXX don't need this wakeup, one below should do? // wake_up_interruptible(&board->wait); if (isr3_bits & HR_NFF) @@ -730,7 +730,7 @@ static int tnt4882_parallel_poll(struct gpib_board *board, u8 *result) if (tnt_priv->nec7210_priv.type != NEC7210) { tnt_priv->auxg_bits |= RPP2_BIT; write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR); - udelay(2); //FIXME use parallel poll timeout + udelay(2); // FIXME use parallel poll timeout *result = read_byte(&tnt_priv->nec7210_priv, CPTR); tnt_priv->auxg_bits &= ~RPP2_BIT; write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR); @@ -1522,7 +1522,6 @@ static void __exit tnt4882_exit_module(void) #include <linux/moduleparam.h> #include <linux/ptrace.h> #include <linux/timer.h> -#include <linux/ioport.h> #include <linux/io.h> #include <pcmcia/cistpl.h> diff --git a/drivers/staging/iio/adc/ad7816.c b/drivers/staging/iio/adc/ad7816.c index 4774df778de9..172acf135f3b 100644 --- a/drivers/staging/iio/adc/ad7816.c +++ b/drivers/staging/iio/adc/ad7816.c @@ -359,8 +359,6 @@ static int ad7816_probe(struct spi_device *spi_dev) if (!indio_dev) return -ENOMEM; chip = iio_priv(indio_dev); - /* this is only used for device removal purposes */ - dev_set_drvdata(&spi_dev->dev, indio_dev); chip->spi_dev = spi_dev; for (i = 0; i <= AD7816_CS_MAX; i++) diff --git a/drivers/staging/octeon/ethernet-tx.c b/drivers/staging/octeon/ethernet-tx.c index 0ba240e634a1..f5bbedac6a65 100644 --- a/drivers/staging/octeon/ethernet-tx.c +++ b/drivers/staging/octeon/ethernet-tx.c @@ -573,42 +573,14 @@ netdev_tx_t cvm_oct_xmit_pow(struct sk_buff *skb, struct net_device *dev) if (skb->protocol == htons(ETH_P_IP)) { work->word2.s.ip_offset = 14; -#if 0 - work->word2.s.vlan_valid = 0; /* FIXME */ - work->word2.s.vlan_cfi = 0; /* FIXME */ - work->word2.s.vlan_id = 0; /* FIXME */ - work->word2.s.dec_ipcomp = 0; /* FIXME */ -#endif work->word2.s.tcp_or_udp = (ip_hdr(skb)->protocol == IPPROTO_TCP) || (ip_hdr(skb)->protocol == IPPROTO_UDP); -#if 0 - /* FIXME */ - work->word2.s.dec_ipsec = 0; - /* We only support IPv4 right now */ - work->word2.s.is_v6 = 0; - /* Hardware would set to zero */ - work->word2.s.software = 0; - /* No error, packet is internal */ - work->word2.s.L4_error = 0; -#endif work->word2.s.is_frag = !((ip_hdr(skb)->frag_off == 0) || (ip_hdr(skb)->frag_off == cpu_to_be16(1 << 14))); -#if 0 - /* Assume Linux is sending a good packet */ - work->word2.s.IP_exc = 0; -#endif work->word2.s.is_bcast = (skb->pkt_type == PACKET_BROADCAST); work->word2.s.is_mcast = (skb->pkt_type == PACKET_MULTICAST); -#if 0 - /* This is an IP packet */ - work->word2.s.not_IP = 0; - /* No error, packet is internal */ - work->word2.s.rcv_error = 0; - /* No error, packet is internal */ - work->word2.s.err_code = 0; -#endif /* * When copying the data, include 4 bytes of the @@ -618,12 +590,6 @@ netdev_tx_t cvm_oct_xmit_pow(struct sk_buff *skb, struct net_device *dev) memcpy(work->packet_data, skb->data + 10, sizeof(work->packet_data)); } else { -#if 0 - work->word2.snoip.vlan_valid = 0; /* FIXME */ - work->word2.snoip.vlan_cfi = 0; /* FIXME */ - work->word2.snoip.vlan_id = 0; /* FIXME */ - work->word2.snoip.software = 0; /* Hardware would set to zero */ -#endif work->word2.snoip.is_rarp = skb->protocol == htons(ETH_P_RARP); work->word2.snoip.is_arp = skb->protocol == htons(ETH_P_ARP); work->word2.snoip.is_bcast = @@ -631,12 +597,6 @@ netdev_tx_t cvm_oct_xmit_pow(struct sk_buff *skb, struct net_device *dev) work->word2.snoip.is_mcast = (skb->pkt_type == PACKET_MULTICAST); work->word2.snoip.not_IP = 1; /* IP was done up above */ -#if 0 - /* No error, packet is internal */ - work->word2.snoip.rcv_error = 0; - /* No error, packet is internal */ - work->word2.snoip.err_code = 0; -#endif memcpy(work->packet_data, skb->data, sizeof(work->packet_data)); } diff --git a/drivers/staging/octeon/octeon-stubs.h b/drivers/staging/octeon/octeon-stubs.h index 44cced319c11..35b5078ba51e 100644 --- a/drivers/staging/octeon/octeon-stubs.h +++ b/drivers/staging/octeon/octeon-stubs.h @@ -43,81 +43,83 @@ #define CVMX_POW_WQ_INT_PC 0 union cvmx_pip_wqe_word2 { - uint64_t u64; + u64 u64; + struct { - uint64_t bufs:8; - uint64_t ip_offset:8; - uint64_t vlan_valid:1; - uint64_t vlan_stacked:1; - uint64_t unassigned:1; - uint64_t vlan_cfi:1; - uint64_t vlan_id:12; - uint64_t pr:4; - uint64_t unassigned2:8; - uint64_t dec_ipcomp:1; - uint64_t tcp_or_udp:1; - uint64_t dec_ipsec:1; - uint64_t is_v6:1; - uint64_t software:1; - uint64_t L4_error:1; - uint64_t is_frag:1; - uint64_t IP_exc:1; - uint64_t is_bcast:1; - uint64_t is_mcast:1; - uint64_t not_IP:1; - uint64_t rcv_error:1; - uint64_t err_code:8; + u64 bufs : 8; + u64 ip_offset : 8; + u64 vlan_valid : 1; + u64 vlan_stacked : 1; + u64 unassigned : 1; + u64 vlan_cfi : 1; + u64 vlan_id : 12; + u64 pr : 4; + u64 unassigned2 : 8; + u64 dec_ipcomp : 1; + u64 tcp_or_udp : 1; + u64 dec_ipsec : 1; + u64 is_v6 : 1; + u64 software : 1; + u64 L4_error : 1; + u64 is_frag : 1; + u64 IP_exc : 1; + u64 is_bcast : 1; + u64 is_mcast : 1; + u64 not_IP : 1; + u64 rcv_error : 1; + u64 err_code : 8; } s; + struct { - uint64_t bufs:8; - uint64_t ip_offset:8; - uint64_t vlan_valid:1; - uint64_t vlan_stacked:1; - uint64_t unassigned:1; - uint64_t vlan_cfi:1; - uint64_t vlan_id:12; - uint64_t port:12; - uint64_t dec_ipcomp:1; - uint64_t tcp_or_udp:1; - uint64_t dec_ipsec:1; - uint64_t is_v6:1; - uint64_t software:1; - uint64_t L4_error:1; - uint64_t is_frag:1; - uint64_t IP_exc:1; - uint64_t is_bcast:1; - uint64_t is_mcast:1; - uint64_t not_IP:1; - uint64_t rcv_error:1; - uint64_t err_code:8; + u64 bufs : 8; + u64 ip_offset : 8; + u64 vlan_valid : 1; + u64 vlan_stacked : 1; + u64 unassigned : 1; + u64 vlan_cfi : 1; + u64 vlan_id : 12; + u64 port : 12; + u64 dec_ipcomp : 1; + u64 tcp_or_udp : 1; + u64 dec_ipsec : 1; + u64 is_v6 : 1; + u64 software : 1; + u64 L4_error : 1; + u64 is_frag : 1; + u64 IP_exc : 1; + u64 is_bcast : 1; + u64 is_mcast : 1; + u64 not_IP : 1; + u64 rcv_error : 1; + u64 err_code : 8; } s_cn68xx; struct { - uint64_t unused1:16; - uint64_t vlan:16; - uint64_t unused2:32; + u64 unused1 : 16; + u64 vlan : 16; + u64 unused2 : 32; } svlan; + struct { - uint64_t bufs:8; - uint64_t unused:8; - uint64_t vlan_valid:1; - uint64_t vlan_stacked:1; - uint64_t unassigned:1; - uint64_t vlan_cfi:1; - uint64_t vlan_id:12; - uint64_t pr:4; - uint64_t unassigned2:12; - uint64_t software:1; - uint64_t unassigned3:1; - uint64_t is_rarp:1; - uint64_t is_arp:1; - uint64_t is_bcast:1; - uint64_t is_mcast:1; - uint64_t not_IP:1; - uint64_t rcv_error:1; - uint64_t err_code:8; + u64 bufs : 8; + u64 unused : 8; + u64 vlan_valid : 1; + u64 vlan_stacked : 1; + u64 unassigned : 1; + u64 vlan_cfi : 1; + u64 vlan_id : 12; + u64 pr : 4; + u64 unassigned2 : 12; + u64 software : 1; + u64 unassigned3 : 1; + u64 is_rarp : 1; + u64 is_arp : 1; + u64 is_bcast : 1; + u64 is_mcast : 1; + u64 not_IP : 1; + u64 rcv_error : 1; + u64 err_code : 8; } snoip; - }; union cvmx_pip_wqe_word0 { diff --git a/drivers/staging/rtl8723bs/Makefile b/drivers/staging/rtl8723bs/Makefile index 8560b84a3146..1768b8123f03 100644 --- a/drivers/staging/rtl8723bs/Makefile +++ b/drivers/staging/rtl8723bs/Makefile @@ -48,10 +48,8 @@ r8723bs-y = \ hal/HalHWImg8723B_RF.o \ hal/HalPhyRf_8723B.o \ os_dep/ioctl_cfg80211.o \ - os_dep/mlme_linux.o \ os_dep/osdep_service.o \ os_dep/os_intfs.o \ - os_dep/recv_linux.o \ os_dep/sdio_intf.o \ os_dep/sdio_ops_linux.o \ os_dep/wifi_regd.o \ diff --git a/drivers/staging/rtl8723bs/core/rtw_ap.c b/drivers/staging/rtl8723bs/core/rtw_ap.c index b2e7e7267aa4..0908f2234f67 100644 --- a/drivers/staging/rtl8723bs/core/rtw_ap.c +++ b/drivers/staging/rtl8723bs/core/rtw_ap.c @@ -258,11 +258,9 @@ void expire_timeout_chk(struct adapter *padapter) } else { /* TODO: Aging mechanism to digest frames in sleep_q to */ /* avoid running out of xmitframe */ - if (psta->sleepq_len > (NR_XMITFRAME / pstapriv->asoc_list_cnt) - && padapter->xmitpriv.free_xmitframe_cnt < (( - NR_XMITFRAME / pstapriv->asoc_list_cnt - ) / 2) - ) + if (psta->sleepq_len > (NR_XMITFRAME / pstapriv->asoc_list_cnt) && + padapter->xmitpriv.free_xmitframe_cnt < + ((NR_XMITFRAME / pstapriv->asoc_list_cnt) / 2)) wakeup_sta_to_xmit(padapter, psta); } } diff --git a/drivers/staging/rtl8723bs/core/rtw_efuse.c b/drivers/staging/rtl8723bs/core/rtw_efuse.c index 208373113a62..d5c53b614f61 100644 --- a/drivers/staging/rtl8723bs/core/rtw_efuse.c +++ b/drivers/staging/rtl8723bs/core/rtw_efuse.c @@ -29,57 +29,6 @@ u8 fakeBTEfuseModifiedMap[EFUSE_BT_MAX_MAP_LEN] = {0}; #define REG_EFUSE_CTRL 0x0030 #define EFUSE_CTRL REG_EFUSE_CTRL /* E-Fuse Control. */ -static bool -Efuse_Read1ByteFromFakeContent(u16 Offset, u8 *Value) -{ - if (Offset >= EFUSE_MAX_HW_SIZE) - return false; - if (fakeEfuseBank == 0) - *Value = fakeEfuseContent[Offset]; - else - *Value = fakeBTEfuseContent[fakeEfuseBank - 1][Offset]; - return true; -} - -static bool -Efuse_Write1ByteToFakeContent(u16 Offset, u8 Value) -{ - if (Offset >= EFUSE_MAX_HW_SIZE) - return false; - if (fakeEfuseBank == 0) - fakeEfuseContent[Offset] = Value; - else - fakeBTEfuseContent[fakeEfuseBank - 1][Offset] = Value; - return true; -} - -/*----------------------------------------------------------------------------- - * Function: Efuse_PowerSwitch - * - * Overview: When we want to enable write operation, we should change to - * pwr on state. When we stop write, we should switch to 500k mode - * and disable LDO 2.5V. - * - * Input: NONE - * - * Output: NONE - * - * Return: NONE - * - * Revised History: - * When Who Remark - * 11/17/2008 MHC Create Version 0. - * - */ -void -Efuse_PowerSwitch( -struct adapter *padapter, -u8 bWrite, -u8 PwrState) -{ - Hal_EfusePowerSwitch(padapter, bWrite, PwrState); -} - /* 11/16/2008 MH Add description. Get current efuse area enabled word!!. */ u8 Efuse_CalculateWordCnts(u8 word_en) @@ -97,58 +46,6 @@ Efuse_CalculateWordCnts(u8 word_en) return word_cnts; } -/* */ -/* Description: */ -/* 1. Execute E-Fuse read byte operation according as map offset and */ -/* save to E-Fuse table. */ -/* 2. Referred from SD1 Richard. */ -/* */ -/* Assumption: */ -/* 1. Boot from E-Fuse and successfully auto-load. */ -/* 2. PASSIVE_LEVEL (USB interface) */ -/* */ -/* Created by Roger, 2008.10.21. */ -/* */ -/* 2008/12/12 MH 1. Reorganize code flow and reserve bytes. and add description. */ -/* 2. Add efuse utilization collect. */ -/* 2008/12/22 MH Read Efuse must check if we write section 1 data again!!! Sec1 */ -/* write addr must be after sec5. */ -/* */ - -void -efuse_ReadEFuse( - struct adapter *Adapter, - u8 efuseType, - u16 _offset, - u16 _size_byte, - u8 *pbuf, -bool bPseudoTest - ); -void -efuse_ReadEFuse( - struct adapter *Adapter, - u8 efuseType, - u16 _offset, - u16 _size_byte, - u8 *pbuf, -bool bPseudoTest - ) -{ - Hal_ReadEFuse(Adapter, efuseType, _offset, _size_byte, pbuf, bPseudoTest); -} - -void -EFUSE_GetEfuseDefinition( - struct adapter *padapter, - u8 efuseType, - u8 type, - void *pOut, - bool bPseudoTest - ) -{ - Hal_GetEfuseDefinition(padapter, efuseType, type, pOut, bPseudoTest); -} - /*----------------------------------------------------------------------------- * Function: EFUSE_Read1Byte * @@ -175,7 +72,7 @@ u16 Address) u32 k = 0; u16 contentLen = 0; - EFUSE_GetEfuseDefinition(Adapter, EFUSE_WIFI, TYPE_EFUSE_REAL_CONTENT_LEN, (void *)&contentLen, false); + Hal_GetEfuseDefinition(Adapter, EFUSE_WIFI, TYPE_EFUSE_REAL_CONTENT_LEN, (void *)&contentLen); if (Address < contentLen) {/* E-fuse 512Byte */ /* Write E-fuse Register address bit0~7 */ @@ -210,16 +107,12 @@ u8 efuse_OneByteRead( struct adapter *padapter, u16 addr, -u8 *data, -bool bPseudoTest) +u8 *data) { u32 tmpidx = 0; u8 bResult; u8 readbyte; - if (bPseudoTest) - return Efuse_Read1ByteFromFakeContent(addr, data); - /* <20130121, Kordan> For SMIC EFUSE specificatoin. */ /* 0x34[11]: SW force PGMEN input of efuse to high. (for the bank selected by 0x34[9:8]) */ /* PHY_SetMacReg(padapter, 0x34, BIT11, 0); */ @@ -251,42 +144,6 @@ bool bPseudoTest) return bResult; } -/* 11/16/2008 MH Write one byte to reald Efuse. */ -u8 efuse_OneByteWrite(struct adapter *padapter, u16 addr, u8 data, bool bPseudoTest) -{ - u8 tmpidx = 0; - u8 bResult = false; - - if (bPseudoTest) - return Efuse_Write1ByteToFakeContent(addr, data); - - /* -----------------e-fuse reg ctrl --------------------------------- */ - /* address */ - - /* <20130227, Kordan> 8192E MP chip A-cut had better not set 0x34[11] until B-Cut. */ - - /* <20130121, Kordan> For SMIC EFUSE specificatoin. */ - /* 0x34[11]: SW force PGMEN input of efuse to high. (for the bank selected by 0x34[9:8]) */ - /* PHY_SetMacReg(padapter, 0x34, BIT11, 1); */ - rtw_write16(padapter, 0x34, rtw_read16(padapter, 0x34) | (BIT11)); - rtw_write32(padapter, EFUSE_CTRL, 0x90600000 | ((addr << 8 | data))); - - while ((0x80 & rtw_read8(padapter, EFUSE_CTRL + 3)) && (tmpidx < 100)) { - mdelay(1); - tmpidx++; - } - - if (tmpidx < 100) - bResult = true; - else - bResult = false; - - /* disable Efuse program enable */ - PHY_SetMacReg(padapter, EFUSE_TEST, BIT(11), 0); - - return bResult; -} - /*----------------------------------------------------------------------------- * Function: Efuse_ReadAllMap * @@ -303,23 +160,17 @@ u8 efuse_OneByteWrite(struct adapter *padapter, u16 addr, u8 data, bool bPseudoT * 11/11/2008 MHC Create Version 0. * */ -void -Efuse_ReadAllMap( - struct adapter *padapter, - u8 efuseType, - u8 *Efuse, - bool bPseudoTest); -void Efuse_ReadAllMap(struct adapter *padapter, u8 efuseType, u8 *Efuse, bool bPseudoTest) +static void Efuse_ReadAllMap(struct adapter *padapter, u8 efuseType, u8 *Efuse) { u16 mapLen = 0; - Efuse_PowerSwitch(padapter, false, true); + Hal_EfusePowerSwitch(padapter, true); - EFUSE_GetEfuseDefinition(padapter, efuseType, TYPE_EFUSE_MAP_LEN, (void *)&mapLen, bPseudoTest); + Hal_GetEfuseDefinition(padapter, efuseType, TYPE_EFUSE_MAP_LEN, (void *)&mapLen); - efuse_ReadEFuse(padapter, efuseType, 0, mapLen, Efuse, bPseudoTest); + Hal_ReadEFuse(padapter, efuseType, 0, mapLen, Efuse); - Efuse_PowerSwitch(padapter, false, false); + Hal_EfusePowerSwitch(padapter, false); } /*----------------------------------------------------------------------------- @@ -386,17 +237,17 @@ static void efuse_ShadowRead4Byte(struct adapter *padapter, u16 Offset, u32 *Val * 11/13/2008 MHC Create Version 0. * */ -void EFUSE_ShadowMapUpdate(struct adapter *padapter, u8 efuseType, bool bPseudoTest) +void EFUSE_ShadowMapUpdate(struct adapter *padapter, u8 efuseType) { struct eeprom_priv *pEEPROM = GET_EEPROM_EFUSE_PRIV(padapter); u16 mapLen = 0; - EFUSE_GetEfuseDefinition(padapter, efuseType, TYPE_EFUSE_MAP_LEN, (void *)&mapLen, bPseudoTest); + Hal_GetEfuseDefinition(padapter, efuseType, TYPE_EFUSE_MAP_LEN, (void *)&mapLen); if (pEEPROM->bautoload_fail_flag) memset(pEEPROM->efuse_eeprom_data, 0xFF, mapLen); else - Efuse_ReadAllMap(padapter, efuseType, pEEPROM->efuse_eeprom_data, bPseudoTest); + Efuse_ReadAllMap(padapter, efuseType, pEEPROM->efuse_eeprom_data); /* PlatformMoveMemory((void *)&pHalData->EfuseMap[EFUSE_MODIFY_MAP][0], */ /* void *)&pHalData->EfuseMap[EFUSE_INIT_MAP][0], mapLen); */ diff --git a/drivers/staging/rtl8723bs/core/rtw_mlme.c b/drivers/staging/rtl8723bs/core/rtw_mlme.c index 692d0c2b766d..c06d990350e6 100644 --- a/drivers/staging/rtl8723bs/core/rtw_mlme.c +++ b/drivers/staging/rtl8723bs/core/rtw_mlme.c @@ -9,6 +9,36 @@ #include <hal_btcoex.h> #include <linux/jiffies.h> +static void _dynamic_check_timer_handler(struct timer_list *t) +{ + struct adapter *adapter = + timer_container_of(adapter, t, mlmepriv.dynamic_chk_timer); + + rtw_dynamic_check_timer_handler(adapter); + + _set_timer(&adapter->mlmepriv.dynamic_chk_timer, 2000); +} + +static void _rtw_set_scan_deny_timer_hdl(struct timer_list *t) +{ + struct adapter *adapter = + timer_container_of(adapter, t, mlmepriv.set_scan_deny_timer); + + rtw_clear_scan_deny(adapter); +} + +static void rtw_init_mlme_timer(struct adapter *padapter) +{ + struct mlme_priv *pmlmepriv = &padapter->mlmepriv; + + timer_setup(&pmlmepriv->assoc_timer, _rtw_join_timeout_handler, 0); + timer_setup(&pmlmepriv->scan_to_timer, rtw_scan_timeout_handler, 0); + timer_setup(&pmlmepriv->dynamic_chk_timer, + _dynamic_check_timer_handler, 0); + timer_setup(&pmlmepriv->set_scan_deny_timer, + _rtw_set_scan_deny_timer_hdl, 0); +} + int rtw_init_mlme_priv(struct adapter *padapter) { int i; @@ -170,7 +200,6 @@ void _rtw_free_network(struct mlme_priv *pmlmepriv, struct wlan_network *pnetwor void _rtw_free_network_nolock(struct mlme_priv *pmlmepriv, struct wlan_network *pnetwork) { - struct __queue *free_queue = &pmlmepriv->free_bss_pool; if (!pnetwork) @@ -225,11 +254,9 @@ void rtw_free_network_queue(struct adapter *padapter, u8 isfreeall) phead = get_list_head(scanned_queue); list_for_each_safe(plist, tmp, phead) { - pnetwork = list_entry(plist, struct wlan_network, list); _rtw_free_network(pmlmepriv, pnetwork, isfreeall); - } spin_unlock_bh(&scanned_queue->lock); @@ -318,7 +345,6 @@ int rtw_is_same_ibss(struct adapter *adapter, struct wlan_network *pnetwork) ret = true; return ret; - } inline int is_same_ess(struct wlan_bssid_ex *a, struct wlan_bssid_ex *b) @@ -348,7 +374,6 @@ int is_same_network(struct wlan_bssid_ex *src, struct wlan_bssid_ex *dst, u8 fea (d_cap & WLAN_CAPABILITY_IBSS)) && ((s_cap & WLAN_CAPABILITY_ESS) == (d_cap & WLAN_CAPABILITY_ESS)); - } struct wlan_network *_rtw_find_same_network(struct __queue *scanned_queue, struct wlan_network *network) @@ -380,7 +405,6 @@ struct wlan_network *rtw_get_oldest_wlan_network(struct __queue *scanned_queue) phead = get_list_head(scanned_queue); list_for_each(plist, phead) { - pwlan = list_entry(plist, struct wlan_network, list); if (!pwlan->fixed) { @@ -389,7 +413,6 @@ struct wlan_network *rtw_get_oldest_wlan_network(struct __queue *scanned_queue) } } return oldest; - } void update_network(struct wlan_bssid_ex *dst, struct wlan_bssid_ex *src, @@ -424,7 +447,6 @@ void update_network(struct wlan_bssid_ex *dst, struct wlan_bssid_ex *src, sq_final = dst->phy_info.signal_quality; rssi_final = dst->rssi; } - } if (update_ie) { @@ -486,7 +508,6 @@ void rtw_update_scanned_network(struct adapter *adapter, struct wlan_bssid_ex *t if (!oldest || time_after(oldest->last_scanned, pnetwork->last_scanned)) oldest = pnetwork; - } /* If we didn't find a match, then get a new network slot to initialize @@ -530,7 +551,6 @@ void rtw_update_scanned_network(struct adapter *adapter, struct wlan_bssid_ex *t pnetwork->network.phy_info.signal_quality = 0; list_add_tail(&pnetwork->list, &queue->queue); - } } else { /* we have an entry and we are going to update it. But this entry may @@ -567,12 +587,14 @@ void rtw_add_network(struct adapter *adapter, struct wlan_bssid_ex *pnetwork) rtw_update_scanned_network(adapter, pnetwork); } -/* select the desired network based on the capability of the (i)bss. */ -/* check items: (1) security */ -/* (2) network_type */ -/* (3) WMM */ -/* (4) HT */ -/* (5) others */ +/* select the desired network based on the capability of the (i)bss. + * check items: + * (1) security + * (2) network_type + * (3) WMM + * (4) HT + * (5) others + */ int rtw_is_desired_network(struct adapter *adapter, struct wlan_network *pnetwork); int rtw_is_desired_network(struct adapter *adapter, struct wlan_network *pnetwork) { @@ -591,7 +613,6 @@ int rtw_is_desired_network(struct adapter *adapter, struct wlan_network *pnetwor return true; else return false; - } if (adapter->registrypriv.wifi_spec == 1) { /* for correct flow of 8021X to do.... */ u8 *p = NULL; @@ -868,15 +889,23 @@ void rtw_indicate_connect(struct adapter *padapter) pmlmepriv->to_join = false; if (!check_fwstate(&padapter->mlmepriv, _FW_LINKED)) { - set_fwstate(pmlmepriv, _FW_LINKED); - rtw_os_indicate_connect(padapter); + if (check_fwstate(pmlmepriv, WIFI_ADHOC_MASTER_STATE) || + check_fwstate(pmlmepriv, WIFI_ADHOC_STATE)) { + rtw_cfg80211_ibss_indicate_connect(padapter); + } else { + rtw_cfg80211_indicate_connect(padapter); + } + + netif_carrier_on(padapter->pnetdev); + + if (padapter->pid[2] != 0) + rtw_signal_process(padapter->pid[2], SIGALRM); } rtw_set_to_roam(padapter, 0); rtw_set_scan_deny(padapter, 3000); - } /* @@ -891,10 +920,14 @@ void rtw_indicate_disconnect(struct adapter *padapter) if (rtw_to_roam(padapter) > 0) _clr_fwstate_(pmlmepriv, _FW_LINKED); - if (check_fwstate(&padapter->mlmepriv, _FW_LINKED) - || (rtw_to_roam(padapter) <= 0) - ) { - rtw_os_indicate_disconnect(padapter); + if (check_fwstate(&padapter->mlmepriv, _FW_LINKED) || rtw_to_roam(padapter) <= 0) { + /* Do it first for tx broadcast pkt after disconnection issue! */ + netif_carrier_off(padapter->pnetdev); + + rtw_cfg80211_indicate_disconnect(padapter); + + /* modify for CONFIG_IEEE80211W, none 11w also can use the same command */ + rtw_reset_securitypriv_cmd(padapter); /* set ips_deny_time to avoid enter IPS before LPS leave */ rtw_set_ips_deny(padapter, 3000); @@ -909,7 +942,7 @@ void rtw_indicate_disconnect(struct adapter *padapter) inline void rtw_indicate_scan_done(struct adapter *padapter, bool aborted) { - rtw_os_indicate_scan_done(padapter, aborted); + rtw_cfg80211_indicate_scan_done(padapter, aborted); if ((!adapter_to_pwrctl(padapter)->bInSuspend) && (!check_fwstate(&padapter->mlmepriv, @@ -929,7 +962,6 @@ void rtw_scan_abort(struct adapter *adapter) pmlmeext->scan_abort = true; while (check_fwstate(pmlmepriv, _FW_UNDER_SURVEY) && jiffies_to_msecs(start) <= 200) { - if (adapter->bDriverStopped || adapter->bSurpriseRemoved) break; @@ -1022,7 +1054,6 @@ static struct sta_info *rtw_joinbss_update_stainfo(struct adapter *padapter, str } return psta; - } /* pnetwork : returns from rtw_joinbss_event_callback */ @@ -1073,6 +1104,66 @@ static void rtw_joinbss_update_network(struct adapter *padapter, struct wlan_net rtw_update_ht_cap(padapter, cur_network->network.ies, cur_network->network.ie_length, (u8) cur_network->network.configuration.ds_config); } +static struct rt_pmkid_list backupPMKIDList[NUM_PMKID_CACHE]; +void rtw_reset_securitypriv(struct adapter *adapter) +{ + u8 backupPMKIDIndex = 0; + u8 backupTKIPCountermeasure = 0x00; + u32 backupTKIPcountermeasure_time = 0; + /* add for CONFIG_IEEE80211W, none 11w also can use */ + struct mlme_ext_priv *pmlmeext = &adapter->mlmeextpriv; + + spin_lock_bh(&adapter->security_key_mutex); + + if (adapter->securitypriv.dot11AuthAlgrthm == dot11AuthAlgrthm_8021X) { + /* 802.1x */ + /* Added by Albert 2009/02/18 */ + /* We have to backup the PMK information for WiFi PMK Caching test item. */ + /* */ + /* Backup the btkip_countermeasure information. */ + /* When the countermeasure is trigger, the driver have to disconnect with AP for 60 seconds. */ + + memcpy(&backupPMKIDList[0], &adapter->securitypriv.PMKIDList[0], sizeof(struct rt_pmkid_list) * NUM_PMKID_CACHE); + backupPMKIDIndex = adapter->securitypriv.PMKIDIndex; + backupTKIPCountermeasure = adapter->securitypriv.btkip_countermeasure; + backupTKIPcountermeasure_time = adapter->securitypriv.btkip_countermeasure_time; + + /* reset RX BIP packet number */ + pmlmeext->mgnt_80211w_IPN_rx = 0; + + memset((unsigned char *)&adapter->securitypriv, 0, sizeof(struct security_priv)); + + /* Added by Albert 2009/02/18 */ + /* Restore the PMK information to securitypriv structure for the following connection. */ + memcpy(&adapter->securitypriv.PMKIDList[0], &backupPMKIDList[0], sizeof(struct rt_pmkid_list) * NUM_PMKID_CACHE); + adapter->securitypriv.PMKIDIndex = backupPMKIDIndex; + adapter->securitypriv.btkip_countermeasure = backupTKIPCountermeasure; + adapter->securitypriv.btkip_countermeasure_time = backupTKIPcountermeasure_time; + + adapter->securitypriv.ndisauthtype = Ndis802_11AuthModeOpen; + adapter->securitypriv.ndisencryptstatus = Ndis802_11WEPDisabled; + + } else { + /* reset values in securitypriv */ + /* if (adapter->mlmepriv.fw_state & WIFI_STATION_STATE) */ + /* */ + struct security_priv *psec_priv = &adapter->securitypriv; + + psec_priv->dot11AuthAlgrthm = dot11AuthAlgrthm_Open; /* open system */ + psec_priv->dot11PrivacyAlgrthm = _NO_PRIVACY_; + psec_priv->dot11PrivacyKeyIndex = 0; + + psec_priv->dot118021XGrpPrivacy = _NO_PRIVACY_; + psec_priv->dot118021XGrpKeyid = 1; + + psec_priv->ndisauthtype = Ndis802_11AuthModeOpen; + psec_priv->ndisencryptstatus = Ndis802_11WEPDisabled; + /* */ + } + /* add for CONFIG_IEEE80211W, none 11w also can use */ + spin_unlock_bh(&adapter->security_key_mutex); +} + /* Notes: the function could be > passive_level (the same context as Rx tasklet) */ /* pnetwork : returns from rtw_joinbss_event_callback */ /* ptarget_wlan: found from scanned_queue */ @@ -1397,11 +1488,11 @@ void rtw_stadel_event_callback(struct adapter *adapter, u8 *pbuf) if (check_fwstate(pmlmepriv, WIFI_ADHOC_MASTER_STATE) || check_fwstate(pmlmepriv, WIFI_ADHOC_STATE)) { - rtw_free_stainfo(adapter, psta); if (adapter->stapriv.asoc_sta_count == 1) {/* a sta + bc/mc_stainfo (not Ibss_stainfo) */ u8 ret = _SUCCESS; + spin_lock_bh(&pmlmepriv->scanned_queue.lock); /* free old ibss network */ pwlan = rtw_find_network(&pmlmepriv->scanned_queue, tgt_network->network.mac_address); @@ -1431,7 +1522,6 @@ void rtw_stadel_event_callback(struct adapter *adapter, u8 *pbuf) if (ret != _SUCCESS) goto unlock; } - } unlock: @@ -1490,7 +1580,6 @@ void _rtw_join_timeout_handler(struct timer_list *t) /* indicate disconnect for the case that join_timeout and check_fwstate != FW_LINKED */ rtw_cfg80211_indicate_disconnect(adapter); - } spin_unlock_bh(&pmlmepriv->lock); @@ -1540,7 +1629,6 @@ static void rtw_auto_scan_handler(struct adapter *padapter) if (pmlmepriv->auto_scan_int_ms != 0 && jiffies_to_msecs(jiffies - pmlmepriv->scan_start_time) > pmlmepriv->auto_scan_int_ms) { - if (!padapter->registrypriv.wifi_spec) { if (check_fwstate(pmlmepriv, _FW_UNDER_SURVEY | _FW_UNDER_LINKING) == true) goto exit; @@ -1673,12 +1761,10 @@ int rtw_select_roaming_candidate(struct mlme_priv *mlme) phead = get_list_head(queue); list_for_each(mlme->pscanned, phead) { - pnetwork = list_entry(mlme->pscanned, struct wlan_network, list); rtw_check_roaming_candidate(mlme, &candidate, pnetwork); - } if (!candidate) { @@ -1770,12 +1856,10 @@ int rtw_select_and_join_from_scanned_queue(struct mlme_priv *pmlmepriv) phead = get_list_head(queue); list_for_each(pmlmepriv->pscanned, phead) { - pnetwork = list_entry(pmlmepriv->pscanned, struct wlan_network, list); rtw_check_join_candidate(pmlmepriv, &candidate, pnetwork); - } if (!candidate) { @@ -1841,9 +1925,9 @@ exit: signed int rtw_set_key(struct adapter *adapter, struct security_priv *psecuritypriv, signed int keyid, u8 set_tx, bool enqueue) { u8 keylen; - struct cmd_obj *pcmd; + struct cmd_obj *pcmd; struct setkey_parm *psetkeyparm; - struct cmd_priv *pcmdpriv = &adapter->cmdpriv; + struct cmd_priv *pcmdpriv = &adapter->cmdpriv; signed int res = _SUCCESS; psetkeyparm = rtw_zmalloc(sizeof(struct setkey_parm)); @@ -1863,7 +1947,6 @@ signed int rtw_set_key(struct adapter *adapter, struct security_priv *psecurityp adapter->securitypriv.key_mask |= BIT(psetkeyparm->keyid); switch (psetkeyparm->algorithm) { - case _WEP40_: keylen = 5; memcpy(&psetkeyparm->key[0], &psecuritypriv->dot11DefKey[keyid].skey[0], keylen); @@ -1939,20 +2022,18 @@ int rtw_restruct_wmm_ie(struct adapter *adapter, u8 *in_ie, u8 *out_ie, uint in_ } return ielength; - } -/* */ -/* Ported from 8185: IsInPreAuthKeyList(). (Renamed from SecIsInPreAuthKeyList(), 2006-10-13.) */ -/* Added by Annie, 2006-05-07. */ -/* */ -/* Search by BSSID, */ -/* Return Value: */ -/* -1 :if there is no pre-auth key in the table */ -/* >= 0 :if there is pre-auth key, and return the entry id */ -/* */ -/* */ - +/* Ported from 8185: IsInPreAuthKeyList(). + * (Renamed from SecIsInPreAuthKeyList(), 2006-10-13.) + * Added by Annie, 2006-05-07. + * + * Search by BSSID, + * + * Return Value: + * -1: if there is no pre-auth key in the table + * >=0: if there is pre-auth key, and return the entry id + */ static int SecIsInPMKIDList(struct adapter *Adapter, u8 *bssid) { struct security_priv *p = &Adapter->securitypriv; @@ -1990,6 +2071,40 @@ static int rtw_append_pmkid(struct adapter *Adapter, int iEntry, u8 *ie, uint ie return ie_len; } +static void rtw_report_sec_ie(struct adapter *adapter, u8 authmode, u8 *sec_ie) +{ + uint len; + u8 *buff, *p, i; + union iwreq_data wrqu; + + buff = NULL; + if (authmode == WLAN_EID_VENDOR_SPECIFIC) { + buff = rtw_zmalloc(IW_CUSTOM_MAX); + if (!buff) + return; + + p = buff; + + p += scnprintf(p, IW_CUSTOM_MAX - (p - buff), "ASSOCINFO(ReqIEs ="); + + len = sec_ie[1] + 2; + len = (len < IW_CUSTOM_MAX) ? len : IW_CUSTOM_MAX; + + for (i = 0; i < len; i++) + p += scnprintf(p, IW_CUSTOM_MAX - (p - buff), "%02x", sec_ie[i]); + + p += scnprintf(p, IW_CUSTOM_MAX - (p - buff), ")"); + + memset(&wrqu, 0, sizeof(wrqu)); + + wrqu.data.length = p - buff; + + wrqu.data.length = (wrqu.data.length < IW_CUSTOM_MAX) ? wrqu.data.length : IW_CUSTOM_MAX; + + kfree(buff); + } +} + signed int rtw_restruct_sec_ie(struct adapter *adapter, u8 *in_ie, u8 *out_ie, uint in_len) { u8 authmode = 0x0; @@ -2099,7 +2214,7 @@ void rtw_joinbss_reset(struct adapter *padapter) u8 threshold; struct mlme_priv *pmlmepriv = &padapter->mlmepriv; - struct ht_priv *phtpriv = &pmlmepriv->htpriv; + struct ht_priv *phtpriv = &pmlmepriv->htpriv; /* todo: if you want to do something io/reg/hw setting before join_bss, please add code here */ @@ -2125,8 +2240,8 @@ void rtw_joinbss_reset(struct adapter *padapter) void rtw_ht_use_default_setting(struct adapter *padapter) { - struct mlme_priv *pmlmepriv = &padapter->mlmepriv; - struct ht_priv *phtpriv = &pmlmepriv->htpriv; + struct mlme_priv *pmlmepriv = &padapter->mlmepriv; + struct ht_priv *phtpriv = &pmlmepriv->htpriv; struct registry_priv *pregistrypriv = &padapter->registrypriv; bool bHwLDPCSupport = false, bHwSTBCSupport = false; bool bHwSupportBeamformer = false, bHwSupportBeamformee = false; @@ -2200,7 +2315,7 @@ unsigned int rtw_restructure_ht_ie(struct adapter *padapter, u8 *in_ie, u8 *out_ u8 cbw40_enable = 0, stbc_rx_enable = 0, operation_bw = 0; struct registry_priv *pregistrypriv = &padapter->registrypriv; struct mlme_priv *pmlmepriv = &padapter->mlmepriv; - struct ht_priv *phtpriv = &pmlmepriv->htpriv; + struct ht_priv *phtpriv = &pmlmepriv->htpriv; struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; phtpriv->ht_option = false; @@ -2311,7 +2426,6 @@ unsigned int rtw_restructure_ht_ie(struct adapter *padapter, u8 *in_ie, u8 *out_ } return phtpriv->ht_option; - } /* the function is > passive_level (in critical_section) */ @@ -2321,7 +2435,7 @@ void rtw_update_ht_cap(struct adapter *padapter, u8 *pie, uint ie_len, u8 channe int len; struct ieee80211_ht_cap *pht_capie; struct mlme_priv *pmlmepriv = &padapter->mlmepriv; - struct ht_priv *phtpriv = &pmlmepriv->htpriv; + struct ht_priv *phtpriv = &pmlmepriv->htpriv; struct registry_priv *pregistrypriv = &padapter->registrypriv; struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct mlme_ext_info *pmlmeinfo = &pmlmeext->mlmext_info; @@ -2346,7 +2460,6 @@ void rtw_update_ht_cap(struct adapter *padapter, u8 *pie, uint ie_len, u8 channe max_ampdu_sz = 1 << (max_ampdu_sz+3); /* max_ampdu_sz (kbytes); */ phtpriv->rx_ampdu_maxlen = max_ampdu_sz; - } len = 0; @@ -2437,13 +2550,12 @@ void rtw_issue_addbareq_cmd(struct adapter *padapter, struct xmit_frame *pxmitfr rtw_addbareq_cmd(padapter, (u8) priority, pattrib->ra); } } - } void rtw_append_exented_cap(struct adapter *padapter, u8 *out_ie, uint *pout_len) { struct mlme_priv *pmlmepriv = &padapter->mlmepriv; - struct ht_priv *phtpriv = &pmlmepriv->htpriv; + struct ht_priv *phtpriv = &pmlmepriv->htpriv; u8 cap_content[8] = {0}; if (phtpriv->bss_coexist) @@ -2478,6 +2590,7 @@ void rtw_roaming(struct adapter *padapter, struct wlan_network *tgt_network) _rtw_roaming(padapter, tgt_network); spin_unlock_bh(&pmlmepriv->lock); } + void _rtw_roaming(struct adapter *padapter, struct wlan_network *tgt_network) { struct mlme_priv *pmlmepriv = &padapter->mlmepriv; diff --git a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c index bc980d21d50e..a897c433d2b0 100644 --- a/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c +++ b/drivers/staging/rtl8723bs/core/rtw_mlme_ext.c @@ -374,6 +374,15 @@ static u8 init_channel_set(struct adapter *padapter, u8 ChannelPlan, struct rt_c return chanset_size; } +static void init_mlme_ext_timer(struct adapter *padapter) +{ + struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; + + timer_setup(&pmlmeext->survey_timer, survey_timer_hdl, 0); + timer_setup(&pmlmeext->link_timer, link_timer_hdl, 0); + timer_setup(&pmlmeext->sa_query_timer, sa_query_timer_hdl, 0); +} + void init_mlme_ext_priv(struct adapter *padapter) { struct registry_priv *pregistrypriv = &padapter->registrypriv; @@ -937,10 +946,10 @@ unsigned int OnAssocReq(struct adapter *padapter, union recv_frame *precv_frame) u16 capab_info; struct rtw_ieee802_11_elems elems; struct sta_info *pstat; - unsigned char *p, *pos, *wpa_ie; + unsigned char *p, *pos, *wpa_ie; unsigned char WMM_IE[] = {0x00, 0x50, 0xf2, 0x02, 0x00, 0x01}; int i, ie_len, wpa_ie_len, left; - unsigned char supportRate[16]; + unsigned char supportRate[16]; int supportRateNum; unsigned short status = WLAN_STATUS_SUCCESS; unsigned short frame_type, ie_offset = 0; @@ -1122,9 +1131,6 @@ unsigned int OnAssocReq(struct adapter *padapter, union recv_frame *precv_frame) if (!wpa_ie) { if (elems.wps_ie) { pstat->flags |= WLAN_STA_WPS; - /* wpabuf_free(sta->wps_ie); */ - /* sta->wps_ie = wpabuf_alloc_copy(elems.wps_ie + 4, */ - /* elems.wps_ie_len - 4); */ } else { pstat->flags |= WLAN_STA_MAYBE_WPS; } @@ -1502,11 +1508,12 @@ unsigned int OnDeAuth(struct adapter *padapter, union recv_frame *precv_frame) return _SUCCESS; } - /* Commented by Albert 20130604 */ - /* Before sending the auth frame to start the STA/GC mode connection with AP/GO, */ - /* we will send the deauth first. */ - /* However, the Win8.1 with BRCM Wi-Fi will send the deauth with reason code 6 to us after receieving our deauth. */ - /* Added the following code to avoid this case. */ + /* Commented by Albert 20130604 + * Before sending the auth frame to start the STA/GC mode connection with AP/GO, + * we will send the deauth first. + * However, the Win8.1 with BRCM Wi-Fi will send the deauth with reason code 6 to us after receieving our deauth. + * Added the following code to avoid this case. + */ if ((pmlmeinfo->state & WIFI_FW_AUTH_STATE) || (pmlmeinfo->state & WIFI_FW_ASSOC_STATE)) { if (reason == WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA) { @@ -1626,8 +1633,8 @@ unsigned int OnAction_back(struct adapter *padapter, union recv_frame *precv_fra u8 *addr; struct sta_info *psta = NULL; struct recv_reorder_ctrl *preorder_ctrl; - unsigned char *frame_body; - unsigned char category, action; + unsigned char *frame_body; + unsigned char category, action; unsigned short tid, status; struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); @@ -2259,10 +2266,10 @@ void issue_probersp(struct adapter *padapter, unsigned char *da, u8 is_valid_p2p { struct xmit_frame *pmgntframe; struct pkt_attrib *pattrib; - unsigned char *pframe; + unsigned char *pframe; struct ieee80211_hdr *pwlanhdr; __le16 *fctrl; - unsigned char *mac, *bssid; + unsigned char *mac, *bssid; struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); u8 *pwps_ie; @@ -2456,12 +2463,12 @@ static int _issue_probereq(struct adapter *padapter, int ret = _FAIL; struct xmit_frame *pmgntframe; struct pkt_attrib *pattrib; - unsigned char *pframe; + unsigned char *pframe; struct ieee80211_hdr *pwlanhdr; __le16 *fctrl; - unsigned char *mac; - unsigned char bssrate[NumRates]; - struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); + unsigned char *mac; + unsigned char bssrate[NumRates]; + struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); int bssrate_len = 0; @@ -2486,11 +2493,11 @@ static int _issue_probereq(struct adapter *padapter, *(fctrl) = 0; if (da) { - /* unicast probe request frame */ + /* unicast probe request frame */ memcpy(pwlanhdr->addr1, da, ETH_ALEN); memcpy(pwlanhdr->addr3, da, ETH_ALEN); } else { - /* broadcast probe request frame */ + /* broadcast probe request frame */ eth_broadcast_addr(pwlanhdr->addr1); eth_broadcast_addr(pwlanhdr->addr3); } @@ -2584,13 +2591,13 @@ void issue_auth(struct adapter *padapter, struct sta_info *psta, unsigned short { struct xmit_frame *pmgntframe; struct pkt_attrib *pattrib; - unsigned char *pframe; + unsigned char *pframe; struct ieee80211_hdr *pwlanhdr; __le16 *fctrl; unsigned int val32; unsigned short val16; int use_shared_key = 0; - struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); + struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); __le16 le_tmp; @@ -2841,14 +2848,14 @@ void issue_assocreq(struct adapter *padapter) int ret = _FAIL; struct xmit_frame *pmgntframe; struct pkt_attrib *pattrib; - unsigned char *pframe; + unsigned char *pframe; struct ieee80211_hdr *pwlanhdr; __le16 *fctrl; __le16 val16; unsigned int i, j, index = 0; unsigned char bssrate[NumRates], sta_bssrate[NumRates]; struct ndis_80211_var_ie *pIE; - struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); + struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); struct mlme_priv *pmlmepriv = &(padapter->mlmepriv); struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); @@ -3018,7 +3025,7 @@ static int _issue_nulldata(struct adapter *padapter, unsigned char *da, int ret = _FAIL; struct xmit_frame *pmgntframe; struct pkt_attrib *pattrib; - unsigned char *pframe; + unsigned char *pframe; struct ieee80211_hdr *pwlanhdr; __le16 *fctrl; struct xmit_priv *pxmitpriv; @@ -3163,11 +3170,11 @@ static int _issue_qos_nulldata(struct adapter *padapter, unsigned char *da, int ret = _FAIL; struct xmit_frame *pmgntframe; struct pkt_attrib *pattrib; - unsigned char *pframe; + unsigned char *pframe; struct ieee80211_hdr *pwlanhdr; __le16 *fctrl; u16 *qc; - struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); + struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); @@ -3272,10 +3279,10 @@ static int _issue_deauth(struct adapter *padapter, unsigned char *da, { struct xmit_frame *pmgntframe; struct pkt_attrib *pattrib; - unsigned char *pframe; + unsigned char *pframe; struct ieee80211_hdr *pwlanhdr; __le16 *fctrl; - struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); + struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); int ret = _FAIL; @@ -3366,10 +3373,10 @@ void issue_action_SA_Query(struct adapter *padapter, unsigned char *raddr, unsig u8 category = RTW_WLAN_CATEGORY_SA_QUERY; struct xmit_frame *pmgntframe; struct pkt_attrib *pattrib; - u8 *pframe; + u8 *pframe; struct ieee80211_hdr *pwlanhdr; __le16 *fctrl; - struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); + struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); __le16 le_tmp; @@ -3439,15 +3446,15 @@ void issue_action_BA(struct adapter *padapter, unsigned char *raddr, unsigned ch enum ieee80211_max_ampdu_length_exp max_rx_ampdu_factor; struct xmit_frame *pmgntframe; struct pkt_attrib *pattrib; - u8 *pframe; + u8 *pframe; struct ieee80211_hdr *pwlanhdr; __le16 *fctrl; - struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); + struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); - struct sta_info *psta; - struct sta_priv *pstapriv = &padapter->stapriv; - struct registry_priv *pregpriv = &padapter->registrypriv; + struct sta_info *psta; + struct sta_priv *pstapriv = &padapter->stapriv; + struct registry_priv *pregpriv = &padapter->registrypriv; __le16 le_tmp; pmgntframe = alloc_mgtxmitframe(pxmitpriv); @@ -3585,11 +3592,11 @@ static void issue_action_BSSCoexistPacket(struct adapter *padapter) unsigned char category, action; struct xmit_frame *pmgntframe; struct pkt_attrib *pattrib; - unsigned char *pframe; + unsigned char *pframe; struct ieee80211_hdr *pwlanhdr; __le16 *fctrl; struct wlan_network *pnetwork = NULL; - struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); + struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); struct mlme_priv *pmlmepriv = &padapter->mlmepriv; struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv); struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); @@ -3798,7 +3805,7 @@ Following are some utility functions for WiFi MLME void site_survey(struct adapter *padapter) { - unsigned char survey_channel = 0, val8; + unsigned char survey_channel = 0, val8; enum rt_scan_type ScanType = SCAN_PASSIVE; struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); @@ -3867,7 +3874,7 @@ void site_survey(struct adapter *padapter) set_survey_timer(pmlmeext, channel_scan_time_ms); } else { - /* channel number is 0 or this channel is not valid. */ + /* channel number is 0 or this channel is not valid. */ { pmlmeext->sitesurvey_res.state = SCAN_COMPLETE; @@ -4144,12 +4151,13 @@ void start_clnt_join(struct adapter *padapter) rtw_hal_set_hwreg(padapter, HW_VAR_SEC_CFG, (u8 *)(&val8)); - /* Because of AP's not receiving deauth before */ - /* AP may: 1)not response auth or 2)deauth us after link is complete */ - /* issue deauth before issuing auth to deal with the situation */ - - /* Commented by Albert 2012/07/21 */ - /* For the Win8 P2P connection, it will be hard to have a successful connection if this Wi-Fi doesn't connect to it. */ + /* Because of AP's not receiving deauth before + * AP may: 1)not response auth or 2)deauth us after link is complete + * issue deauth before issuing auth to deal with the situation + * + * Commented by Albert 2012/07/21 + * For the Win8 P2P connection, it will be hard to have a successful connection if this Wi-Fi doesn't connect to it. + */ { /* To avoid connecting to AP fail during resume process, change retry count from 5 to 1 */ issue_deauth_ex(padapter, pnetwork->mac_address, WLAN_REASON_DEAUTH_LEAVING, 1, 100); @@ -4322,7 +4330,6 @@ static void process_80211d(struct adapter *padapter, struct wlan_bssid_ex *bssid k++; } else if (chplan_sta[i].ChannelNum < chplan_ap.Channel[j]) { chplan_new[k].ChannelNum = chplan_sta[i].ChannelNum; -/* chplan_new[k].ScanType = chplan_sta[i].ScanType; */ chplan_new[k].ScanType = SCAN_PASSIVE; i++; k++; @@ -4340,7 +4347,6 @@ static void process_80211d(struct adapter *padapter, struct wlan_bssid_ex *bssid (chplan_sta[i].ChannelNum <= 14)) { chplan_new[k].ChannelNum = chplan_sta[i].ChannelNum; -/* chplan_new[k].ScanType = chplan_sta[i].ScanType; */ chplan_new[k].ScanType = SCAN_PASSIVE; i++; k++; @@ -4460,7 +4466,7 @@ void report_surveydone_event(struct adapter *padapter) u32 cmdsz; struct surveydone_event *psurveydone_evt; struct C2HEvent_Header *pc2h_evt_hdr; - struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; + struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct cmd_priv *pcmdpriv = &padapter->cmdpriv; pcmd_obj = rtw_zmalloc(sizeof(struct cmd_obj)); @@ -4504,7 +4510,7 @@ void report_join_res(struct adapter *padapter, int res) u32 cmdsz; struct joinbss_event *pjoinbss_evt; struct C2HEvent_Header *pc2h_evt_hdr; - struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; + struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); struct cmd_priv *pcmdpriv = &padapter->cmdpriv; @@ -4554,7 +4560,7 @@ void report_wmm_edca_update(struct adapter *padapter) u32 cmdsz; struct wmm_event *pwmm_event; struct C2HEvent_Header *pc2h_evt_hdr; - struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; + struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct cmd_priv *pcmdpriv = &padapter->cmdpriv; pcmd_obj = rtw_zmalloc(sizeof(struct cmd_obj)); @@ -4600,7 +4606,7 @@ void report_del_sta_event(struct adapter *padapter, unsigned char *MacAddr, unsi int mac_id; struct stadel_event *pdel_sta_evt; struct C2HEvent_Header *pc2h_evt_hdr; - struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; + struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct cmd_priv *pcmdpriv = &padapter->cmdpriv; pcmd_obj = rtw_zmalloc(sizeof(struct cmd_obj)); @@ -4651,7 +4657,7 @@ void report_add_sta_event(struct adapter *padapter, unsigned char *MacAddr, int u32 cmdsz; struct stassoc_event *padd_sta_evt; struct C2HEvent_Header *pc2h_evt_hdr; - struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; + struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct cmd_priv *pcmdpriv = &padapter->cmdpriv; pcmd_obj = rtw_zmalloc(sizeof(struct cmd_obj)); @@ -4755,7 +4761,7 @@ void update_sta_info(struct adapter *padapter, struct sta_info *psta) static void rtw_mlmeext_disconnect(struct adapter *padapter) { - struct mlme_priv *pmlmepriv = &padapter->mlmepriv; + struct mlme_priv *pmlmepriv = &padapter->mlmepriv; struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); struct wlan_bssid_ex *pnetwork = (struct wlan_bssid_ex *)(&(pmlmeinfo->network)); @@ -4805,7 +4811,7 @@ void mlmeext_joinbss_event_callback(struct adapter *padapter, int join_res) struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); struct wlan_bssid_ex *cur_network = &(pmlmeinfo->network); - struct sta_priv *pstapriv = &padapter->stapriv; + struct sta_priv *pstapriv = &padapter->stapriv; u8 join_type; struct sta_info *psta; @@ -4983,11 +4989,11 @@ static u8 chk_ap_is_alive(struct adapter *padapter, struct sta_info *psta) void linked_status_chk(struct adapter *padapter) { u32 i; - struct sta_info *psta; - struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); + struct sta_info *psta; + struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); - struct sta_priv *pstapriv = &padapter->stapriv; + struct sta_priv *pstapriv = &padapter->stapriv; if (is_client_associated_to_ap(padapter)) { @@ -5091,8 +5097,8 @@ void survey_timer_hdl(struct timer_list *t) timer_container_of(padapter, t, mlmeextpriv.survey_timer); struct cmd_obj *ph2c; struct sitesurvey_parm *psurveyPara; - struct cmd_priv *pcmdpriv = &padapter->cmdpriv; - struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; + struct cmd_priv *pcmdpriv = &padapter->cmdpriv; + struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; /* issue rtw_sitesurvey_cmd */ if (pmlmeext->sitesurvey_res.state > SCAN_START) { @@ -5124,12 +5130,8 @@ void link_timer_hdl(struct timer_list *t) { struct adapter *padapter = timer_container_of(padapter, t, mlmeextpriv.link_timer); - /* static unsigned int rx_pkt = 0; */ - /* static u64 tx_cnt = 0; */ - /* struct xmit_priv *pxmitpriv = &(padapter->xmitpriv); */ struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); - /* struct sta_priv *pstapriv = &padapter->stapriv; */ if (pmlmeinfo->state & WIFI_FW_AUTH_NULL) { diff --git a/drivers/staging/rtl8723bs/core/rtw_pwrctrl.c b/drivers/staging/rtl8723bs/core/rtw_pwrctrl.c index 6a2583d0d3eb..7b643ac320f0 100644 --- a/drivers/staging/rtl8723bs/core/rtw_pwrctrl.c +++ b/drivers/staging/rtl8723bs/core/rtw_pwrctrl.c @@ -430,10 +430,7 @@ s32 LPS_RF_ON_check(struct adapter *padapter, u32 delay_ms) return err; } -/* */ -/* Description: */ -/* Enter the leisure power save mode. */ -/* */ +/* Description: Enter the leisure power save mode. */ void LPS_Enter(struct adapter *padapter, const char *msg) { struct dvobj_priv *dvobj = adapter_to_dvobj(padapter); @@ -466,10 +463,7 @@ void LPS_Enter(struct adapter *padapter, const char *msg) } } -/* */ -/* Description: */ -/* Leave the leisure power save mode. */ -/* */ +/* Description: Leave the leisure power save mode. */ void LPS_Leave(struct adapter *padapter, const char *msg) { #define LPS_LEAVE_TIMEOUT_MS 100 diff --git a/drivers/staging/rtl8723bs/core/rtw_recv.c b/drivers/staging/rtl8723bs/core/rtw_recv.c index 8ae527b6e0d6..e893cb6fa273 100644 --- a/drivers/staging/rtl8723bs/core/rtw_recv.c +++ b/drivers/staging/rtl8723bs/core/rtw_recv.c @@ -66,7 +66,8 @@ signed int _rtw_init_recv_priv(struct recv_priv *precvpriv, struct adapter *pada list_add_tail(&(precvframe->u.list), &(precvpriv->free_recv_queue.queue)); - rtw_os_recv_resource_alloc(padapter, precvframe); + precvframe->u.hdr.pkt_newalloc = NULL; + precvframe->u.hdr.pkt = NULL; precvframe->u.hdr.len = 0; @@ -90,11 +91,22 @@ exit: void _rtw_free_recv_priv(struct recv_priv *precvpriv) { + signed int i; + union recv_frame *precvframe; struct adapter *padapter = precvpriv->adapter; rtw_free_uc_swdec_pending_queue(padapter); - rtw_os_recv_resource_free(precvpriv); + precvframe = (union recv_frame *)precvpriv->precv_frame_buf; + + for (i = 0; i < NR_RECVFRAME; i++) { + if (precvframe->u.hdr.pkt) { + /* free skb by driver */ + dev_kfree_skb_any(precvframe->u.hdr.pkt); + precvframe->u.hdr.pkt = NULL; + } + precvframe++; + } vfree(precvpriv->pallocated_frame_buf); @@ -147,8 +159,10 @@ int rtw_free_recvframe(union recv_frame *precvframe, struct __queue *pfree_recv_ struct adapter *padapter = precvframe->u.hdr.adapter; struct recv_priv *precvpriv = &padapter->recvpriv; - rtw_os_free_recvframe(precvframe); - + if (precvframe->u.hdr.pkt) { + dev_kfree_skb_any(precvframe->u.hdr.pkt);/* free skb by driver */ + precvframe->u.hdr.pkt = NULL; + } spin_lock_bh(&pfree_recv_queue->lock); @@ -294,6 +308,50 @@ struct recv_buf *rtw_dequeue_recvbuf(struct __queue *queue) } +static void rtw_handle_tkip_mic_err(struct adapter *padapter, u8 bgroup) +{ + enum nl80211_key_type key_type = 0; + union iwreq_data wrqu; + struct iw_michaelmicfailure ev; + struct mlme_priv *pmlmepriv = &padapter->mlmepriv; + struct security_priv *psecuritypriv = &padapter->securitypriv; + unsigned long cur_time = 0; + + if (psecuritypriv->last_mic_err_time == 0) { + psecuritypriv->last_mic_err_time = jiffies; + } else { + cur_time = jiffies; + + if (cur_time - psecuritypriv->last_mic_err_time < 60*HZ) { + psecuritypriv->btkip_countermeasure = true; + psecuritypriv->last_mic_err_time = 0; + psecuritypriv->btkip_countermeasure_time = cur_time; + } else { + psecuritypriv->last_mic_err_time = jiffies; + } + } + + if (bgroup) + key_type |= NL80211_KEYTYPE_GROUP; + else + key_type |= NL80211_KEYTYPE_PAIRWISE; + + cfg80211_michael_mic_failure(padapter->pnetdev, (u8 *)&pmlmepriv->assoc_bssid[0], key_type, -1, + NULL, GFP_ATOMIC); + + memset(&ev, 0x00, sizeof(ev)); + if (bgroup) + ev.flags |= IW_MICFAILURE_GROUP; + else + ev.flags |= IW_MICFAILURE_PAIRWISE; + + ev.src_addr.sa_family = ARPHRD_ETHER; + memcpy(ev.src_addr.sa_data, &pmlmepriv->assoc_bssid[0], ETH_ALEN); + + memset(&wrqu, 0x00, sizeof(wrqu)); + wrqu.data.length = sizeof(ev); +} + static signed int recvframe_chkmic(struct adapter *adapter, union recv_frame *precvframe) { @@ -1564,6 +1622,93 @@ static signed int wlanhdr_to_ethhdr(union recv_frame *precvframe) return _SUCCESS; } +static struct sk_buff *rtw_alloc_msdu_pkt(union recv_frame *prframe, u16 nSubframe_Length, u8 *pdata) +{ + u16 eth_type; + struct sk_buff *sub_skb; + struct rx_pkt_attrib *pattrib; + + pattrib = &prframe->u.hdr.attrib; + + sub_skb = rtw_skb_alloc(nSubframe_Length + 12); + if (!sub_skb) + return NULL; + + skb_reserve(sub_skb, 12); + skb_put_data(sub_skb, (pdata + ETH_HLEN), nSubframe_Length); + + eth_type = get_unaligned_be16(&sub_skb->data[6]); + + if (sub_skb->len >= 8 && + ((!memcmp(sub_skb->data, rfc1042_header, SNAP_SIZE) && + eth_type != ETH_P_AARP && eth_type != ETH_P_IPX) || + !memcmp(sub_skb->data, bridge_tunnel_header, SNAP_SIZE))) { + /* + * remove RFC1042 or Bridge-Tunnel encapsulation and replace + * EtherType + */ + skb_pull(sub_skb, SNAP_SIZE); + memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->src, ETH_ALEN); + memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->dst, ETH_ALEN); + } else { + __be16 len; + /* Leave Ethernet header part of hdr and full payload */ + len = htons(sub_skb->len); + memcpy(skb_push(sub_skb, 2), &len, 2); + memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->src, ETH_ALEN); + memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->dst, ETH_ALEN); + } + + return sub_skb; +} + +static void rtw_recv_indicate_pkt(struct adapter *padapter, struct sk_buff *pkt, struct rx_pkt_attrib *pattrib) +{ + struct mlme_priv *pmlmepriv = &padapter->mlmepriv; + + /* Indicate the packets to upper layer */ + if (pkt) { + if (check_fwstate(pmlmepriv, WIFI_AP_STATE) == true) { + struct sk_buff *pskb2 = NULL; + struct sta_info *psta = NULL; + struct sta_priv *pstapriv = &padapter->stapriv; + int bmcast = is_multicast_ether_addr(pattrib->dst); + + if (memcmp(pattrib->dst, myid(&padapter->eeprompriv), ETH_ALEN)) { + if (bmcast) { + psta = rtw_get_bcmc_stainfo(padapter); + pskb2 = skb_clone(pkt, GFP_ATOMIC); + } else { + psta = rtw_get_stainfo(pstapriv, pattrib->dst); + } + + if (psta) { + struct net_device *pnetdev = (struct net_device *)padapter->pnetdev; + /* skb->ip_summed = CHECKSUM_NONE; */ + pkt->dev = pnetdev; + skb_set_queue_mapping(pkt, rtw_recv_select_queue(pkt)); + + _rtw_xmit_entry(pkt, pnetdev); + + if (bmcast && pskb2) + pkt = pskb2; + else + return; + } + } else { + /* to APself */ + } + } + + pkt->protocol = eth_type_trans(pkt, padapter->pnetdev); + pkt->dev = padapter->pnetdev; + + pkt->ip_summed = CHECKSUM_NONE; + + rtw_netif_rx(padapter->pnetdev, pkt); + } +} + static int amsdu_to_msdu(struct adapter *padapter, union recv_frame *prframe) { int a_len, padding_len; @@ -1593,7 +1738,7 @@ static int amsdu_to_msdu(struct adapter *padapter, union recv_frame *prframe) if (a_len < ETH_HLEN + nSubframe_Length) break; - sub_pkt = rtw_os_alloc_msdu_pkt(prframe, nSubframe_Length, pdata); + sub_pkt = rtw_alloc_msdu_pkt(prframe, nSubframe_Length, pdata); if (!sub_pkt) break; @@ -1626,7 +1771,7 @@ static int amsdu_to_msdu(struct adapter *padapter, union recv_frame *prframe) /* Indicate the packets to upper layer */ if (sub_pkt) - rtw_os_recv_indicate_pkt(padapter, sub_pkt, &prframe->u.hdr.attrib); + rtw_recv_indicate_pkt(padapter, sub_pkt, &prframe->u.hdr.attrib); } prframe->u.hdr.len = 0; @@ -1725,6 +1870,43 @@ static void recv_indicatepkts_pkt_loss_cnt(struct debug_priv *pdbgpriv, u64 prev } +static int rtw_recv_indicatepkt(struct adapter *padapter, union recv_frame *precv_frame) +{ + struct recv_priv *precvpriv; + struct __queue *pfree_recv_queue; + struct sk_buff *skb; + struct rx_pkt_attrib *pattrib = &precv_frame->u.hdr.attrib; + + precvpriv = &(padapter->recvpriv); + pfree_recv_queue = &(precvpriv->free_recv_queue); + + skb = precv_frame->u.hdr.pkt; + if (!skb) + goto _recv_indicatepkt_drop; + + skb->data = precv_frame->u.hdr.rx_data; + + skb_set_tail_pointer(skb, precv_frame->u.hdr.len); + + skb->len = precv_frame->u.hdr.len; + + rtw_recv_indicate_pkt(padapter, skb, pattrib); + + /* pointers to NULL before rtw_free_recvframe() */ + precv_frame->u.hdr.pkt = NULL; + + rtw_free_recvframe(precv_frame, pfree_recv_queue); + + return _SUCCESS; + +_recv_indicatepkt_drop: + + /* enqueue back to free_recv_queue */ + rtw_free_recvframe(precv_frame, pfree_recv_queue); + + return _FAIL; +} + static int recv_indicatepkts_in_order(struct adapter *padapter, struct recv_reorder_ctrl *preorder_ctrl, int bforced) { struct list_head *phead, *plist; diff --git a/drivers/staging/rtl8723bs/core/rtw_security.c b/drivers/staging/rtl8723bs/core/rtw_security.c index e9f382c280d9..3d99d045f4b6 100644 --- a/drivers/staging/rtl8723bs/core/rtw_security.c +++ b/drivers/staging/rtl8723bs/core/rtw_security.c @@ -7,6 +7,7 @@ #include <linux/crc32.h> #include <drv_types.h> #include <crypto/aes.h> +#include <crypto/utils.h> static const char * const _security_type_str[] = { "N/A", @@ -637,37 +638,6 @@ exit: #define MAX_MSG_SIZE 2048 -/*****************************/ -/**** Function Prototypes ****/ -/*****************************/ - -static void bitwise_xor(u8 *ina, u8 *inb, u8 *out); -static void construct_mic_iv(u8 *mic_header1, - signed int qc_exists, - signed int a4_exists, - u8 *mpdu, - uint payload_length, - u8 *pn_vector, - uint frtype); /* add for CONFIG_IEEE80211W, none 11w also can use */ -static void construct_mic_header1(u8 *mic_header1, - signed int header_length, - u8 *mpdu, - uint frtype); /* for CONFIG_IEEE80211W, none 11w also can use */ -static void construct_mic_header2(u8 *mic_header2, - u8 *mpdu, - signed int a4_exists, - signed int qc_exists); -static void construct_ctr_preload(u8 *ctr_preload, - signed int a4_exists, - signed int qc_exists, - u8 *mpdu, - u8 *pn_vector, - signed int c, - uint frtype); /* for CONFIG_IEEE80211W, none 11w also can use */ - -static void aes128k128d(u8 *key, u8 *data, u8 *ciphertext); - - /****************************************/ /* aes128k128d() */ /* Performs a 128 bit AES encrypt with */ @@ -849,18 +819,6 @@ static void construct_ctr_preload(u8 *ctr_preload, ctr_preload[15] = (unsigned char) (c % 256); } -/************************************/ -/* bitwise_xor() */ -/* A 128 bit, bitwise exclusive or */ -/************************************/ -static void bitwise_xor(u8 *ina, u8 *inb, u8 *out) -{ - signed int i; - - for (i = 0; i < 16; i++) - out[i] = ina[i] ^ inb[i]; -} - static signed int aes_cipher(u8 *key, uint hdrlen, u8 *pframe, uint plen) { @@ -941,13 +899,13 @@ static signed int aes_cipher(u8 *key, uint hdrlen, /* Calculate MIC */ aes128k128d(key, mic_iv, aes_out); - bitwise_xor(aes_out, mic_header1, chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, mic_header1, 16); aes128k128d(key, chain_buffer, aes_out); - bitwise_xor(aes_out, mic_header2, chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, mic_header2, 16); aes128k128d(key, chain_buffer, aes_out); for (i = 0; i < num_blocks; i++) { - bitwise_xor(aes_out, &pframe[payload_index], chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, &pframe[payload_index], 16); payload_index += 16; aes128k128d(key, chain_buffer, aes_out); @@ -960,7 +918,7 @@ static signed int aes_cipher(u8 *key, uint hdrlen, for (j = 0; j < payload_remainder; j++) padded_buffer[j] = pframe[payload_index++]; - bitwise_xor(aes_out, padded_buffer, chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16); aes128k128d(key, chain_buffer, aes_out); } @@ -977,7 +935,7 @@ static signed int aes_cipher(u8 *key, uint hdrlen, pn_vector, i+1, frtype); /* add for CONFIG_IEEE80211W, none 11w also can use */ aes128k128d(key, ctr_preload, aes_out); - bitwise_xor(aes_out, &pframe[payload_index], chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, &pframe[payload_index], 16); for (j = 0; j < 16; j++) pframe[payload_index++] = chain_buffer[j]; } @@ -995,7 +953,7 @@ static signed int aes_cipher(u8 *key, uint hdrlen, padded_buffer[j] = pframe[payload_index+j]; aes128k128d(key, ctr_preload, aes_out); - bitwise_xor(aes_out, padded_buffer, chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16); for (j = 0; j < payload_remainder; j++) pframe[payload_index++] = chain_buffer[j]; } @@ -1011,7 +969,7 @@ static signed int aes_cipher(u8 *key, uint hdrlen, padded_buffer[j] = pframe[j+hdrlen+8+plen]; aes128k128d(key, ctr_preload, aes_out); - bitwise_xor(aes_out, padded_buffer, chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16); for (j = 0; j < 8; j++) pframe[payload_index++] = chain_buffer[j]; @@ -1137,7 +1095,7 @@ static signed int aes_decipher(u8 *key, uint hdrlen, frtype); /* add for CONFIG_IEEE80211W, none 11w also can use */ aes128k128d(key, ctr_preload, aes_out); - bitwise_xor(aes_out, &pframe[payload_index], chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, &pframe[payload_index], 16); for (j = 0; j < 16; j++) pframe[payload_index++] = chain_buffer[j]; @@ -1156,7 +1114,7 @@ static signed int aes_decipher(u8 *key, uint hdrlen, padded_buffer[j] = pframe[payload_index+j]; aes128k128d(key, ctr_preload, aes_out); - bitwise_xor(aes_out, padded_buffer, chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16); for (j = 0; j < payload_remainder; j++) pframe[payload_index++] = chain_buffer[j]; } @@ -1187,13 +1145,13 @@ static signed int aes_decipher(u8 *key, uint hdrlen, /* Calculate MIC */ aes128k128d(key, mic_iv, aes_out); - bitwise_xor(aes_out, mic_header1, chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, mic_header1, 16); aes128k128d(key, chain_buffer, aes_out); - bitwise_xor(aes_out, mic_header2, chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, mic_header2, 16); aes128k128d(key, chain_buffer, aes_out); for (i = 0; i < num_blocks; i++) { - bitwise_xor(aes_out, &message[payload_index], chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, &message[payload_index], 16); payload_index += 16; aes128k128d(key, chain_buffer, aes_out); @@ -1206,7 +1164,7 @@ static signed int aes_decipher(u8 *key, uint hdrlen, for (j = 0; j < payload_remainder; j++) padded_buffer[j] = message[payload_index++]; - bitwise_xor(aes_out, padded_buffer, chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16); aes128k128d(key, chain_buffer, aes_out); } @@ -1223,7 +1181,7 @@ static signed int aes_decipher(u8 *key, uint hdrlen, frtype); /* add for CONFIG_IEEE80211W, none 11w also can use */ aes128k128d(key, ctr_preload, aes_out); - bitwise_xor(aes_out, &message[payload_index], chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, &message[payload_index], 16); for (j = 0; j < 16; j++) message[payload_index++] = chain_buffer[j]; } @@ -1241,7 +1199,7 @@ static signed int aes_decipher(u8 *key, uint hdrlen, padded_buffer[j] = message[payload_index+j]; aes128k128d(key, ctr_preload, aes_out); - bitwise_xor(aes_out, padded_buffer, chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16); for (j = 0; j < payload_remainder; j++) message[payload_index++] = chain_buffer[j]; } @@ -1256,7 +1214,7 @@ static signed int aes_decipher(u8 *key, uint hdrlen, padded_buffer[j] = message[j+hdrlen+8+plen-8]; aes128k128d(key, ctr_preload, aes_out); - bitwise_xor(aes_out, padded_buffer, chain_buffer); + crypto_xor_cpy(chain_buffer, aes_out, padded_buffer, 16); for (j = 0; j < 8; j++) message[payload_index++] = chain_buffer[j]; @@ -1405,7 +1363,7 @@ u32 rtw_BIP_verify(struct adapter *padapter, u8 *precvframe) ClearPwrMgt(BIP_AAD); ClearMData(BIP_AAD); /* conscruct AAD, copy address 1 to address 3 */ - memcpy(BIP_AAD+2, pwlanhdr->addr1, 18); + memcpy(BIP_AAD + 2, &pwlanhdr->addrs, sizeof(pwlanhdr->addrs)); if (omac1_aes_128(padapter->securitypriv.dot11wBIPKey[padapter->securitypriv.dot11wBIPKeyid].skey , BIP_AAD, ori_len, mic)) diff --git a/drivers/staging/rtl8723bs/core/rtw_sta_mgt.c b/drivers/staging/rtl8723bs/core/rtw_sta_mgt.c index 1d2b53c76afc..d1f6030799cb 100644 --- a/drivers/staging/rtl8723bs/core/rtw_sta_mgt.c +++ b/drivers/staging/rtl8723bs/core/rtw_sta_mgt.c @@ -229,7 +229,7 @@ struct sta_info *rtw_alloc_stainfo(struct sta_priv *pstapriv, u8 *hwaddr) for (i = 0; i < 16; i++) memcpy(&psta->sta_recvpriv.rxcache.tid_rxseq[i], &wRxSeqInitialValue, 2); - init_addba_retry_timer(pstapriv->padapter, psta); + timer_setup(&psta->addba_retry_timer, addba_timer_hdl, 0); /* for A-MPDU Rx reordering buffer control */ for (i = 0; i < 16 ; i++) { @@ -247,7 +247,9 @@ struct sta_info *rtw_alloc_stainfo(struct sta_priv *pstapriv, u8 *hwaddr) INIT_LIST_HEAD(&preorder_ctrl->pending_recvframe_queue.queue); spin_lock_init(&preorder_ctrl->pending_recvframe_queue.lock); - rtw_init_recv_timer(preorder_ctrl); + /* init recv timer */ + timer_setup(&preorder_ctrl->reordering_ctrl_timer, + rtw_reordering_ctrl_timeout_handler, 0); } /* init for DM */ diff --git a/drivers/staging/rtl8723bs/core/rtw_wlan_util.c b/drivers/staging/rtl8723bs/core/rtw_wlan_util.c index 0c6072d08661..1def9758852c 100644 --- a/drivers/staging/rtl8723bs/core/rtw_wlan_util.c +++ b/drivers/staging/rtl8723bs/core/rtw_wlan_util.c @@ -170,10 +170,10 @@ void get_rate_set(struct adapter *padapter, unsigned char *pbssrate, int *bssrat void set_mcs_rate_by_mask(u8 *mcs_set, u32 mask) { - u8 mcs_rate_1r = (u8)(mask&0xff); - u8 mcs_rate_2r = (u8)((mask>>8)&0xff); - u8 mcs_rate_3r = (u8)((mask>>16)&0xff); - u8 mcs_rate_4r = (u8)((mask>>24)&0xff); + u8 mcs_rate_1r = (u8)(mask & 0xff); + u8 mcs_rate_2r = (u8)((mask >> 8) & 0xff); + u8 mcs_rate_3r = (u8)((mask >> 16) & 0xff); + u8 mcs_rate_4r = (u8)((mask >> 24) & 0xff); mcs_set[0] &= mcs_rate_1r; mcs_set[1] &= mcs_rate_2r; @@ -267,21 +267,21 @@ inline void rtw_set_oper_ch(struct adapter *adapter, u8 ch) dvobj->on_oper_ch_time = jiffies; #ifdef DBG_CH_SWITCH - cnt += scnprintf(msg+cnt, len-cnt, "switch to ch %3u", ch); + cnt += scnprintf(msg + cnt, len - cnt, "switch to ch %3u", ch); for (i = 0; i < dvobj->iface_nums; i++) { struct adapter *iface = dvobj->padapters[i]; - cnt += scnprintf(msg+cnt, len-cnt, " [%s:", ADPT_ARG(iface)); + cnt += scnprintf(msg + cnt, len - cnt, " [%s:", ADPT_ARG(iface)); if (iface->mlmeextpriv.cur_channel == ch) - cnt += scnprintf(msg+cnt, len-cnt, "C"); + cnt += scnprintf(msg + cnt, len - cnt, "C"); else - cnt += scnprintf(msg+cnt, len-cnt, "_"); + cnt += scnprintf(msg + cnt, len - cnt, "_"); if (iface->wdinfo.listen_channel == ch && !rtw_p2p_chk_state(&iface->wdinfo, P2P_STATE_NONE)) - cnt += scnprintf(msg+cnt, len-cnt, "L"); + cnt += scnprintf(msg + cnt, len - cnt, "L"); else - cnt += scnprintf(msg+cnt, len-cnt, "_"); - cnt += scnprintf(msg+cnt, len-cnt, "]"); + cnt += scnprintf(msg + cnt, len - cnt, "_"); + cnt += scnprintf(msg + cnt, len - cnt, "]"); } #endif /* DBG_CH_SWITCH */ @@ -381,7 +381,7 @@ int is_client_associated_to_ap(struct adapter *padapter) pmlmeext = &padapter->mlmeextpriv; pmlmeinfo = &(pmlmeext->mlmext_info); - if ((pmlmeinfo->state & WIFI_FW_ASSOC_SUCCESS) && ((pmlmeinfo->state&0x03) == WIFI_FW_STATION_STATE)) + if ((pmlmeinfo->state & WIFI_FW_ASSOC_SUCCESS) && ((pmlmeinfo->state & 0x03) == WIFI_FW_STATION_STATE)) return true; else return _FAIL; @@ -392,7 +392,7 @@ int is_client_associated_to_ibss(struct adapter *padapter) struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; struct mlme_ext_info *pmlmeinfo = &(pmlmeext->mlmext_info); - if ((pmlmeinfo->state & WIFI_FW_ASSOC_SUCCESS) && ((pmlmeinfo->state&0x03) == WIFI_FW_ADHOC_STATE)) + if ((pmlmeinfo->state & WIFI_FW_ASSOC_SUCCESS) && ((pmlmeinfo->state & 0x03) == WIFI_FW_ADHOC_STATE)) return true; else return _FAIL; @@ -431,7 +431,7 @@ void invalidate_cam_all(struct adapter *padapter) spin_lock_bh(&cam_ctl->lock); cam_ctl->bitmap = 0; - memset(dvobj->cam_cache, 0, sizeof(struct cam_entry_cache)*TOTAL_CAM_ENTRY); + memset(dvobj->cam_cache, 0, sizeof(struct cam_entry_cache) * TOTAL_CAM_ENTRY); spin_unlock_bh(&cam_ctl->lock); } @@ -453,7 +453,7 @@ void _write_cam(struct adapter *padapter, u8 entry, u16 ctrl, u8 *mac, u8 *key) break; default: i = (j - 2) << 2; - val = (key[i] | (key[i+1] << 8) | (key[i+2] << 16) | (key[i+3] << 24)); + val = (key[i] | (key[i + 1] << 8) | (key[i + 2] << 16) | (key[i + 3] << 24)); break; } @@ -522,7 +522,7 @@ static bool _rtw_camid_is_gk(struct adapter *adapter, u8 cam_id) if (!(cam_ctl->bitmap & BIT(cam_id))) goto exit; - ret = (dvobj->cam_cache[cam_id].ctrl&BIT6)?true:false; + ret = (dvobj->cam_cache[cam_id].ctrl & BIT6) ? true : false; exit: return ret; @@ -537,7 +537,7 @@ static s16 _rtw_camid_search(struct adapter *adapter, u8 *addr, s16 kid) for (i = 0; i < TOTAL_CAM_ENTRY; i++) { if (addr && memcmp(dvobj->cam_cache[i].mac, addr, ETH_ALEN)) continue; - if (kid >= 0 && kid != (dvobj->cam_cache[i].ctrl&0x03)) + if (kid >= 0 && kid != (dvobj->cam_cache[i].ctrl & 0x03)) continue; cam_id = i; @@ -571,7 +571,7 @@ s16 rtw_camid_alloc(struct adapter *adapter, struct sta_info *sta, u8 kid) mlmeinfo = &adapter->mlmeextpriv.mlmext_info; - if ((((mlmeinfo->state&0x03) == WIFI_FW_AP_STATE) || ((mlmeinfo->state&0x03) == WIFI_FW_ADHOC_STATE)) + if ((((mlmeinfo->state & 0x03) == WIFI_FW_AP_STATE) || ((mlmeinfo->state & 0x03) == WIFI_FW_ADHOC_STATE)) && !sta) { /* AP/Ad-hoc mode group key: static alloction to default key by key ID */ if (kid > 3) { @@ -585,7 +585,7 @@ s16 rtw_camid_alloc(struct adapter *adapter, struct sta_info *sta, u8 kid) cam_id = kid; } else { int i; - u8 *addr = sta?sta->hwaddr:NULL; + u8 *addr = sta ? sta->hwaddr : NULL; if (!sta) { if (!(mlmeinfo->state & WIFI_FW_ASSOC_SUCCESS)) { @@ -792,7 +792,7 @@ void WMMOnAssocRsp(struct adapter *padapter) switch (ACI) { case 0x0: rtw_hal_set_hwreg(padapter, HW_VAR_AC_PARAM_BE, (u8 *)(&acParm)); - acm_mask |= (ACM ? BIT(1):0); + acm_mask |= (ACM ? BIT(1) : 0); edca[XMIT_BE_QUEUE] = acParm; break; @@ -804,13 +804,13 @@ void WMMOnAssocRsp(struct adapter *padapter) case 0x2: rtw_hal_set_hwreg(padapter, HW_VAR_AC_PARAM_VI, (u8 *)(&acParm)); - acm_mask |= (ACM ? BIT(2):0); + acm_mask |= (ACM ? BIT(2) : 0); edca[XMIT_VI_QUEUE] = acParm; break; case 0x3: rtw_hal_set_hwreg(padapter, HW_VAR_AC_PARAM_VO, (u8 *)(&acParm)); - acm_mask |= (ACM ? BIT(3):0); + acm_mask |= (ACM ? BIT(3) : 0); edca[XMIT_VO_QUEUE] = acParm; break; } @@ -1170,7 +1170,7 @@ int rtw_check_bcn_info(struct adapter *Adapter, u8 *pframe, u32 packet_len) ht_info_infos_0 = 0; } if (ht_cap_info != cur_network->bcn_info.ht_cap_info || - ((ht_info_infos_0&0x03) != (cur_network->bcn_info.ht_info_infos_0&0x03))) { + ((ht_info_infos_0 & 0x03) != (cur_network->bcn_info.ht_info_infos_0 & 0x03))) { { /* bcn_info_update */ cur_network->bcn_info.ht_cap_info = ht_cap_info; @@ -1238,12 +1238,12 @@ int rtw_check_bcn_info(struct adapter *Adapter, u8 *pframe, u32 packet_len) goto _mismatch; if (encryp_protocol == ENCRYP_PROTOCOL_WPA || encryp_protocol == ENCRYP_PROTOCOL_WPA2) { - pbuf = rtw_get_wpa_ie(&bssid->ies[12], &wpa_ielen, bssid->ie_length-12); + pbuf = rtw_get_wpa_ie(&bssid->ies[12], &wpa_ielen, bssid->ie_length - 12); if (pbuf && (wpa_ielen > 0)) { rtw_parse_wpa_ie(pbuf, wpa_ielen + 2, &group_cipher, &pairwise_cipher, &is_8021x); } else { - pbuf = rtw_get_wpa2_ie(&bssid->ies[12], &wpa_ielen, bssid->ie_length-12); + pbuf = rtw_get_wpa2_ie(&bssid->ies[12], &wpa_ielen, bssid->ie_length - 12); if (pbuf && (wpa_ielen > 0)) rtw_parse_wpa2_ie(pbuf, wpa_ielen + 2, &group_cipher, @@ -1630,7 +1630,7 @@ void process_addba_req(struct adapter *padapter, u8 *paddba_req, u8 *addr) if (psta) { param = le16_to_cpu(preq->BA_para_set); - tid = (param>>2)&0x0f; + tid = (param >> 2) & 0x0f; preorder_ctrl = &psta->recvreorder_ctrl[tid]; @@ -1648,7 +1648,7 @@ void update_TSF(struct mlme_ext_priv *pmlmeext, u8 *pframe, uint len) pIE = pframe + sizeof(struct ieee80211_hdr_3addr); pbuf = (__le32 *)pIE; - pmlmeext->TSFValue = le32_to_cpu(*(pbuf+1)); + pmlmeext->TSFValue = le32_to_cpu(*(pbuf + 1)); pmlmeext->TSFValue = pmlmeext->TSFValue << 32; @@ -1674,14 +1674,14 @@ void adaptive_early_32k(struct mlme_ext_priv *pmlmeext, u8 *pframe, uint len) pIE = pframe + sizeof(struct ieee80211_hdr_3addr); pbuf = (__le32 *)pIE; - tsf = le32_to_cpu(*(pbuf+1)); + tsf = le32_to_cpu(*(pbuf + 1)); tsf = tsf << 32; tsf |= le32_to_cpu(*pbuf); /* delay = (timestamp mod 1024*100)/1000 (unit: ms) */ /* delay_ms = do_div(tsf, (pmlmeinfo->bcn_interval*1024))/1000; */ - delay_ms = do_div(tsf, (pmlmeinfo->bcn_interval*1024)); - delay_ms = delay_ms/1000; + delay_ms = do_div(tsf, (pmlmeinfo->bcn_interval * 1024)); + delay_ms = delay_ms / 1000; if (delay_ms >= 8) pmlmeext->bcn_delay_cnt[8]++; diff --git a/drivers/staging/rtl8723bs/core/rtw_xmit.c b/drivers/staging/rtl8723bs/core/rtw_xmit.c index 8c6841f078b4..21690857fd62 100644 --- a/drivers/staging/rtl8723bs/core/rtw_xmit.c +++ b/drivers/staging/rtl8723bs/core/rtw_xmit.c @@ -1209,7 +1209,7 @@ s32 rtw_mgmt_xmitframe_coalesce(struct adapter *padapter, struct sk_buff *pkt, s ClearPwrMgt(BIP_AAD); ClearMData(BIP_AAD); /* conscruct AAD, copy address 1 to address 3 */ - memcpy(BIP_AAD+2, pwlanhdr->addr1, 18); + memcpy(BIP_AAD + 2, &pwlanhdr->addrs, sizeof(pwlanhdr->addrs)); /* copy management fram body */ memcpy(BIP_AAD+BIP_AAD_SIZE, MGMT_body, frame_body_len); /* calculate mic */ diff --git a/drivers/staging/rtl8723bs/hal/hal_com_phycfg.c b/drivers/staging/rtl8723bs/hal/hal_com_phycfg.c index d5649e7d8f99..cd76e26e868f 100644 --- a/drivers/staging/rtl8723bs/hal/hal_com_phycfg.c +++ b/drivers/staging/rtl8723bs/hal/hal_com_phycfg.c @@ -59,10 +59,7 @@ phy_SetTxPowerByRateBase(struct adapter *Adapter, u8 RfPath, } } -static void -phy_StoreTxPowerByRateBase( -struct adapter *padapter - ) +static void phy_StoreTxPowerByRateBase(struct adapter *padapter) { u8 path, base; diff --git a/drivers/staging/rtl8723bs/hal/hal_pwr_seq.c b/drivers/staging/rtl8723bs/hal/hal_pwr_seq.c index fba67a7c069c..2438931ca51b 100644 --- a/drivers/staging/rtl8723bs/hal/hal_pwr_seq.c +++ b/drivers/staging/rtl8723bs/hal/hal_pwr_seq.c @@ -8,7 +8,7 @@ /* * This file includes all kinds of Power Action event for RTL8723B -and corresponding hardware configurtions which are released from HW SD. +and corresponding hardware configurations which are released from HW SD. Major Change History: When Who What diff --git a/drivers/staging/rtl8723bs/hal/odm.c b/drivers/staging/rtl8723bs/hal/odm.c index ba85efb30db2..4b36af47f680 100644 --- a/drivers/staging/rtl8723bs/hal/odm.c +++ b/drivers/staging/rtl8723bs/hal/odm.c @@ -9,118 +9,6 @@ /* Global var */ -u32 OFDMSwingTable[OFDM_TABLE_SIZE] = { - 0x7f8001fe, /* 0, +6.0dB */ - 0x788001e2, /* 1, +5.5dB */ - 0x71c001c7, /* 2, +5.0dB */ - 0x6b8001ae, /* 3, +4.5dB */ - 0x65400195, /* 4, +4.0dB */ - 0x5fc0017f, /* 5, +3.5dB */ - 0x5a400169, /* 6, +3.0dB */ - 0x55400155, /* 7, +2.5dB */ - 0x50800142, /* 8, +2.0dB */ - 0x4c000130, /* 9, +1.5dB */ - 0x47c0011f, /* 10, +1.0dB */ - 0x43c0010f, /* 11, +0.5dB */ - 0x40000100, /* 12, +0dB */ - 0x3c8000f2, /* 13, -0.5dB */ - 0x390000e4, /* 14, -1.0dB */ - 0x35c000d7, /* 15, -1.5dB */ - 0x32c000cb, /* 16, -2.0dB */ - 0x300000c0, /* 17, -2.5dB */ - 0x2d4000b5, /* 18, -3.0dB */ - 0x2ac000ab, /* 19, -3.5dB */ - 0x288000a2, /* 20, -4.0dB */ - 0x26000098, /* 21, -4.5dB */ - 0x24000090, /* 22, -5.0dB */ - 0x22000088, /* 23, -5.5dB */ - 0x20000080, /* 24, -6.0dB */ - 0x1e400079, /* 25, -6.5dB */ - 0x1c800072, /* 26, -7.0dB */ - 0x1b00006c, /* 27. -7.5dB */ - 0x19800066, /* 28, -8.0dB */ - 0x18000060, /* 29, -8.5dB */ - 0x16c0005b, /* 30, -9.0dB */ - 0x15800056, /* 31, -9.5dB */ - 0x14400051, /* 32, -10.0dB */ - 0x1300004c, /* 33, -10.5dB */ - 0x12000048, /* 34, -11.0dB */ - 0x11000044, /* 35, -11.5dB */ - 0x10000040, /* 36, -12.0dB */ -}; - -u8 CCKSwingTable_Ch1_Ch13[CCK_TABLE_SIZE][8] = { - {0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04}, /* 0, +0dB */ - {0x33, 0x32, 0x2b, 0x23, 0x1a, 0x11, 0x08, 0x04}, /* 1, -0.5dB */ - {0x30, 0x2f, 0x29, 0x21, 0x19, 0x10, 0x08, 0x03}, /* 2, -1.0dB */ - {0x2d, 0x2d, 0x27, 0x1f, 0x18, 0x0f, 0x08, 0x03}, /* 3, -1.5dB */ - {0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03}, /* 4, -2.0dB */ - {0x28, 0x28, 0x22, 0x1c, 0x15, 0x0d, 0x07, 0x03}, /* 5, -2.5dB */ - {0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03}, /* 6, -3.0dB */ - {0x24, 0x23, 0x1f, 0x19, 0x13, 0x0c, 0x06, 0x03}, /* 7, -3.5dB */ - {0x22, 0x21, 0x1d, 0x18, 0x11, 0x0b, 0x06, 0x02}, /* 8, -4.0dB */ - {0x20, 0x20, 0x1b, 0x16, 0x11, 0x08, 0x05, 0x02}, /* 9, -4.5dB */ - {0x1f, 0x1e, 0x1a, 0x15, 0x10, 0x0a, 0x05, 0x02}, /* 10, -5.0dB */ - {0x1d, 0x1c, 0x18, 0x14, 0x0f, 0x0a, 0x05, 0x02}, /* 11, -5.5dB */ - {0x1b, 0x1a, 0x17, 0x13, 0x0e, 0x09, 0x04, 0x02}, /* 12, -6.0dB <== default */ - {0x1a, 0x19, 0x16, 0x12, 0x0d, 0x09, 0x04, 0x02}, /* 13, -6.5dB */ - {0x18, 0x17, 0x15, 0x11, 0x0c, 0x08, 0x04, 0x02}, /* 14, -7.0dB */ - {0x17, 0x16, 0x13, 0x10, 0x0c, 0x08, 0x04, 0x02}, /* 15, -7.5dB */ - {0x16, 0x15, 0x12, 0x0f, 0x0b, 0x07, 0x04, 0x01}, /* 16, -8.0dB */ - {0x14, 0x14, 0x11, 0x0e, 0x0b, 0x07, 0x03, 0x02}, /* 17, -8.5dB */ - {0x13, 0x13, 0x10, 0x0d, 0x0a, 0x06, 0x03, 0x01}, /* 18, -9.0dB */ - {0x12, 0x12, 0x0f, 0x0c, 0x09, 0x06, 0x03, 0x01}, /* 19, -9.5dB */ - {0x11, 0x11, 0x0f, 0x0c, 0x09, 0x06, 0x03, 0x01}, /* 20, -10.0dB */ - {0x10, 0x10, 0x0e, 0x0b, 0x08, 0x05, 0x03, 0x01}, /* 21, -10.5dB */ - {0x0f, 0x0f, 0x0d, 0x0b, 0x08, 0x05, 0x03, 0x01}, /* 22, -11.0dB */ - {0x0e, 0x0e, 0x0c, 0x0a, 0x08, 0x05, 0x02, 0x01}, /* 23, -11.5dB */ - {0x0d, 0x0d, 0x0c, 0x0a, 0x07, 0x05, 0x02, 0x01}, /* 24, -12.0dB */ - {0x0d, 0x0c, 0x0b, 0x09, 0x07, 0x04, 0x02, 0x01}, /* 25, -12.5dB */ - {0x0c, 0x0c, 0x0a, 0x09, 0x06, 0x04, 0x02, 0x01}, /* 26, -13.0dB */ - {0x0b, 0x0b, 0x0a, 0x08, 0x06, 0x04, 0x02, 0x01}, /* 27, -13.5dB */ - {0x0b, 0x0a, 0x09, 0x08, 0x06, 0x04, 0x02, 0x01}, /* 28, -14.0dB */ - {0x0a, 0x0a, 0x09, 0x07, 0x05, 0x03, 0x02, 0x01}, /* 29, -14.5dB */ - {0x0a, 0x09, 0x08, 0x07, 0x05, 0x03, 0x02, 0x01}, /* 30, -15.0dB */ - {0x09, 0x09, 0x08, 0x06, 0x05, 0x03, 0x01, 0x01}, /* 31, -15.5dB */ - {0x09, 0x08, 0x07, 0x06, 0x04, 0x03, 0x01, 0x01} /* 32, -16.0dB */ -}; - -u8 CCKSwingTable_Ch14[CCK_TABLE_SIZE][8] = { - {0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00}, /* 0, +0dB */ - {0x33, 0x32, 0x2b, 0x19, 0x00, 0x00, 0x00, 0x00}, /* 1, -0.5dB */ - {0x30, 0x2f, 0x29, 0x18, 0x00, 0x00, 0x00, 0x00}, /* 2, -1.0dB */ - {0x2d, 0x2d, 0x17, 0x17, 0x00, 0x00, 0x00, 0x00}, /* 3, -1.5dB */ - {0x2b, 0x2a, 0x25, 0x15, 0x00, 0x00, 0x00, 0x00}, /* 4, -2.0dB */ - {0x28, 0x28, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00}, /* 5, -2.5dB */ - {0x26, 0x25, 0x21, 0x13, 0x00, 0x00, 0x00, 0x00}, /* 6, -3.0dB */ - {0x24, 0x23, 0x1f, 0x12, 0x00, 0x00, 0x00, 0x00}, /* 7, -3.5dB */ - {0x22, 0x21, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00}, /* 8, -4.0dB */ - {0x20, 0x20, 0x1b, 0x10, 0x00, 0x00, 0x00, 0x00}, /* 9, -4.5dB */ - {0x1f, 0x1e, 0x1a, 0x0f, 0x00, 0x00, 0x00, 0x00}, /* 10, -5.0dB */ - {0x1d, 0x1c, 0x18, 0x0e, 0x00, 0x00, 0x00, 0x00}, /* 11, -5.5dB */ - {0x1b, 0x1a, 0x17, 0x0e, 0x00, 0x00, 0x00, 0x00}, /* 12, -6.0dB <== default */ - {0x1a, 0x19, 0x16, 0x0d, 0x00, 0x00, 0x00, 0x00}, /* 13, -6.5dB */ - {0x18, 0x17, 0x15, 0x0c, 0x00, 0x00, 0x00, 0x00}, /* 14, -7.0dB */ - {0x17, 0x16, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00}, /* 15, -7.5dB */ - {0x16, 0x15, 0x12, 0x0b, 0x00, 0x00, 0x00, 0x00}, /* 16, -8.0dB */ - {0x14, 0x14, 0x11, 0x0a, 0x00, 0x00, 0x00, 0x00}, /* 17, -8.5dB */ - {0x13, 0x13, 0x10, 0x0a, 0x00, 0x00, 0x00, 0x00}, /* 18, -9.0dB */ - {0x12, 0x12, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00}, /* 19, -9.5dB */ - {0x11, 0x11, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00}, /* 20, -10.0dB */ - {0x10, 0x10, 0x0e, 0x08, 0x00, 0x00, 0x00, 0x00}, /* 21, -10.5dB */ - {0x0f, 0x0f, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x00}, /* 22, -11.0dB */ - {0x0e, 0x0e, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00}, /* 23, -11.5dB */ - {0x0d, 0x0d, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00}, /* 24, -12.0dB */ - {0x0d, 0x0c, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x00}, /* 25, -12.5dB */ - {0x0c, 0x0c, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00}, /* 26, -13.0dB */ - {0x0b, 0x0b, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00}, /* 27, -13.5dB */ - {0x0b, 0x0a, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 28, -14.0dB */ - {0x0a, 0x0a, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 29, -14.5dB */ - {0x0a, 0x09, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 30, -15.0dB */ - {0x09, 0x09, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 31, -15.5dB */ - {0x09, 0x08, 0x07, 0x04, 0x00, 0x00, 0x00, 0x00} /* 32, -16.0dB */ -}; - u32 OFDMSwingTable_New[OFDM_TABLE_SIZE] = { 0x0b40002d, /* 0, -15.0dB */ 0x0c000030, /* 1, -14.5dB */ @@ -239,46 +127,6 @@ u8 CCKSwingTable_Ch14_New[CCK_TABLE_SIZE][8] = { {0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00} /* 32, +0dB */ }; -u32 TxScalingTable_Jaguar[TXSCALE_TABLE_SIZE] = { - 0x081, /* 0, -12.0dB */ - 0x088, /* 1, -11.5dB */ - 0x090, /* 2, -11.0dB */ - 0x099, /* 3, -10.5dB */ - 0x0A2, /* 4, -10.0dB */ - 0x0AC, /* 5, -9.5dB */ - 0x0B6, /* 6, -9.0dB */ - 0x0C0, /* 7, -8.5dB */ - 0x0CC, /* 8, -8.0dB */ - 0x0D8, /* 9, -7.5dB */ - 0x0E5, /* 10, -7.0dB */ - 0x0F2, /* 11, -6.5dB */ - 0x101, /* 12, -6.0dB */ - 0x110, /* 13, -5.5dB */ - 0x120, /* 14, -5.0dB */ - 0x131, /* 15, -4.5dB */ - 0x143, /* 16, -4.0dB */ - 0x156, /* 17, -3.5dB */ - 0x16A, /* 18, -3.0dB */ - 0x180, /* 19, -2.5dB */ - 0x197, /* 20, -2.0dB */ - 0x1AF, /* 21, -1.5dB */ - 0x1C8, /* 22, -1.0dB */ - 0x1E3, /* 23, -0.5dB */ - 0x200, /* 24, +0 dB */ - 0x21E, /* 25, +0.5dB */ - 0x23E, /* 26, +1.0dB */ - 0x261, /* 27, +1.5dB */ - 0x285, /* 28, +2.0dB */ - 0x2AB, /* 29, +2.5dB */ - 0x2D3, /* 30, +3.0dB */ - 0x2FE, /* 31, +3.5dB */ - 0x32B, /* 32, +4.0dB */ - 0x35C, /* 33, +4.5dB */ - 0x38E, /* 34, +5.0dB */ - 0x3C4, /* 35, +5.5dB */ - 0x3FE /* 36, +6.0dB */ -}; - /* Remove Edca by Yu Chen */ static void odm_CommonInfoSelfInit(struct dm_odm_t *pDM_Odm) diff --git a/drivers/staging/rtl8723bs/hal/odm.h b/drivers/staging/rtl8723bs/hal/odm.h index 010274ba8079..1c929d88e596 100644 --- a/drivers/staging/rtl8723bs/hal/odm.h +++ b/drivers/staging/rtl8723bs/hal/odm.h @@ -1080,16 +1080,10 @@ enum { /* tag_RF_Type_Definition */ /* */ /* Extern Global Variables. */ /* */ -extern u32 OFDMSwingTable[OFDM_TABLE_SIZE]; -extern u8 CCKSwingTable_Ch1_Ch13[CCK_TABLE_SIZE][8]; -extern u8 CCKSwingTable_Ch14[CCK_TABLE_SIZE][8]; - extern u32 OFDMSwingTable_New[OFDM_TABLE_SIZE]; extern u8 CCKSwingTable_Ch1_Ch13_New[CCK_TABLE_SIZE][8]; extern u8 CCKSwingTable_Ch14_New[CCK_TABLE_SIZE][8]; -extern u32 TxScalingTable_Jaguar[TXSCALE_TABLE_SIZE]; - /* */ /* check Sta pointer valid or not */ /* */ diff --git a/drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c b/drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c index 1608bc71bd71..18244adad9e0 100644 --- a/drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c +++ b/drivers/staging/rtl8723bs/hal/rtl8723b_hal_init.c @@ -445,47 +445,31 @@ void rtl8723b_InitializeFirmwareVars(struct adapter *padapter) /* Efuse related code */ /* */ static u8 hal_EfuseSwitchToBank( - struct adapter *padapter, u8 bank, bool bPseudoTest + struct adapter *padapter, u8 bank ) { - u8 bRet = false; - u32 value32 = 0; -#ifdef HAL_EFUSE_MEMORY - struct hal_com_data *pHalData = GET_HAL_DATA(padapter); - struct efuse_hal *pEfuseHal = &pHalData->EfuseHal; -#endif + u8 bRet = true; + u32 value32 = rtw_read32(padapter, EFUSE_TEST); - - if (bPseudoTest) { -#ifdef HAL_EFUSE_MEMORY - pEfuseHal->fakeEfuseBank = bank; -#else - fakeEfuseBank = bank; -#endif - bRet = true; - } else { - value32 = rtw_read32(padapter, EFUSE_TEST); - bRet = true; - switch (bank) { - case 0: - value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_WIFI_SEL_0); - break; - case 1: - value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_BT_SEL_0); - break; - case 2: - value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_BT_SEL_1); - break; - case 3: - value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_BT_SEL_2); - break; - default: - value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_WIFI_SEL_0); - bRet = false; - break; - } - rtw_write32(padapter, EFUSE_TEST, value32); + switch (bank) { + case 0: + value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_WIFI_SEL_0); + break; + case 1: + value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_BT_SEL_0); + break; + case 2: + value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_BT_SEL_1); + break; + case 3: + value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_BT_SEL_2); + break; + default: + value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_WIFI_SEL_0); + bRet = false; + break; } + rtw_write32(padapter, EFUSE_TEST, value32); return bRet; } @@ -494,8 +478,7 @@ void Hal_GetEfuseDefinition( struct adapter *padapter, u8 efuseType, u8 type, - void *pOut, - bool bPseudoTest + void *pOut ) { switch (type) { @@ -585,17 +568,8 @@ void Hal_GetEfuseDefinition( } } -#define VOLTAGE_V25 0x03 - -/* */ -/* The following is for compile ok */ -/* That should be merged with the original in the future */ -/* */ -#define EFUSE_ACCESS_ON_8723 0x69 /* For RTL8723 only. */ -#define REG_EFUSE_ACCESS_8723 0x00CF /* Efuse access protection for RTL8723 */ - void Hal_EfusePowerSwitch( - struct adapter *padapter, u8 bWrite, u8 PwrState + struct adapter *padapter, u8 PwrState ) { u8 tempval; @@ -628,7 +602,7 @@ void Hal_EfusePowerSwitch( } while (1); } - rtw_write8(padapter, REG_EFUSE_ACCESS_8723, EFUSE_ACCESS_ON_8723); + rtw_write8(padapter, REG_EFUSE_ACCESS, EFUSE_ACCESS_ON); /* Reset: 0x0000h[28], default valid */ tmpV16 = rtw_read16(padapter, REG_SYS_FUNC_EN); @@ -643,25 +617,8 @@ void Hal_EfusePowerSwitch( tmpV16 |= (LOADER_CLK_EN | ANA8M); rtw_write16(padapter, REG_SYS_CLKR, tmpV16); } - - if (bWrite) { - /* Enable LDO 2.5V before read/write action */ - tempval = rtw_read8(padapter, EFUSE_TEST+3); - tempval &= 0x0F; - tempval |= (VOLTAGE_V25 << 4); - rtw_write8(padapter, EFUSE_TEST+3, (tempval | 0x80)); - - /* rtw_write8(padapter, REG_EFUSE_ACCESS, EFUSE_ACCESS_ON); */ - } } else { rtw_write8(padapter, REG_EFUSE_ACCESS, EFUSE_ACCESS_OFF); - - if (bWrite) { - /* Disable LDO 2.5V after read/write action */ - tempval = rtw_read8(padapter, EFUSE_TEST+3); - rtw_write8(padapter, EFUSE_TEST+3, (tempval & 0x7F)); - } - } } @@ -669,14 +626,9 @@ static void hal_ReadEFuse_WiFi( struct adapter *padapter, u16 _offset, u16 _size_byte, - u8 *pbuf, - bool bPseudoTest + u8 *pbuf ) { -#ifdef HAL_EFUSE_MEMORY - struct hal_com_data *pHalData = GET_HAL_DATA(padapter); - struct efuse_hal *pEfuseHal = &pHalData->EfuseHal; -#endif u8 *efuseTbl = NULL; u16 eFuse_Addr = 0; u8 offset, wden; @@ -698,10 +650,10 @@ static void hal_ReadEFuse_WiFi( memset(efuseTbl, 0xFF, EFUSE_MAX_MAP_LEN); /* switch bank back to bank 0 for later BT and wifi use. */ - hal_EfuseSwitchToBank(padapter, 0, bPseudoTest); + hal_EfuseSwitchToBank(padapter, 0); while (AVAILABLE_EFUSE_ADDR(eFuse_Addr)) { - efuse_OneByteRead(padapter, eFuse_Addr++, &efuseHeader, bPseudoTest); + efuse_OneByteRead(padapter, eFuse_Addr++, &efuseHeader); if (efuseHeader == 0xFF) break; @@ -709,7 +661,7 @@ static void hal_ReadEFuse_WiFi( if (EXT_HEADER(efuseHeader)) { /* extended header */ offset = GET_HDR_OFFSET_2_0(efuseHeader); - efuse_OneByteRead(padapter, eFuse_Addr++, &efuseExtHdr, bPseudoTest); + efuse_OneByteRead(padapter, eFuse_Addr++, &efuseExtHdr); if (ALL_WORDS_DISABLED(efuseExtHdr)) continue; @@ -728,10 +680,10 @@ static void hal_ReadEFuse_WiFi( for (i = 0; i < EFUSE_MAX_WORD_UNIT; i++) { /* Check word enable condition in the section */ if (!(wden & (0x01<<i))) { - efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData, bPseudoTest); + efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData); efuseTbl[addr] = efuseData; - efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData, bPseudoTest); + efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData); efuseTbl[addr+1] = efuseData; } addr += 2; @@ -746,19 +698,12 @@ static void hal_ReadEFuse_WiFi( pbuf[i] = efuseTbl[_offset+i]; /* Calculate Efuse utilization */ - EFUSE_GetEfuseDefinition(padapter, EFUSE_WIFI, TYPE_AVAILABLE_EFUSE_BYTES_TOTAL, &total, bPseudoTest); + Hal_GetEfuseDefinition(padapter, EFUSE_WIFI, TYPE_AVAILABLE_EFUSE_BYTES_TOTAL, &total); used = eFuse_Addr - 1; efuse_usage = (u8)((used*100)/total); - if (bPseudoTest) { -#ifdef HAL_EFUSE_MEMORY - pEfuseHal->fakeEfuseUsedBytes = used; -#else - fakeEfuseUsedBytes = used; -#endif - } else { - rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BYTES, (u8 *)&used); - rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_USAGE, (u8 *)&efuse_usage); - } + + rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BYTES, (u8 *)&used); + rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_USAGE, (u8 *)&efuse_usage); kfree(efuseTbl); } @@ -767,14 +712,9 @@ static void hal_ReadEFuse_BT( struct adapter *padapter, u16 _offset, u16 _size_byte, - u8 *pbuf, - bool bPseudoTest + u8 *pbuf ) { -#ifdef HAL_EFUSE_MEMORY - struct hal_com_data *pHalData = GET_HAL_DATA(padapter); - struct efuse_hal *pEfuseHal = &pHalData->EfuseHal; -#endif u8 *efuseTbl; u8 bank; u16 eFuse_Addr; @@ -797,16 +737,16 @@ static void hal_ReadEFuse_BT( /* 0xff will be efuse default value instead of 0x00. */ memset(efuseTbl, 0xFF, EFUSE_BT_MAP_LEN); - EFUSE_GetEfuseDefinition(padapter, EFUSE_BT, TYPE_AVAILABLE_EFUSE_BYTES_BANK, &total, bPseudoTest); + Hal_GetEfuseDefinition(padapter, EFUSE_BT, TYPE_AVAILABLE_EFUSE_BYTES_BANK, &total); for (bank = 1; bank < 3; bank++) { /* 8723b Max bake 0~2 */ - if (hal_EfuseSwitchToBank(padapter, bank, bPseudoTest) == false) + if (hal_EfuseSwitchToBank(padapter, bank) == false) goto exit; eFuse_Addr = 0; while (AVAILABLE_EFUSE_ADDR(eFuse_Addr)) { - efuse_OneByteRead(padapter, eFuse_Addr++, &efuseHeader, bPseudoTest); + efuse_OneByteRead(padapter, eFuse_Addr++, &efuseHeader); if (efuseHeader == 0xFF) break; @@ -814,7 +754,7 @@ static void hal_ReadEFuse_BT( if (EXT_HEADER(efuseHeader)) { /* extended header */ offset = GET_HDR_OFFSET_2_0(efuseHeader); - efuse_OneByteRead(padapter, eFuse_Addr++, &efuseExtHdr, bPseudoTest); + efuse_OneByteRead(padapter, eFuse_Addr++, &efuseExtHdr); if (ALL_WORDS_DISABLED(efuseExtHdr)) continue; @@ -832,10 +772,10 @@ static void hal_ReadEFuse_BT( for (i = 0; i < EFUSE_MAX_WORD_UNIT; i++) { /* Check word enable condition in the section */ if (!(wden & (0x01<<i))) { - efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData, bPseudoTest); + efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData); efuseTbl[addr] = efuseData; - efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData, bPseudoTest); + efuse_OneByteRead(padapter, eFuse_Addr++, &efuseData); efuseTbl[addr+1] = efuseData; } addr += 2; @@ -851,7 +791,7 @@ static void hal_ReadEFuse_BT( } /* switch bank back to bank 0 for later BT and wifi use. */ - hal_EfuseSwitchToBank(padapter, 0, bPseudoTest); + hal_EfuseSwitchToBank(padapter, 0); /* Copy from Efuse map to output pointer memory!!! */ for (i = 0; i < _size_byte; i++) @@ -860,19 +800,12 @@ static void hal_ReadEFuse_BT( /* */ /* Calculate Efuse utilization. */ /* */ - EFUSE_GetEfuseDefinition(padapter, EFUSE_BT, TYPE_AVAILABLE_EFUSE_BYTES_TOTAL, &total, bPseudoTest); + Hal_GetEfuseDefinition(padapter, EFUSE_BT, TYPE_AVAILABLE_EFUSE_BYTES_TOTAL, &total); used = (EFUSE_BT_REAL_BANK_CONTENT_LEN*(bank-1)) + eFuse_Addr - 1; efuse_usage = (u8)((used*100)/total); - if (bPseudoTest) { -#ifdef HAL_EFUSE_MEMORY - pEfuseHal->fakeBTEfuseUsedBytes = used; -#else - fakeBTEfuseUsedBytes = used; -#endif - } else { - rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BT_BYTES, (u8 *)&used); - rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BT_USAGE, (u8 *)&efuse_usage); - } + + rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BT_BYTES, (u8 *)&used); + rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BT_USAGE, (u8 *)&efuse_usage); exit: kfree(efuseTbl); @@ -883,198 +816,13 @@ void Hal_ReadEFuse( u8 efuseType, u16 _offset, u16 _size_byte, - u8 *pbuf, - bool bPseudoTest -) -{ - if (efuseType == EFUSE_WIFI) - hal_ReadEFuse_WiFi(padapter, _offset, _size_byte, pbuf, bPseudoTest); - else - hal_ReadEFuse_BT(padapter, _offset, _size_byte, pbuf, bPseudoTest); -} - -static u16 hal_EfuseGetCurrentSize_WiFi( - struct adapter *padapter, bool bPseudoTest -) -{ -#ifdef HAL_EFUSE_MEMORY - struct hal_com_data *pHalData = GET_HAL_DATA(padapter); - struct efuse_hal *pEfuseHal = &pHalData->EfuseHal; -#endif - u16 efuse_addr = 0; - u16 start_addr = 0; /* for debug */ - u8 hworden = 0; - u8 efuse_data, word_cnts = 0; - u32 count = 0; /* for debug */ - - - if (bPseudoTest) { -#ifdef HAL_EFUSE_MEMORY - efuse_addr = (u16)pEfuseHal->fakeEfuseUsedBytes; -#else - efuse_addr = (u16)fakeEfuseUsedBytes; -#endif - } else - rtw_hal_get_hwreg(padapter, HW_VAR_EFUSE_BYTES, (u8 *)&efuse_addr); - - start_addr = efuse_addr; - - /* switch bank back to bank 0 for later BT and wifi use. */ - hal_EfuseSwitchToBank(padapter, 0, bPseudoTest); - - count = 0; - while (AVAILABLE_EFUSE_ADDR(efuse_addr)) { - if (efuse_OneByteRead(padapter, efuse_addr, &efuse_data, bPseudoTest) == false) - goto error; - - if (efuse_data == 0xFF) - break; - - if ((start_addr != 0) && (efuse_addr == start_addr)) { - count++; - - efuse_data = 0xFF; - if (count < 4) { - /* try again! */ - - if (count > 2) { - /* try again form address 0 */ - efuse_addr = 0; - start_addr = 0; - } - - continue; - } - - goto error; - } - - if (EXT_HEADER(efuse_data)) { - efuse_addr++; - efuse_OneByteRead(padapter, efuse_addr, &efuse_data, bPseudoTest); - if (ALL_WORDS_DISABLED(efuse_data)) - continue; - - hworden = efuse_data & 0x0F; - } else { - hworden = efuse_data & 0x0F; - } - - word_cnts = Efuse_CalculateWordCnts(hworden); - efuse_addr += (word_cnts*2)+1; - } - - if (bPseudoTest) { -#ifdef HAL_EFUSE_MEMORY - pEfuseHal->fakeEfuseUsedBytes = efuse_addr; -#else - fakeEfuseUsedBytes = efuse_addr; -#endif - } else - rtw_hal_set_hwreg(padapter, HW_VAR_EFUSE_BYTES, (u8 *)&efuse_addr); - - goto exit; - -error: - /* report max size to prevent write efuse */ - EFUSE_GetEfuseDefinition(padapter, EFUSE_WIFI, TYPE_AVAILABLE_EFUSE_BYTES_TOTAL, &efuse_addr, bPseudoTest); - -exit: - - return efuse_addr; -} - -static u16 hal_EfuseGetCurrentSize_BT(struct adapter *padapter, u8 bPseudoTest) -{ -#ifdef HAL_EFUSE_MEMORY - struct hal_com_data *pHalData = GET_HAL_DATA(padapter); - struct efuse_hal *pEfuseHal = &pHalData->EfuseHal; -#endif - u16 btusedbytes; - u16 efuse_addr; - u8 bank, startBank; - u8 hworden = 0; - u8 efuse_data, word_cnts = 0; - u16 retU2 = 0; - - if (bPseudoTest) { -#ifdef HAL_EFUSE_MEMORY - btusedbytes = pEfuseHal->fakeBTEfuseUsedBytes; -#else - btusedbytes = fakeBTEfuseUsedBytes; -#endif - } else - rtw_hal_get_hwreg(padapter, HW_VAR_EFUSE_BT_BYTES, (u8 *)&btusedbytes); - - efuse_addr = (u16)((btusedbytes%EFUSE_BT_REAL_BANK_CONTENT_LEN)); - startBank = (u8)(1+(btusedbytes/EFUSE_BT_REAL_BANK_CONTENT_LEN)); - - EFUSE_GetEfuseDefinition(padapter, EFUSE_BT, TYPE_AVAILABLE_EFUSE_BYTES_BANK, &retU2, bPseudoTest); - - for (bank = startBank; bank < 3; bank++) { - if (hal_EfuseSwitchToBank(padapter, bank, bPseudoTest) == false) - /* bank = EFUSE_MAX_BANK; */ - break; - - /* only when bank is switched we have to reset the efuse_addr. */ - if (bank != startBank) - efuse_addr = 0; - - while (AVAILABLE_EFUSE_ADDR(efuse_addr)) { - if (efuse_OneByteRead(padapter, efuse_addr, - &efuse_data, bPseudoTest) == false) - /* bank = EFUSE_MAX_BANK; */ - break; - - if (efuse_data == 0xFF) - break; - - if (EXT_HEADER(efuse_data)) { - efuse_addr++; - efuse_OneByteRead(padapter, efuse_addr, &efuse_data, bPseudoTest); - - if (ALL_WORDS_DISABLED(efuse_data)) { - efuse_addr++; - continue; - } - - hworden = efuse_data & 0x0F; - } else { - hworden = efuse_data & 0x0F; - } - - word_cnts = Efuse_CalculateWordCnts(hworden); - /* read next header */ - efuse_addr += (word_cnts*2)+1; - } - - /* Check if we need to check next bank efuse */ - if (efuse_addr < retU2) - break; /* don't need to check next bank. */ - } - - retU2 = ((bank-1)*EFUSE_BT_REAL_BANK_CONTENT_LEN)+efuse_addr; - if (bPseudoTest) { - pEfuseHal->fakeBTEfuseUsedBytes = retU2; - } else { - pEfuseHal->BTEfuseUsedBytes = retU2; - } - - return retU2; -} - -u16 Hal_EfuseGetCurrentSize( - struct adapter *padapter, u8 efuseType, bool bPseudoTest + u8 *pbuf ) { - u16 ret = 0; - if (efuseType == EFUSE_WIFI) - ret = hal_EfuseGetCurrentSize_WiFi(padapter, bPseudoTest); + hal_ReadEFuse_WiFi(padapter, _offset, _size_byte, pbuf); else - ret = hal_EfuseGetCurrentSize_BT(padapter, bPseudoTest); - - return ret; + hal_ReadEFuse_BT(padapter, _offset, _size_byte, pbuf); } static struct hal_version ReadChipVersion8723B(struct adapter *padapter) @@ -1438,12 +1186,12 @@ void Hal_InitPGData(struct adapter *padapter, u8 *PROMContent) if (!pEEPROM->bautoload_fail_flag) { /* autoload OK. */ if (!pEEPROM->EepromOrEfuse) { /* Read EFUSE real map to shadow. */ - EFUSE_ShadowMapUpdate(padapter, EFUSE_WIFI, false); + EFUSE_ShadowMapUpdate(padapter, EFUSE_WIFI); memcpy((void *)PROMContent, (void *)pEEPROM->efuse_eeprom_data, HWSET_MAX_SIZE_8723B); } } else {/* autoload fail */ if (!pEEPROM->EepromOrEfuse) - EFUSE_ShadowMapUpdate(padapter, EFUSE_WIFI, false); + EFUSE_ShadowMapUpdate(padapter, EFUSE_WIFI); memcpy((void *)PROMContent, (void *)pEEPROM->efuse_eeprom_data, HWSET_MAX_SIZE_8723B); } } @@ -1700,9 +1448,9 @@ void Hal_EfuseParsePackageType_8723B( u8 package; u8 efuseContent; - Efuse_PowerSwitch(padapter, false, true); - efuse_OneByteRead(padapter, 0x1FB, &efuseContent, false); - Efuse_PowerSwitch(padapter, false, false); + Hal_EfusePowerSwitch(padapter, true); + efuse_OneByteRead(padapter, 0x1FB, &efuseContent); + Hal_EfusePowerSwitch(padapter, false); package = efuseContent & 0x7; switch (package) { @@ -1763,14 +1511,6 @@ void Hal_EfuseParseCustomerID_8723B( pHalData->EEPROMCustomerID = 0; } -void Hal_EfuseParseAntennaDiversity_8723B( - struct adapter *padapter, - u8 *hwinfo, - bool AutoLoadFail -) -{ -} - void Hal_EfuseParseXtal_8723B( struct adapter *padapter, u8 *hwinfo, bool AutoLoadFail ) diff --git a/drivers/staging/rtl8723bs/hal/rtl8723bs_recv.c b/drivers/staging/rtl8723bs/hal/rtl8723bs_recv.c index 28c914ec2604..399edfbf8ec6 100644 --- a/drivers/staging/rtl8723bs/hal/rtl8723bs_recv.c +++ b/drivers/staging/rtl8723bs/hal/rtl8723bs_recv.c @@ -431,7 +431,8 @@ initbuferror: precvpriv->free_recv_buf_queue_cnt = 0; for (i = 0; i < n ; i++) { list_del_init(&precvbuf->list); - rtw_os_recvbuf_resource_free(padapter, precvbuf); + if (precvbuf->pskb) + dev_kfree_skb_any(precvbuf->pskb); precvbuf++; } precvpriv->precv_buf = NULL; @@ -467,7 +468,8 @@ void rtl8723bs_free_recv_priv(struct adapter *padapter) precvpriv->free_recv_buf_queue_cnt = 0; for (i = 0; i < NR_RECVBUFF; i++) { list_del_init(&precvbuf->list); - rtw_os_recvbuf_resource_free(padapter, precvbuf); + if (precvbuf->pskb) + dev_kfree_skb_any(precvbuf->pskb); precvbuf++; } precvpriv->precv_buf = NULL; diff --git a/drivers/staging/rtl8723bs/hal/rtl8723bs_xmit.c b/drivers/staging/rtl8723bs/hal/rtl8723bs_xmit.c index 842e19b53421..abb6fdfe7e1f 100644 --- a/drivers/staging/rtl8723bs/hal/rtl8723bs_xmit.c +++ b/drivers/staging/rtl8723bs/hal/rtl8723bs_xmit.c @@ -76,7 +76,7 @@ query_free_page: /* check if hardware tx fifo page is enough */ if (!rtw_hal_sdio_query_tx_freepage(pri_padapter, PageIdx, pxmitbuf->pg_num)) { if (!bUpdatePageNum) { - /* Total number of page is NOT available, so update current FIFO status */ + /* Total page count is not available, so update current FIFO status */ HalQueryTxBufferStatus8723BSdio(padapter); bUpdatePageNum = true; goto query_free_page; diff --git a/drivers/staging/rtl8723bs/hal/sdio_halinit.c b/drivers/staging/rtl8723bs/hal/sdio_halinit.c index 73561a63401e..7fcb874d0eb3 100644 --- a/drivers/staging/rtl8723bs/hal/sdio_halinit.c +++ b/drivers/staging/rtl8723bs/hal/sdio_halinit.c @@ -1071,7 +1071,6 @@ static void _ReadEfuseInfo8723BS(struct adapter *padapter) Hal_EfuseParseChnlPlan_8723B(padapter, hwinfo, pEEPROM->bautoload_fail_flag); Hal_EfuseParseXtal_8723B(padapter, hwinfo, pEEPROM->bautoload_fail_flag); Hal_EfuseParseThermalMeter_8723B(padapter, hwinfo, pEEPROM->bautoload_fail_flag); - Hal_EfuseParseAntennaDiversity_8723B(padapter, hwinfo, pEEPROM->bautoload_fail_flag); Hal_EfuseParseCustomerID_8723B(padapter, hwinfo, pEEPROM->bautoload_fail_flag); Hal_EfuseParseVoltage_8723B(padapter, hwinfo, pEEPROM->bautoload_fail_flag); diff --git a/drivers/staging/rtl8723bs/include/basic_types.h b/drivers/staging/rtl8723bs/include/basic_types.h index 24626e65fc7f..1c2da18e6210 100644 --- a/drivers/staging/rtl8723bs/include/basic_types.h +++ b/drivers/staging/rtl8723bs/include/basic_types.h @@ -22,11 +22,11 @@ /* TODO: Belows are Sync from SD7-Driver. It is necessary to check correctness */ /* - *Call endian free function when + * Call endian free function when * 1. Read/write packet content. * 2. Before write integer to IO. * 3. After read integer from IO. -*/ + */ /* */ /* Byte Swapping routine. */ @@ -68,7 +68,8 @@ (*((u32 *)(_ptr))) = EF2BYTE(_val); \ } while (0) -/* Create a bit mask +/* + * Create a bit mask * Examples: * BIT_LEN_MASK_32(0) => 0x00000000 * BIT_LEN_MASK_32(1) => 0x00000001 @@ -82,7 +83,8 @@ #define BIT_LEN_MASK_8(__bitlen) \ (0xFF >> (8 - (__bitlen))) -/* Create an offset bit mask +/* + * Create an offset bit mask * Examples: * BIT_OFFSET_LEN_MASK_32(0, 2) => 0x00000003 * BIT_OFFSET_LEN_MASK_32(16, 2) => 0x00030000 @@ -94,7 +96,8 @@ #define BIT_OFFSET_LEN_MASK_8(__bitoffset, __bitlen) \ (BIT_LEN_MASK_8(__bitlen) << (__bitoffset)) -/*Description: +/* + * Description: * Return 4-byte value in host byte ordering from * 4-byte pointer in little-endian system. */ @@ -105,11 +108,11 @@ #define LE_P1BYTE_TO_HOST_1BYTE(__pstart) \ (EF1BYTE(*((u8 *)(__pstart)))) -/* */ -/* Description: */ -/* Translate subfield (continuous bits in little-endian) of 4-byte value in litten byte to */ -/* 4-byte value in host byte ordering. */ -/* */ +/* + * Description: + * Translate subfield (continuous bits in little-endian) of 4-byte value in + * little byte to 4-byte value in host byte ordering. + */ #define LE_BITS_TO_4BYTE(__pstart, __bitoffset, __bitlen) \ (\ (LE_P4BYTE_TO_HOST_4BYTE(__pstart) >> (__bitoffset)) & \ @@ -126,11 +129,11 @@ BIT_LEN_MASK_8(__bitlen) \ ) -/* */ -/* Description: */ -/* Mask subfield (continuous bits in little-endian) of 4-byte value in litten byte oredering */ -/* and return the result in 4-byte value in host byte ordering. */ -/* */ +/* + * Description: + * Mask subfield (continuous bits in little-endian) of 4-byte value in little + * byte ordering and return the result in 4-byte value in host byte ordering. + */ #define LE_BITS_CLEARED_TO_4BYTE(__pstart, __bitoffset, __bitlen) \ (\ LE_P4BYTE_TO_HOST_4BYTE(__pstart) & \ @@ -147,10 +150,10 @@ (~BIT_OFFSET_LEN_MASK_8(__bitoffset, __bitlen)) \ ) -/* */ -/* Description: */ -/* Set subfield of little-endian 4-byte value to specified value. */ -/* */ +/* + * Description: + * Set subfield of little-endian 4-byte value to specified value. + */ #define SET_BITS_TO_LE_4BYTE(__pstart, __bitoffset, __bitlen, __val) \ *((u32 *)(__pstart)) = \ ( \ diff --git a/drivers/staging/rtl8723bs/include/drv_types.h b/drivers/staging/rtl8723bs/include/drv_types.h index 080c321665c0..dd9018aa4ee5 100644 --- a/drivers/staging/rtl8723bs/include/drv_types.h +++ b/drivers/staging/rtl8723bs/include/drv_types.h @@ -33,14 +33,12 @@ #include <xmit_osdep.h> #include <rtw_recv.h> -#include <recv_osdep.h> #include <rtw_efuse.h> #include <hal_intf.h> #include <hal_com.h> #include <rtw_qos.h> #include <rtw_pwrctrl.h> #include <rtw_mlme.h> -#include <mlme_osdep.h> #include <rtw_io.h> #include <rtw_ioctl_set.h> #include <osdep_intf.h> diff --git a/drivers/staging/rtl8723bs/include/hal_intf.h b/drivers/staging/rtl8723bs/include/hal_intf.h index 67d51e55bd44..2fa2382ad5f3 100644 --- a/drivers/staging/rtl8723bs/include/hal_intf.h +++ b/drivers/staging/rtl8723bs/include/hal_intf.h @@ -265,11 +265,10 @@ u8 GetHalDefVar8723BSDIO(struct adapter *Adapter, enum hal_def_variable eVariabl u8 SetHalDefVar8723BSDIO(struct adapter *Adapter, enum hal_def_variable eVariable, void *pValue); void UpdateHalRAMask8723B(struct adapter *padapter, u32 mac_id, u8 rssi_level); void rtl8723b_SetBeaconRelatedRegisters(struct adapter *padapter); -void Hal_EfusePowerSwitch(struct adapter *padapter, u8 bWrite, u8 PwrState); +void Hal_EfusePowerSwitch(struct adapter *padapter, u8 PwrState); void Hal_ReadEFuse(struct adapter *padapter, u8 efuseType, u16 _offset, - u16 _size_byte, u8 *pbuf, bool bPseudoTest); + u16 _size_byte, u8 *pbuf); void Hal_GetEfuseDefinition(struct adapter *padapter, u8 efuseType, u8 type, - void *pOut, bool bPseudoTest); -u16 Hal_EfuseGetCurrentSize(struct adapter *padapter, u8 efuseType, bool bPseudoTest); + void *pOut); void hal_notch_filter_8723b(struct adapter *adapter, bool enable); #endif /* __HAL_INTF_H__ */ diff --git a/drivers/staging/rtl8723bs/include/mlme_osdep.h b/drivers/staging/rtl8723bs/include/mlme_osdep.h deleted file mode 100644 index f0d19637fb0f..000000000000 --- a/drivers/staging/rtl8723bs/include/mlme_osdep.h +++ /dev/null @@ -1,19 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/****************************************************************************** - * - * Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved. - * - ******************************************************************************/ -#ifndef __MLME_OSDEP_H_ -#define __MLME_OSDEP_H_ - - -extern void rtw_init_mlme_timer(struct adapter *padapter); -extern void rtw_os_indicate_disconnect(struct adapter *adapter); -extern void rtw_os_indicate_connect(struct adapter *adapter); -void rtw_os_indicate_scan_done(struct adapter *padapter, bool aborted); -extern void rtw_report_sec_ie(struct adapter *adapter, u8 authmode, u8 *sec_ie); - -void rtw_reset_securitypriv(struct adapter *adapter); - -#endif /* _MLME_OSDEP_H_ */ diff --git a/drivers/staging/rtl8723bs/include/recv_osdep.h b/drivers/staging/rtl8723bs/include/recv_osdep.h deleted file mode 100644 index 83330ea98fbf..000000000000 --- a/drivers/staging/rtl8723bs/include/recv_osdep.h +++ /dev/null @@ -1,40 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/****************************************************************************** - * - * Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved. - * - ******************************************************************************/ -#ifndef __RECV_OSDEP_H_ -#define __RECV_OSDEP_H_ - - -extern signed int _rtw_init_recv_priv(struct recv_priv *precvpriv, struct adapter *padapter); -extern void _rtw_free_recv_priv(struct recv_priv *precvpriv); - - -extern s32 rtw_recv_entry(union recv_frame *precv_frame); -extern int rtw_recv_indicatepkt(struct adapter *adapter, union recv_frame *precv_frame); -extern void rtw_recv_returnpacket(struct net_device *cnxt, struct sk_buff *preturnedpkt); - -extern void rtw_handle_tkip_mic_err(struct adapter *padapter, u8 bgroup); - -int rtw_init_recv_priv(struct recv_priv *precvpriv, struct adapter *padapter); -void rtw_free_recv_priv(struct recv_priv *precvpriv); - - -void rtw_os_recv_resource_alloc(struct adapter *padapter, union recv_frame *precvframe); -void rtw_os_recv_resource_free(struct recv_priv *precvpriv); - - -void rtw_os_free_recvframe(union recv_frame *precvframe); - - -void rtw_os_recvbuf_resource_free(struct adapter *padapter, struct recv_buf *precvbuf); - -struct sk_buff *rtw_os_alloc_msdu_pkt(union recv_frame *prframe, u16 nSubframe_Length, u8 *pdata); -void rtw_os_recv_indicate_pkt(struct adapter *padapter, struct sk_buff *pkt, struct rx_pkt_attrib *pattrib); - -void rtw_init_recv_timer(struct recv_reorder_ctrl *preorder_ctrl); - - -#endif /* */ diff --git a/drivers/staging/rtl8723bs/include/rtl8723b_hal.h b/drivers/staging/rtl8723bs/include/rtl8723b_hal.h index 40ff96d3cf74..2ed1fc8549ec 100644 --- a/drivers/staging/rtl8723bs/include/rtl8723b_hal.h +++ b/drivers/staging/rtl8723bs/include/rtl8723b_hal.h @@ -210,8 +210,6 @@ void Hal_EfuseParseChnlPlan_8723B(struct adapter *padapter, u8 *hwinfo, bool AutoLoadFail); void Hal_EfuseParseCustomerID_8723B(struct adapter *padapter, u8 *hwinfo, bool AutoLoadFail); -void Hal_EfuseParseAntennaDiversity_8723B(struct adapter *padapter, u8 *hwinfo, - bool AutoLoadFail); void Hal_EfuseParseXtal_8723B(struct adapter *padapter, u8 *hwinfo, bool AutoLoadFail); void Hal_EfuseParseThermalMeter_8723B(struct adapter *padapter, u8 *hwinfo, diff --git a/drivers/staging/rtl8723bs/include/rtw_efuse.h b/drivers/staging/rtl8723bs/include/rtw_efuse.h index 669565fa1c69..936b204b8830 100644 --- a/drivers/staging/rtl8723bs/include/rtw_efuse.h +++ b/drivers/staging/rtl8723bs/include/rtw_efuse.h @@ -1,9 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ -/****************************************************************************** - * - * Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved. - * - ******************************************************************************/ +/* Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved. */ + #ifndef __RTW_EFUSE_H__ #define __RTW_EFUSE_H__ @@ -91,14 +88,10 @@ extern u8 fakeBTEfuseModifiedMap[]; /*------------------------Export global variable----------------------------*/ u8 Efuse_CalculateWordCnts(u8 word_en); -void EFUSE_GetEfuseDefinition(struct adapter *padapter, u8 efuseType, u8 type, void *pOut, bool bPseudoTest); -u8 efuse_OneByteRead(struct adapter *padapter, u16 addr, u8 *data, bool bPseudoTest); -u8 efuse_OneByteWrite(struct adapter *padapter, u16 addr, u8 data, bool bPseudoTest); - -void Efuse_PowerSwitch(struct adapter *padapter, u8 bWrite, u8 PwrState); +u8 efuse_OneByteRead(struct adapter *padapter, u16 addr, u8 *data); u8 EFUSE_Read1Byte(struct adapter *padapter, u16 Address); -void EFUSE_ShadowMapUpdate(struct adapter *padapter, u8 efuseType, bool bPseudoTest); +void EFUSE_ShadowMapUpdate(struct adapter *padapter, u8 efuseType); void EFUSE_ShadowRead(struct adapter *padapter, u8 Type, u16 Offset, u32 *Value); void Rtw_Hal_ReadMACAddrFromFile(struct adapter *padapter); u32 Rtw_Hal_readPGDataFromConfigFile(struct adapter *padapter); diff --git a/drivers/staging/rtl8723bs/include/rtw_mlme.h b/drivers/staging/rtl8723bs/include/rtw_mlme.h index 3cf68b85eb32..4c15d0194d4f 100644 --- a/drivers/staging/rtl8723bs/include/rtw_mlme.h +++ b/drivers/staging/rtl8723bs/include/rtw_mlme.h @@ -395,5 +395,6 @@ u8 rtw_to_roam(struct adapter *adapter); int rtw_select_roaming_candidate(struct mlme_priv *pmlmepriv); void rtw_sta_media_status_rpt(struct adapter *adapter, struct sta_info *psta, u32 mstatus); +void rtw_reset_securitypriv(struct adapter *adapter); #endif /* __RTL871X_MLME_H_ */ diff --git a/drivers/staging/rtl8723bs/include/rtw_mlme_ext.h b/drivers/staging/rtl8723bs/include/rtw_mlme_ext.h index 2080408743ef..53fac838c36a 100644 --- a/drivers/staging/rtl8723bs/include/rtw_mlme_ext.h +++ b/drivers/staging/rtl8723bs/include/rtw_mlme_ext.h @@ -426,8 +426,6 @@ void init_mlme_default_rate_set(struct adapter *padapter); void init_mlme_ext_priv(struct adapter *padapter); int init_hw_mlme_ext(struct adapter *padapter); void free_mlme_ext_priv(struct mlme_ext_priv *pmlmeext); -extern void init_mlme_ext_timer(struct adapter *padapter); -extern void init_addba_retry_timer(struct adapter *padapter, struct sta_info *psta); extern struct xmit_frame *alloc_mgtxmitframe(struct xmit_priv *pxmitpriv); /* void fill_fwpriv(struct adapter *padapter, struct fw_priv *pfwpriv); */ diff --git a/drivers/staging/rtl8723bs/include/rtw_recv.h b/drivers/staging/rtl8723bs/include/rtw_recv.h index aa9f9d5ecd01..8e45871f07f0 100644 --- a/drivers/staging/rtl8723bs/include/rtw_recv.h +++ b/drivers/staging/rtl8723bs/include/rtw_recv.h @@ -342,6 +342,10 @@ struct recv_buf *rtw_dequeue_recvbuf(struct __queue *queue); void rtw_reordering_ctrl_timeout_handler(struct timer_list *t); +signed int _rtw_init_recv_priv(struct recv_priv *precvpriv, struct adapter *padapter); +void _rtw_free_recv_priv(struct recv_priv *precvpriv); +s32 rtw_recv_entry(union recv_frame *precv_frame); + static inline u8 *get_rxmem(union recv_frame *precvframe) { /* always return rx_head... */ diff --git a/drivers/staging/rtl8723bs/os_dep/mlme_linux.c b/drivers/staging/rtl8723bs/os_dep/mlme_linux.c deleted file mode 100644 index fd4ae870a617..000000000000 --- a/drivers/staging/rtl8723bs/os_dep/mlme_linux.c +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/****************************************************************************** - * - * Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved. - * - ******************************************************************************/ -#include <drv_types.h> - -static void _dynamic_check_timer_handler(struct timer_list *t) -{ - struct adapter *adapter = - timer_container_of(adapter, t, mlmepriv.dynamic_chk_timer); - - rtw_dynamic_check_timer_handler(adapter); - - _set_timer(&adapter->mlmepriv.dynamic_chk_timer, 2000); -} - -static void _rtw_set_scan_deny_timer_hdl(struct timer_list *t) -{ - struct adapter *adapter = - timer_container_of(adapter, t, mlmepriv.set_scan_deny_timer); - - rtw_clear_scan_deny(adapter); -} - -void rtw_init_mlme_timer(struct adapter *padapter) -{ - struct mlme_priv *pmlmepriv = &padapter->mlmepriv; - - timer_setup(&pmlmepriv->assoc_timer, _rtw_join_timeout_handler, 0); - timer_setup(&pmlmepriv->scan_to_timer, rtw_scan_timeout_handler, 0); - timer_setup(&pmlmepriv->dynamic_chk_timer, - _dynamic_check_timer_handler, 0); - timer_setup(&pmlmepriv->set_scan_deny_timer, - _rtw_set_scan_deny_timer_hdl, 0); -} - -void rtw_os_indicate_connect(struct adapter *adapter) -{ - struct mlme_priv *pmlmepriv = &(adapter->mlmepriv); - - if ((check_fwstate(pmlmepriv, WIFI_ADHOC_MASTER_STATE) == true) || - (check_fwstate(pmlmepriv, WIFI_ADHOC_STATE) == true)) { - rtw_cfg80211_ibss_indicate_connect(adapter); - } else { - rtw_cfg80211_indicate_connect(adapter); - } - - netif_carrier_on(adapter->pnetdev); - - if (adapter->pid[2] != 0) - rtw_signal_process(adapter->pid[2], SIGALRM); -} - -void rtw_os_indicate_scan_done(struct adapter *padapter, bool aborted) -{ - rtw_cfg80211_indicate_scan_done(padapter, aborted); -} - -static struct rt_pmkid_list backupPMKIDList[NUM_PMKID_CACHE]; -void rtw_reset_securitypriv(struct adapter *adapter) -{ - u8 backupPMKIDIndex = 0; - u8 backupTKIPCountermeasure = 0x00; - u32 backupTKIPcountermeasure_time = 0; - /* add for CONFIG_IEEE80211W, none 11w also can use */ - struct mlme_ext_priv *pmlmeext = &adapter->mlmeextpriv; - - spin_lock_bh(&adapter->security_key_mutex); - - if (adapter->securitypriv.dot11AuthAlgrthm == dot11AuthAlgrthm_8021X) { - /* 802.1x */ - /* Added by Albert 2009/02/18 */ - /* We have to backup the PMK information for WiFi PMK Caching test item. */ - /* */ - /* Backup the btkip_countermeasure information. */ - /* When the countermeasure is trigger, the driver have to disconnect with AP for 60 seconds. */ - - memcpy(&backupPMKIDList[0], &adapter->securitypriv.PMKIDList[0], sizeof(struct rt_pmkid_list) * NUM_PMKID_CACHE); - backupPMKIDIndex = adapter->securitypriv.PMKIDIndex; - backupTKIPCountermeasure = adapter->securitypriv.btkip_countermeasure; - backupTKIPcountermeasure_time = adapter->securitypriv.btkip_countermeasure_time; - - /* reset RX BIP packet number */ - pmlmeext->mgnt_80211w_IPN_rx = 0; - - memset((unsigned char *)&adapter->securitypriv, 0, sizeof(struct security_priv)); - - /* Added by Albert 2009/02/18 */ - /* Restore the PMK information to securitypriv structure for the following connection. */ - memcpy(&adapter->securitypriv.PMKIDList[0], &backupPMKIDList[0], sizeof(struct rt_pmkid_list) * NUM_PMKID_CACHE); - adapter->securitypriv.PMKIDIndex = backupPMKIDIndex; - adapter->securitypriv.btkip_countermeasure = backupTKIPCountermeasure; - adapter->securitypriv.btkip_countermeasure_time = backupTKIPcountermeasure_time; - - adapter->securitypriv.ndisauthtype = Ndis802_11AuthModeOpen; - adapter->securitypriv.ndisencryptstatus = Ndis802_11WEPDisabled; - - } else { - /* reset values in securitypriv */ - /* if (adapter->mlmepriv.fw_state & WIFI_STATION_STATE) */ - /* */ - struct security_priv *psec_priv = &adapter->securitypriv; - - psec_priv->dot11AuthAlgrthm = dot11AuthAlgrthm_Open; /* open system */ - psec_priv->dot11PrivacyAlgrthm = _NO_PRIVACY_; - psec_priv->dot11PrivacyKeyIndex = 0; - - psec_priv->dot118021XGrpPrivacy = _NO_PRIVACY_; - psec_priv->dot118021XGrpKeyid = 1; - - psec_priv->ndisauthtype = Ndis802_11AuthModeOpen; - psec_priv->ndisencryptstatus = Ndis802_11WEPDisabled; - /* */ - } - /* add for CONFIG_IEEE80211W, none 11w also can use */ - spin_unlock_bh(&adapter->security_key_mutex); -} - -void rtw_os_indicate_disconnect(struct adapter *adapter) -{ - /* struct rt_pmkid_list backupPMKIDList[ NUM_PMKID_CACHE ]; */ - - netif_carrier_off(adapter->pnetdev); /* Do it first for tx broadcast pkt after disconnection issue! */ - - rtw_cfg80211_indicate_disconnect(adapter); - - /* modify for CONFIG_IEEE80211W, none 11w also can use the same command */ - rtw_reset_securitypriv_cmd(adapter); -} - -void rtw_report_sec_ie(struct adapter *adapter, u8 authmode, u8 *sec_ie) -{ - uint len; - u8 *buff, *p, i; - union iwreq_data wrqu; - - buff = NULL; - if (authmode == WLAN_EID_VENDOR_SPECIFIC) { - buff = rtw_zmalloc(IW_CUSTOM_MAX); - if (!buff) - return; - - p = buff; - - p += scnprintf(p, IW_CUSTOM_MAX - (p - buff), "ASSOCINFO(ReqIEs ="); - - len = sec_ie[1] + 2; - len = (len < IW_CUSTOM_MAX) ? len : IW_CUSTOM_MAX; - - for (i = 0; i < len; i++) - p += scnprintf(p, IW_CUSTOM_MAX - (p - buff), "%02x", sec_ie[i]); - - p += scnprintf(p, IW_CUSTOM_MAX - (p - buff), ")"); - - memset(&wrqu, 0, sizeof(wrqu)); - - wrqu.data.length = p - buff; - - wrqu.data.length = (wrqu.data.length < IW_CUSTOM_MAX) ? wrqu.data.length : IW_CUSTOM_MAX; - - kfree(buff); - } -} - -void init_addba_retry_timer(struct adapter *padapter, struct sta_info *psta) -{ - timer_setup(&psta->addba_retry_timer, addba_timer_hdl, 0); -} - -void init_mlme_ext_timer(struct adapter *padapter) -{ - struct mlme_ext_priv *pmlmeext = &padapter->mlmeextpriv; - - timer_setup(&pmlmeext->survey_timer, survey_timer_hdl, 0); - timer_setup(&pmlmeext->link_timer, link_timer_hdl, 0); - timer_setup(&pmlmeext->sa_query_timer, sa_query_timer_hdl, 0); -} diff --git a/drivers/staging/rtl8723bs/os_dep/recv_linux.c b/drivers/staging/rtl8723bs/os_dep/recv_linux.c deleted file mode 100644 index 98d3e4777210..000000000000 --- a/drivers/staging/rtl8723bs/os_dep/recv_linux.c +++ /dev/null @@ -1,225 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/****************************************************************************** - * - * Copyright(c) 2007 - 2011 Realtek Corporation. All rights reserved. - * - ******************************************************************************/ -#include <drv_types.h> -#include <linux/jiffies.h> -#include <net/cfg80211.h> -#include <linux/unaligned.h> - -void rtw_os_free_recvframe(union recv_frame *precvframe) -{ - if (precvframe->u.hdr.pkt) { - dev_kfree_skb_any(precvframe->u.hdr.pkt);/* free skb by driver */ - - precvframe->u.hdr.pkt = NULL; - } -} - -/* alloc os related resource in union recv_frame */ -void rtw_os_recv_resource_alloc(struct adapter *padapter, union recv_frame *precvframe) -{ - precvframe->u.hdr.pkt_newalloc = precvframe->u.hdr.pkt = NULL; -} - -/* free os related resource in union recv_frame */ -void rtw_os_recv_resource_free(struct recv_priv *precvpriv) -{ - signed int i; - union recv_frame *precvframe; - - precvframe = (union recv_frame *) precvpriv->precv_frame_buf; - - for (i = 0; i < NR_RECVFRAME; i++) { - if (precvframe->u.hdr.pkt) { - /* free skb by driver */ - dev_kfree_skb_any(precvframe->u.hdr.pkt); - precvframe->u.hdr.pkt = NULL; - } - precvframe++; - } -} - -/* free os related resource in struct recv_buf */ -void rtw_os_recvbuf_resource_free(struct adapter *padapter, struct recv_buf *precvbuf) -{ - if (precvbuf->pskb) - dev_kfree_skb_any(precvbuf->pskb); -} - -struct sk_buff *rtw_os_alloc_msdu_pkt(union recv_frame *prframe, u16 nSubframe_Length, u8 *pdata) -{ - u16 eth_type; - struct sk_buff *sub_skb; - struct rx_pkt_attrib *pattrib; - - pattrib = &prframe->u.hdr.attrib; - - sub_skb = rtw_skb_alloc(nSubframe_Length + 12); - if (!sub_skb) - return NULL; - - skb_reserve(sub_skb, 12); - skb_put_data(sub_skb, (pdata + ETH_HLEN), nSubframe_Length); - - eth_type = get_unaligned_be16(&sub_skb->data[6]); - - if (sub_skb->len >= 8 && - ((!memcmp(sub_skb->data, rfc1042_header, SNAP_SIZE) && - eth_type != ETH_P_AARP && eth_type != ETH_P_IPX) || - !memcmp(sub_skb->data, bridge_tunnel_header, SNAP_SIZE))) { - /* - * remove RFC1042 or Bridge-Tunnel encapsulation and replace - * EtherType - */ - skb_pull(sub_skb, SNAP_SIZE); - memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->src, ETH_ALEN); - memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->dst, ETH_ALEN); - } else { - __be16 len; - /* Leave Ethernet header part of hdr and full payload */ - len = htons(sub_skb->len); - memcpy(skb_push(sub_skb, 2), &len, 2); - memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->src, ETH_ALEN); - memcpy(skb_push(sub_skb, ETH_ALEN), pattrib->dst, ETH_ALEN); - } - - return sub_skb; -} - -void rtw_os_recv_indicate_pkt(struct adapter *padapter, struct sk_buff *pkt, struct rx_pkt_attrib *pattrib) -{ - struct mlme_priv *pmlmepriv = &padapter->mlmepriv; - - /* Indicate the packets to upper layer */ - if (pkt) { - if (check_fwstate(pmlmepriv, WIFI_AP_STATE) == true) { - struct sk_buff *pskb2 = NULL; - struct sta_info *psta = NULL; - struct sta_priv *pstapriv = &padapter->stapriv; - int bmcast = is_multicast_ether_addr(pattrib->dst); - - if (memcmp(pattrib->dst, myid(&padapter->eeprompriv), ETH_ALEN)) { - if (bmcast) { - psta = rtw_get_bcmc_stainfo(padapter); - pskb2 = skb_clone(pkt, GFP_ATOMIC); - } else { - psta = rtw_get_stainfo(pstapriv, pattrib->dst); - } - - if (psta) { - struct net_device *pnetdev = (struct net_device *)padapter->pnetdev; - /* skb->ip_summed = CHECKSUM_NONE; */ - pkt->dev = pnetdev; - skb_set_queue_mapping(pkt, rtw_recv_select_queue(pkt)); - - _rtw_xmit_entry(pkt, pnetdev); - - if (bmcast && pskb2) - pkt = pskb2; - else - return; - } - } else { - /* to APself */ - } - } - - pkt->protocol = eth_type_trans(pkt, padapter->pnetdev); - pkt->dev = padapter->pnetdev; - - pkt->ip_summed = CHECKSUM_NONE; - - rtw_netif_rx(padapter->pnetdev, pkt); - } -} - -void rtw_handle_tkip_mic_err(struct adapter *padapter, u8 bgroup) -{ - enum nl80211_key_type key_type = 0; - union iwreq_data wrqu; - struct iw_michaelmicfailure ev; - struct mlme_priv *pmlmepriv = &padapter->mlmepriv; - struct security_priv *psecuritypriv = &padapter->securitypriv; - unsigned long cur_time = 0; - - if (psecuritypriv->last_mic_err_time == 0) { - psecuritypriv->last_mic_err_time = jiffies; - } else { - cur_time = jiffies; - - if (cur_time - psecuritypriv->last_mic_err_time < 60*HZ) { - psecuritypriv->btkip_countermeasure = true; - psecuritypriv->last_mic_err_time = 0; - psecuritypriv->btkip_countermeasure_time = cur_time; - } else { - psecuritypriv->last_mic_err_time = jiffies; - } - } - - if (bgroup) - key_type |= NL80211_KEYTYPE_GROUP; - else - key_type |= NL80211_KEYTYPE_PAIRWISE; - - cfg80211_michael_mic_failure(padapter->pnetdev, (u8 *)&pmlmepriv->assoc_bssid[0], key_type, -1, - NULL, GFP_ATOMIC); - - memset(&ev, 0x00, sizeof(ev)); - if (bgroup) - ev.flags |= IW_MICFAILURE_GROUP; - else - ev.flags |= IW_MICFAILURE_PAIRWISE; - - ev.src_addr.sa_family = ARPHRD_ETHER; - memcpy(ev.src_addr.sa_data, &pmlmepriv->assoc_bssid[0], ETH_ALEN); - - memset(&wrqu, 0x00, sizeof(wrqu)); - wrqu.data.length = sizeof(ev); -} - -int rtw_recv_indicatepkt(struct adapter *padapter, union recv_frame *precv_frame) -{ - struct recv_priv *precvpriv; - struct __queue *pfree_recv_queue; - struct sk_buff *skb; - struct rx_pkt_attrib *pattrib = &precv_frame->u.hdr.attrib; - - precvpriv = &(padapter->recvpriv); - pfree_recv_queue = &(precvpriv->free_recv_queue); - - skb = precv_frame->u.hdr.pkt; - if (!skb) - goto _recv_indicatepkt_drop; - - skb->data = precv_frame->u.hdr.rx_data; - - skb_set_tail_pointer(skb, precv_frame->u.hdr.len); - - skb->len = precv_frame->u.hdr.len; - - rtw_os_recv_indicate_pkt(padapter, skb, pattrib); - - /* pointers to NULL before rtw_free_recvframe() */ - precv_frame->u.hdr.pkt = NULL; - - rtw_free_recvframe(precv_frame, pfree_recv_queue); - - return _SUCCESS; - -_recv_indicatepkt_drop: - - /* enqueue back to free_recv_queue */ - rtw_free_recvframe(precv_frame, pfree_recv_queue); - - return _FAIL; -} - -void rtw_init_recv_timer(struct recv_reorder_ctrl *preorder_ctrl) -{ - timer_setup(&preorder_ctrl->reordering_ctrl_timer, - rtw_reordering_ctrl_timeout_handler, 0); - -} diff --git a/drivers/staging/sm750fb/sm750.h b/drivers/staging/sm750fb/sm750.h index d7f40efe3a2c..fcb7d586ebf0 100644 --- a/drivers/staging/sm750fb/sm750.h +++ b/drivers/staging/sm750fb/sm750.h @@ -50,9 +50,9 @@ struct init_status { struct lynx_accel { /* base virtual address of DPR registers */ - volatile unsigned char __iomem *dprBase; + unsigned char __iomem *dpr_base; /* base virtual address of de data port */ - volatile unsigned char __iomem *dpPortBase; + unsigned char __iomem *dp_port_base; /* function pointers */ void (*de_init)(struct lynx_accel *accel); @@ -128,7 +128,7 @@ struct lynx_cursor { char __iomem *vstart; int offset; /* mmio addr of hw cursor */ - volatile char __iomem *mmio; + char __iomem *mmio; }; struct lynxfb_crtc { diff --git a/drivers/staging/sm750fb/sm750_accel.c b/drivers/staging/sm750fb/sm750_accel.c index 44b9e3fe3a41..b07c1aa68621 100644 --- a/drivers/staging/sm750fb/sm750_accel.c +++ b/drivers/staging/sm750fb/sm750_accel.c @@ -17,19 +17,19 @@ #include "sm750.h" #include "sm750_accel.h" -static inline void write_dpr(struct lynx_accel *accel, int offset, u32 regValue) +static inline void write_dpr(struct lynx_accel *accel, int offset, u32 reg_value) { - writel(regValue, accel->dprBase + offset); + writel(reg_value, accel->dpr_base + offset); } static inline u32 read_dpr(struct lynx_accel *accel, int offset) { - return readl(accel->dprBase + offset); + return readl(accel->dpr_base + offset); } static inline void write_dpPort(struct lynx_accel *accel, u32 data) { - writel(data, accel->dpPortBase); + writel(data, accel->dp_port_base); } void sm750_hw_de_init(struct lynx_accel *accel) diff --git a/drivers/staging/sm750fb/sm750_hw.c b/drivers/staging/sm750fb/sm750_hw.c index 7119b67efe11..ce46f240cbaf 100644 --- a/drivers/staging/sm750fb/sm750_hw.c +++ b/drivers/staging/sm750fb/sm750_hw.c @@ -58,8 +58,8 @@ int hw_sm750_map(struct sm750_dev *sm750_dev, struct pci_dev *pdev) } pr_info("mmio virtual addr = %p\n", sm750_dev->pvReg); - sm750_dev->accel.dprBase = sm750_dev->pvReg + DE_BASE_ADDR_TYPE1; - sm750_dev->accel.dpPortBase = sm750_dev->pvReg + DE_PORT_ADDR_TYPE1; + sm750_dev->accel.dpr_base = sm750_dev->pvReg + DE_BASE_ADDR_TYPE1; + sm750_dev->accel.dp_port_base = sm750_dev->pvReg + DE_PORT_ADDR_TYPE1; mmio750 = sm750_dev->pvReg; sm750_set_chip_type(sm750_dev->devid, sm750_dev->revid); diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h index 471413248a14..1889494425eb 100644 --- a/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h +++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h @@ -13,7 +13,7 @@ /* * all the data structures which serialise the MMAL protocol. note - * these are directly mapped onto the recived message data. + * these are directly mapped onto the received message data. * * BEWARE: They seem to *assume* pointers are u32 and that there is no * structure padding! diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c index 3fe482bd2793..c2b5a37915f2 100644 --- a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c +++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c @@ -326,7 +326,7 @@ static int bulk_receive(struct vchiq_mmal_instance *instance, * committed a buffer_to_host operation to the mmal * port without the buffer to back it up (underflow * handling) and there is no obvious way to deal with - * this - how is the mmal servie going to react when + * this - how is the mmal service going to react when * we fail to do the xfer and reschedule a buffer when * it arrives? perhaps a starved flag to indicate a * waiting bulk receive? diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h index 97abe4bdcfc5..8c3959f6f97f 100644 --- a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h +++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h @@ -115,7 +115,7 @@ int vchiq_mmal_component_disable(struct vchiq_mmal_instance *instance, /* enable a mmal port * - * enables a port and if a buffer callback provided enque buffer + * enables a port and, if a buffer callback provided, enqueues buffer * headers as appropriate for the port. */ int vchiq_mmal_port_enable(struct vchiq_mmal_instance *instance, diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig index 0abdb69ee9f4..db3b0bef48f4 100644 --- a/drivers/thunderbolt/Kconfig +++ b/drivers/thunderbolt/Kconfig @@ -4,8 +4,8 @@ menuconfig USB4 depends on PCI select APPLE_PROPERTIES if EFI_STUB && X86 select CRC32 - select CRYPTO - select CRYPTO_HASH + select CRYPTO_LIB_SHA256 + select CRYPTO_LIB_UTILS select NVMEM help USB4 and Thunderbolt driver. USB4 is the public specification diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c index d2a0054217da..45d1415871b4 100644 --- a/drivers/thunderbolt/acpi.c +++ b/drivers/thunderbolt/acpi.c @@ -86,7 +86,7 @@ out_put: * @nhi ACPI node. For each reference a device link is added. The link * is automatically removed by the driver core. * - * Returns %true if at least one link was created. + * Returns %true if at least one link was created, %false otherwise. */ bool tb_acpi_add_links(struct tb_nhi *nhi) { @@ -113,8 +113,10 @@ bool tb_acpi_add_links(struct tb_nhi *nhi) /** * tb_acpi_is_native() - Did the platform grant native TBT/USB4 control * - * Returns %true if the platform granted OS native control over - * TBT/USB4. In this case software based connection manager can be used, + * Return: %true if the platform granted OS native control over + * TBT/USB4, %false otherwise. + * + * When returned %true, software based connection manager can be used, * otherwise there is firmware based connection manager running. */ bool tb_acpi_is_native(void) @@ -126,8 +128,8 @@ bool tb_acpi_is_native(void) /** * tb_acpi_may_tunnel_usb3() - Is USB3 tunneling allowed by the platform * - * When software based connection manager is used, this function - * returns %true if platform allows native USB3 tunneling. + * Return: %true if software based connection manager is used and + * platform allows native USB 3.x tunneling, %false otherwise. */ bool tb_acpi_may_tunnel_usb3(void) { @@ -139,8 +141,8 @@ bool tb_acpi_may_tunnel_usb3(void) /** * tb_acpi_may_tunnel_dp() - Is DisplayPort tunneling allowed by the platform * - * When software based connection manager is used, this function - * returns %true if platform allows native DP tunneling. + * Return: %true if software based connection manager is used and + * platform allows native DP tunneling, %false otherwise. */ bool tb_acpi_may_tunnel_dp(void) { @@ -152,8 +154,8 @@ bool tb_acpi_may_tunnel_dp(void) /** * tb_acpi_may_tunnel_pcie() - Is PCIe tunneling allowed by the platform * - * When software based connection manager is used, this function - * returns %true if platform allows native PCIe tunneling. + * Return: %true if software based connection manager is used and + * platform allows native PCIe tunneling, %false otherwise. */ bool tb_acpi_may_tunnel_pcie(void) { @@ -165,8 +167,8 @@ bool tb_acpi_may_tunnel_pcie(void) /** * tb_acpi_is_xdomain_allowed() - Are XDomain connections allowed * - * When software based connection manager is used, this function - * returns %true if platform allows XDomain connections. + * Return: %true if software based connection manager is used and + * platform allows XDomain tunneling, %false otherwise. */ bool tb_acpi_is_xdomain_allowed(void) { @@ -256,7 +258,7 @@ static int tb_acpi_retimer_set_power(struct tb_port *port, bool power) * * This should only be called if the USB4/TBT link is not up. * - * Returns %0 on success. + * Return: %0 on success, negative errno otherwise. */ int tb_acpi_power_on_retimers(struct tb_port *port) { @@ -270,7 +272,7 @@ int tb_acpi_power_on_retimers(struct tb_port *port) * This is the opposite of tb_acpi_power_on_retimers(). After returning * successfully the normal operations with the @port can continue. * - * Returns %0 on success. + * Return: %0 on success, negative errno otherwise. */ int tb_acpi_power_off_retimers(struct tb_port *port) { diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c index 8ecd610c62d5..4ab22d5291ac 100644 --- a/drivers/thunderbolt/cap.c +++ b/drivers/thunderbolt/cap.c @@ -64,10 +64,14 @@ static void tb_port_dummy_read(struct tb_port *port) * @port: Port to find the capability for * @offset: Previous capability offset (%0 for start) * - * Returns dword offset of the next capability in port config space - * capability list and returns it. Passing %0 returns the first entry in - * the capability list. If no next capability is found returns %0. In case - * of failure returns negative errno. + * Finds dword offset of the next capability in port config space + * capability list. When passed %0 in @offset parameter, first entry + * will be returned, if it exists. + * + * Return: + * * Double word offset of the first or next capability - On success. + * * %0 - If no next capability is found. + * * Negative errno - Another error occurred. */ int tb_port_next_cap(struct tb_port *port, unsigned int offset) { @@ -112,9 +116,10 @@ static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) * @port: Port to find the capability for * @cap: Capability to look * - * Returns offset to start of capability or %-ENOENT if no such - * capability was found. Negative errno is returned if there was an - * error. + * Return: + * * Offset to the start of capability - On success. + * * %-ENOENT - If no such capability was found. + * * Negative errno - Another error occurred. */ int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) { @@ -137,10 +142,14 @@ int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) * @sw: Switch to find the capability for * @offset: Previous capability offset (%0 for start) * - * Finds dword offset of the next capability in router config space - * capability list and returns it. Passing %0 returns the first entry in - * the capability list. If no next capability is found returns %0. In case - * of failure returns negative errno. + * Finds dword offset of the next capability in port config space + * capability list. When passed %0 in @offset parameter, first entry + * will be returned, if it exists. + * + * Return: + * * Double word offset of the first or next capability - On success. + * * %0 - If no next capability is found. + * * Negative errno - Another error occurred. */ int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset) { @@ -181,9 +190,10 @@ int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset) * @sw: Switch to find the capability for * @cap: Capability to look * - * Returns offset to start of capability or %-ENOENT if no such - * capability was found. Negative errno is returned if there was an - * error. + * Return: + * * Offset to the start of capability - On success. + * * %-ENOENT - If no such capability was found. + * * Negative errno - Another error occurred. */ int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) { @@ -213,10 +223,13 @@ int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) * @sw: Switch to find the capability for * @vsec: Vendor specific capability to look * - * Functions enumerates vendor specific capabilities (VSEC) of a switch - * and returns offset when capability matching @vsec is found. If no - * such capability is found returns %-ENOENT. In case of error returns - * negative errno. + * This function enumerates vendor specific capabilities (VSEC) of a + * switch and returns offset when capability matching @vsec is found. + * + * Return: + * * Offset of capability - On success. + * * %-ENOENT - If capability was not found. + * * Negative errno - Another error occurred. */ int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) { diff --git a/drivers/thunderbolt/clx.c b/drivers/thunderbolt/clx.c index 787dfd1550e5..1637e79d988a 100644 --- a/drivers/thunderbolt/clx.c +++ b/drivers/thunderbolt/clx.c @@ -167,7 +167,8 @@ static int tb_port_clx(struct tb_port *port) * @port: USB4 port to check * @clx: Mask of CL states to check * - * Returns true if any of the given CL states is enabled for @port. + * Return: %true if any of the given CL states is enabled for @port, + * %false otherwise. */ bool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx) { @@ -177,6 +178,8 @@ bool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx) /** * tb_switch_clx_is_supported() - Is CLx supported on this type of router * @sw: The router to check CLx support for + * + * Return: %true if CLx is supported, %false otherwise. */ static bool tb_switch_clx_is_supported(const struct tb_switch *sw) { @@ -203,7 +206,7 @@ static bool tb_switch_clx_is_supported(const struct tb_switch *sw) * Can be called for any router. Initializes the current CL state by * reading it from the hardware. * - * Returns %0 in case of success and negative errno in case of failure. + * Return: %0 on success, negative errno otherwise. */ int tb_switch_clx_init(struct tb_switch *sw) { @@ -313,7 +316,7 @@ static bool validate_mask(unsigned int clx) * is not inter-domain link. The complete set of conditions is described in CM * Guide 1.0 section 8.1. * - * Returns %0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ int tb_switch_clx_enable(struct tb_switch *sw, unsigned int clx) { @@ -390,8 +393,7 @@ int tb_switch_clx_enable(struct tb_switch *sw, unsigned int clx) * Disables all CL states of the given router. Can be called on any * router and if the states were not enabled already does nothing. * - * Returns the CL states that were disabled or negative errno in case of - * failure. + * Return: CL states that were disabled or negative errno otherwise. */ int tb_switch_clx_disable(struct tb_switch *sw) { diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 1db2e951b53f..f92175ee3841 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -82,6 +82,8 @@ static DEFINE_MUTEX(tb_cfg_request_lock); * * This is refcounted object so when you are done with this, call * tb_cfg_request_put() to it. + * + * Return: &struct tb_cfg_request on success, %NULL otherwise. */ struct tb_cfg_request *tb_cfg_request_alloc(void) { @@ -359,7 +361,7 @@ static void tb_ctl_tx_callback(struct tb_ring *ring, struct ring_frame *frame, * * len must be a multiple of four. * - * Return: Returns 0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ static int tb_ctl_tx(struct tb_ctl *ctl, const void *data, size_t len, enum tb_cfg_pkg_type type) @@ -539,6 +541,8 @@ static void tb_cfg_request_work(struct work_struct *work) * * This queues @req on the given control channel without waiting for it * to complete. When the request completes @callback is called. + * + * Return: %0 on success, negative errno otherwise. */ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, void (*callback)(void *), void *callback_data) @@ -605,6 +609,9 @@ static void tb_cfg_request_complete(void *data) * triggers the request is canceled before function returns. Note the * caller needs to make sure only one message for given switch is active * at a time. + * + * Return: &struct tb_cfg_result with non-zero @err field if error + * has occurred. */ struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl, struct tb_cfg_request *req, @@ -641,7 +648,7 @@ struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl, * * cb will be invoked once for every hot plug event. * - * Return: Returns a pointer on success or NULL on failure. + * Return: Pointer to &struct tb_ctl, %NULL on failure. */ struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int index, int timeout_msec, event_cb cb, void *cb_data) @@ -764,8 +771,9 @@ void tb_ctl_stop(struct tb_ctl *ctl) * @route: Router that originated the event * @error: Pointer to the notification package * - * Call this as response for non-plug notification to ack it. Returns - * %0 on success or an error code on failure. + * Call this as a response for non-plug notification to ack it. + * + * Return: %0 on success, negative errno otherwise. */ int tb_cfg_ack_notification(struct tb_ctl *ctl, u64 route, const struct cfg_error_pkg *error) @@ -827,8 +835,9 @@ int tb_cfg_ack_notification(struct tb_ctl *ctl, u64 route, * @port: Port where the hot plug/unplug happened * @unplug: Ack hot plug or unplug * - * Call this as response for hot plug/unplug event to ack it. - * Returns %0 on success or an error code on failure. + * Call this as a response for hot plug/unplug event to ack it. + * + * Return: %0 on success, negative errno otherwise. */ int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug) { @@ -895,6 +904,9 @@ static bool tb_cfg_copy(struct tb_cfg_request *req, const struct ctl_pkg *pkg) * If the switch at route is incorrectly configured then we will not receive a * reply (even though the switch will reset). The caller should check for * -ETIMEDOUT and attempt to reconfigure the switch. + * + * Return: &struct tb_cfg_result with non-zero @err field if error + * has occurred. */ struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route) { @@ -937,6 +949,9 @@ struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route) * @timeout_msec: Timeout in ms how long to wait for the response * * Reads from router config space without translating the possible error. + * + * Return: &struct tb_cfg_result with non-zero @err field if error + * has occurred. */ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer, u64 route, u32 port, enum tb_cfg_space space, @@ -1008,6 +1023,9 @@ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer, * @timeout_msec: Timeout in ms how long to wait for the response * * Writes to router config space without translating the possible error. + * + * Return: &struct tb_cfg_result with non-zero @err field if error + * has occurred. */ struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port, enum tb_cfg_space space, @@ -1150,8 +1168,7 @@ int tb_cfg_write(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port, * Reads the first dword from the switches TB_CFG_SWITCH config area and * returns the port number from which the reply originated. * - * Return: Returns the upstream port number on success or an error code on - * failure. + * Return: Upstream port number on success or negative error code on failure. */ int tb_cfg_get_upstream_port(struct tb_ctl *ctl, u64 route) { diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h index 7e08ca8f0895..db1646eb4fd0 100644 --- a/drivers/thunderbolt/ctl.h +++ b/drivers/thunderbolt/ctl.h @@ -54,6 +54,7 @@ struct ctl_pkg { * @kref: Reference count * @ctl: Pointer to the control channel structure. Only set when the * request is queued. + * @request: Request is stored here * @request_size: Size of the request packet (in bytes) * @request_type: Type of the request packet * @response: Response is stored here diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index f8328ca7e22e..46a2a3550be7 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -12,6 +12,7 @@ #include <linux/debugfs.h> #include <linux/delay.h> #include <linux/pm_runtime.h> +#include <linux/string_choices.h> #include <linux/uaccess.h> #include "tb.h" @@ -691,7 +692,7 @@ static int margining_caps_show(struct seq_file *s, void *not_used) seq_printf(s, "0x%08x\n", margining->caps[i]); seq_printf(s, "# software margining: %s\n", - supports_software(margining) ? "yes" : "no"); + str_yes_no(supports_software(margining))); if (supports_hardware(margining)) { seq_puts(s, "# hardware margining: yes\n"); seq_puts(s, "# minimum BER level contour: "); diff --git a/drivers/thunderbolt/dma_port.c b/drivers/thunderbolt/dma_port.c index 9f20c7bbf0ce..dc8ea188a114 100644 --- a/drivers/thunderbolt/dma_port.c +++ b/drivers/thunderbolt/dma_port.c @@ -197,6 +197,8 @@ static int dma_find_port(struct tb_switch *sw) * * The DMA control port is functional also when the switch is in safe * mode. + * + * Return: &struct tb_dma_port on success, %NULL otherwise. */ struct tb_dma_port *dma_port_alloc(struct tb_switch *sw) { @@ -354,6 +356,8 @@ static int dma_port_flash_write_block(void *data, unsigned int dwaddress, * @address: Address relative to the start of active region * @buf: Buffer where the data is read * @size: Size of the buffer + * + * Return: %0 on success, negative errno otherwise. */ int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address, void *buf, size_t size) @@ -372,6 +376,8 @@ int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address, * Writes block of data to the non-active flash region of the switch. If * the address is given as %DMA_PORT_CSS_ADDRESS the block is written * using CSS command. + * + * Return: %0 on success, negative errno otherwise. */ int dma_port_flash_write(struct tb_dma_port *dma, unsigned int address, const void *buf, size_t size) @@ -393,6 +399,8 @@ int dma_port_flash_write(struct tb_dma_port *dma, unsigned int address, * dma_port_flash_update_auth_status() to get status of this command. * This is because if the switch in question is root switch the * thunderbolt host controller gets reset as well. + * + * Return: %0 on success, negative errno otherwise. */ int dma_port_flash_update_auth(struct tb_dma_port *dma) { @@ -410,12 +418,13 @@ int dma_port_flash_update_auth(struct tb_dma_port *dma) * @status: Status code of the operation * * The function checks if there is status available from the last update - * auth command. Returns %0 if there is no status and no further - * action is required. If there is status, %1 is returned instead and - * @status holds the failure code. + * auth command. * - * Negative return means there was an error reading status from the - * switch. + * Return: + * * %0 - If there is no status and no further action is required. + * * %1 - If there is some status. @status holds the failure code. + * * Negative errno - An error occurred when reading status from the + * switch. */ int dma_port_flash_update_auth_status(struct tb_dma_port *dma, u32 *status) { @@ -446,6 +455,8 @@ int dma_port_flash_update_auth_status(struct tb_dma_port *dma, u32 *status) * @dma: DMA control port * * Triggers power cycle to the switch. + * + * Return: %0 on success, negative errno otherwise. */ int dma_port_power_cycle(struct tb_dma_port *dma) { diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 45239703745e..83defc915d33 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -12,7 +12,8 @@ #include <linux/pm_runtime.h> #include <linux/slab.h> #include <linux/random.h> -#include <crypto/hash.h> +#include <crypto/sha2.h> +#include <crypto/utils.h> #include "tb.h" @@ -368,7 +369,7 @@ static bool tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type, * Call tb_domain_put() to release the domain before it has been added * to the system. * - * Return: allocated domain structure on %NULL in case of error + * Return: Pointer to &struct tb or %NULL in case of error. */ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize) { @@ -430,7 +431,7 @@ err_free: * and release the domain after this function has been called, call * tb_domain_remove(). * - * Return: %0 in case of success and negative errno in case of error + * Return: %0 on success, negative errno otherwise. */ int tb_domain_add(struct tb *tb, bool reset) { @@ -518,6 +519,8 @@ void tb_domain_remove(struct tb *tb) * @tb: Domain to suspend * * Suspends all devices in the domain and stops the control channel. + * + * Return: %0 on success, negative errno otherwise. */ int tb_domain_suspend_noirq(struct tb *tb) { @@ -544,6 +547,8 @@ int tb_domain_suspend_noirq(struct tb *tb) * * Re-starts the control channel, and resumes all devices connected to * the domain. + * + * Return: %0 on success, negative errno otherwise. */ int tb_domain_resume_noirq(struct tb *tb) { @@ -643,6 +648,8 @@ int tb_domain_disapprove_switch(struct tb *tb, struct tb_switch *sw) * This will approve switch by connection manager specific means. In * case of success the connection manager will create PCIe tunnel from * parent to @sw. + * + * Return: %0 on success, negative errno otherwise. */ int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw) { @@ -708,8 +715,6 @@ int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw) u8 response[TB_SWITCH_KEY_SIZE]; u8 hmac[TB_SWITCH_KEY_SIZE]; struct tb_switch *parent_sw; - struct crypto_shash *tfm; - struct shash_desc *shash; int ret; if (!tb->cm_ops->approve_switch || !tb->cm_ops->challenge_switch_key) @@ -725,45 +730,15 @@ int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw) if (ret) return ret; - tfm = crypto_alloc_shash("hmac(sha256)", 0, 0); - if (IS_ERR(tfm)) - return PTR_ERR(tfm); - - ret = crypto_shash_setkey(tfm, sw->key, TB_SWITCH_KEY_SIZE); - if (ret) - goto err_free_tfm; - - shash = kzalloc(sizeof(*shash) + crypto_shash_descsize(tfm), - GFP_KERNEL); - if (!shash) { - ret = -ENOMEM; - goto err_free_tfm; - } - - shash->tfm = tfm; - - memset(hmac, 0, sizeof(hmac)); - ret = crypto_shash_digest(shash, challenge, sizeof(hmac), hmac); - if (ret) - goto err_free_shash; + static_assert(sizeof(hmac) == SHA256_DIGEST_SIZE); + hmac_sha256_usingrawkey(sw->key, TB_SWITCH_KEY_SIZE, + challenge, sizeof(challenge), hmac); /* The returned HMAC must match the one we calculated */ - if (memcmp(response, hmac, sizeof(hmac))) { - ret = -EKEYREJECTED; - goto err_free_shash; - } - - crypto_free_shash(tfm); - kfree(shash); + if (crypto_memneq(response, hmac, sizeof(hmac))) + return -EKEYREJECTED; return tb->cm_ops->approve_switch(tb, sw); - -err_free_shash: - kfree(shash); -err_free_tfm: - crypto_free_shash(tfm); - - return ret; } /** @@ -773,7 +748,7 @@ err_free_tfm: * This needs to be called in preparation for NVM upgrade of the host * controller. Makes sure all PCIe paths are disconnected. * - * Return %0 on success and negative errno in case of error. + * Return: %0 on success and negative errno in case of error. */ int tb_domain_disconnect_pcie_paths(struct tb *tb) { @@ -795,9 +770,11 @@ int tb_domain_disconnect_pcie_paths(struct tb *tb) * Calls connection manager specific method to enable DMA paths to the * XDomain in question. * - * Return: 0% in case of success and negative errno otherwise. In - * particular returns %-ENOTSUPP if the connection manager - * implementation does not support XDomains. + * Return: + * * %0 - On success. + * * %-ENOTSUPP - If the connection manager implementation does not support + * XDomains. + * * Negative errno - An error occurred. */ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, @@ -822,9 +799,11 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, * Calls connection manager specific method to disconnect DMA paths to * the XDomain in question. * - * Return: 0% in case of success and negative errno otherwise. In - * particular returns %-ENOTSUPP if the connection manager - * implementation does not support XDomains. + * Return: + * * %0 - On success. + * * %-ENOTSUPP - If the connection manager implementation does not support + * XDomains. + * * Negative errno - An error occurred. */ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index e66183a72cf9..1af65fece495 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -298,6 +298,8 @@ struct tb_drom_entry_desc { * * Does not use the cached copy in sw->drom. Used during resume to check switch * identity. + * + * Return: %0 on success, negative errno otherwise. */ int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid) { @@ -709,7 +711,7 @@ static int tb_drom_device_read(struct tb_switch *sw) * populates the fields in @sw accordingly. Can be called for any router * generation. * - * Returns %0 in case of success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_drom_read(struct tb_switch *sw) { diff --git a/drivers/thunderbolt/lc.c b/drivers/thunderbolt/lc.c index 63cb4b6afb71..0891d51ac2e9 100644 --- a/drivers/thunderbolt/lc.c +++ b/drivers/thunderbolt/lc.c @@ -14,6 +14,8 @@ * tb_lc_read_uuid() - Read switch UUID from link controller common register * @sw: Switch whose UUID is read * @uuid: UUID is placed here + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_read_uuid(struct tb_switch *sw, u32 *uuid) { @@ -52,9 +54,10 @@ static int find_port_lc_cap(struct tb_port *port) * @port: Port that is reset * * Triggers downstream port reset through link controller registers. - * Returns %0 in case of success negative errno otherwise. Only supports - * non-USB4 routers with link controller (that's Thunderbolt 2 and - * Thunderbolt 3). + * Only supports non-USB4 routers with link controller (that's + * Thunderbolt 2 and Thunderbolt 3). + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_reset_port(struct tb_port *port) { @@ -132,6 +135,8 @@ static int tb_lc_set_port_configured(struct tb_port *port, bool configured) * @port: Port that is set as configured * * Sets the port configured for power management purposes. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_configure_port(struct tb_port *port) { @@ -143,6 +148,8 @@ int tb_lc_configure_port(struct tb_port *port) * @port: Port that is set as configured * * Sets the port unconfigured for power management purposes. + * + * Return: %0 on success, negative errno otherwise. */ void tb_lc_unconfigure_port(struct tb_port *port) { @@ -184,8 +191,10 @@ static int tb_lc_set_xdomain_configured(struct tb_port *port, bool configure) * tb_lc_configure_xdomain() - Inform LC that the link is XDomain * @port: Switch downstream port connected to another host * - * Sets the lane configured for XDomain accordingly so that the LC knows - * about this. Returns %0 in success and negative errno in failure. + * Sets the lane configured for XDomain accordingly so that LC knows + * about this. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_configure_xdomain(struct tb_port *port) { @@ -211,7 +220,7 @@ void tb_lc_unconfigure_xdomain(struct tb_port *port) * sleep. Should be called for those downstream lane adapters that were * not connected (tb_lc_configure_port() was not called) before sleep. * - * Returns %0 in success and negative errno in case of failure. + * Return: %0 on success, negative errno otherwise. */ int tb_lc_start_lane_initialization(struct tb_port *port) { @@ -244,6 +253,8 @@ int tb_lc_start_lane_initialization(struct tb_port *port) * * TB_LC_LINK_ATTR_CPS bit reflects if the link supports CLx including * active cables (if connected on the link). + * + * Return: %true if CLx is supported, %false otherwise. */ bool tb_lc_is_clx_supported(struct tb_port *port) { @@ -266,7 +277,8 @@ bool tb_lc_is_clx_supported(struct tb_port *port) * tb_lc_is_usb_plugged() - Is there USB device connected to port * @port: Device router lane 0 adapter * - * Returns true if the @port has USB type-C device connected. + * Return: %true if the @port has USB Type-C device connected, %false + * otherwise. */ bool tb_lc_is_usb_plugged(struct tb_port *port) { @@ -292,7 +304,8 @@ bool tb_lc_is_usb_plugged(struct tb_port *port) * tb_lc_is_xhci_connected() - Is the internal xHCI connected * @port: Device router lane 0 adapter * - * Returns true if the internal xHCI has been connected to @port. + * Return: %true if the internal xHCI has been connected to + * @port, %false otherwise. */ bool tb_lc_is_xhci_connected(struct tb_port *port) { @@ -343,9 +356,10 @@ static int __tb_lc_xhci_connect(struct tb_port *port, bool connect) * tb_lc_xhci_connect() - Connect internal xHCI * @port: Device router lane 0 adapter * - * Tells LC to connect the internal xHCI to @port. Returns %0 on success - * and negative errno in case of failure. Can be called for Thunderbolt 3 - * routers only. + * Tells LC to connect the internal xHCI to @port. Can be called for + * Thunderbolt 3 routers only. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_xhci_connect(struct tb_port *port) { @@ -408,6 +422,8 @@ static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset, * @flags: Wakeup flags (%0 to disable) * * For each LC sets wake bits accordingly. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags) { @@ -447,6 +463,8 @@ int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags) * * Let the switch link controllers know that the switch is going to * sleep. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_set_sleep(struct tb_switch *sw) { @@ -491,6 +509,8 @@ int tb_lc_set_sleep(struct tb_switch *sw) * * Checks whether conditions for lane bonding from parent to @sw are * possible. + * + * Return: %true if lane bonding is possible, %false otherwise. */ bool tb_lc_lane_bonding_possible(struct tb_switch *sw) { @@ -562,6 +582,8 @@ static int tb_lc_dp_sink_available(struct tb_switch *sw, int sink) * * Queries through LC SNK_ALLOCATION registers whether DP sink is available * for the given DP IN port or not. + * + * Return: %true if DP sink is available, %false otherwise. */ bool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in) { @@ -586,10 +608,12 @@ bool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in) * @sw: Switch whose DP sink is allocated * @in: DP IN port the DP sink is allocated for * - * Allocate DP sink for @in via LC SNK_ALLOCATION registers. If the - * resource is available and allocation is successful returns %0. In all - * other cases returs negative errno. In particular %-EBUSY is returned if - * the resource was not available. + * Allocate DP sink for @in via LC SNK_ALLOCATION registers. + * + * Return: + * * %0 - If the resource is available and allocation is successful. + * * %-EBUSY - If resource is not available. + * * Negative errno - Another error occurred. */ int tb_lc_dp_sink_alloc(struct tb_switch *sw, struct tb_port *in) { @@ -637,6 +661,8 @@ int tb_lc_dp_sink_alloc(struct tb_switch *sw, struct tb_port *in) * @in: DP IN port whose DP sink is de-allocated * * De-allocate DP sink from @in using LC SNK_ALLOCATION registers. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_dp_sink_dealloc(struct tb_switch *sw, struct tb_port *in) { @@ -680,6 +706,8 @@ int tb_lc_dp_sink_dealloc(struct tb_switch *sw, struct tb_port *in) * * This is useful to let authentication cycle pass even without * a Thunderbolt link present. + * + * Return: %0 on success, negative errno otherwise. */ int tb_lc_force_power(struct tb_switch *sw) { diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index f3a2264e012b..5f63f9b9cf40 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -19,6 +19,7 @@ #include <linux/module.h> #include <linux/delay.h> #include <linux/property.h> +#include <linux/string_choices.h> #include <linux/string_helpers.h> #include "nhi.h" @@ -146,7 +147,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) dev_WARN(&ring->nhi->pdev->dev, "interrupt for %s %d is already %s\n", RING_TYPE(ring), ring->hop, - active ? "enabled" : "disabled"); + str_enabled_disabled(active)); if (active) iowrite32(new, ring->nhi->iobase + reg); @@ -343,8 +344,10 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue); * * This function can be called when @start_poll callback of the @ring * has been called. It will read one completed frame from the ring and - * return it to the caller. Returns %NULL if there is no more completed - * frames. + * return it to the caller. + * + * Return: Pointer to &struct ring_frame, %NULL if there is no more + * completed frames. */ struct ring_frame *tb_ring_poll(struct tb_ring *ring) { @@ -639,6 +642,8 @@ err_free_ring: * @hop: HopID (ring) to allocate * @size: Number of entries in the ring * @flags: Flags for the ring + * + * Return: Pointer to &struct tb_ring, %NULL otherwise. */ struct tb_ring *tb_ring_alloc_tx(struct tb_nhi *nhi, int hop, int size, unsigned int flags) @@ -660,6 +665,8 @@ EXPORT_SYMBOL_GPL(tb_ring_alloc_tx); * interrupt is triggered and masked, instead of callback * in each Rx frame. * @poll_data: Optional data passed to @start_poll + * + * Return: Pointer to &struct tb_ring, %NULL otherwise. */ struct tb_ring *tb_ring_alloc_rx(struct tb_nhi *nhi, int hop, int size, unsigned int flags, int e2e_tx_hop, @@ -853,8 +860,9 @@ EXPORT_SYMBOL_GPL(tb_ring_free); * @cmd: Command to send * @data: Data to be send with the command * - * Sends mailbox command to the firmware running on NHI. Returns %0 in - * case of success and negative errno in case of failure. + * Sends mailbox command to the firmware running on NHI. + * + * Return: %0 on success, negative errno otherwise. */ int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data) { @@ -890,6 +898,8 @@ int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data) * * The function reads current firmware operation mode using NHI mailbox * registers and returns it to the caller. + * + * Return: &enum nhi_fw_mode. */ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi) { diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index 297a3e440648..cf5222bee971 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -21,6 +21,12 @@ enum ring_flags { /** * struct ring_desc - TX/RX ring entry + * @phys: DMA mapped address of the frame + * @length: Size of the ring + * @eof: End of frame protocol defined field + * @sof: Start of frame protocol defined field + * @flags: Ring descriptor flags + * @time: Fill with zero * * For TX set length/eof/sof. * For RX length/eof/sof are set by the NHI. diff --git a/drivers/thunderbolt/nvm.c b/drivers/thunderbolt/nvm.c index da11c8112e29..6901058b7ac0 100644 --- a/drivers/thunderbolt/nvm.c +++ b/drivers/thunderbolt/nvm.c @@ -278,9 +278,13 @@ static const struct tb_nvm_vendor retimer_nvm_vendors[] = { * tb_nvm_alloc() - Allocate new NVM structure * @dev: Device owning the NVM * - * Allocates new NVM structure with unique @id and returns it. In case - * of error returns ERR_PTR(). Specifically returns %-EOPNOTSUPP if the - * NVM format of the @dev is not known by the kernel. + * Allocates new NVM structure with unique @id and returns it. + * + * Return: + * * Pointer to &struct tb_nvm - On success. + * * %-EOPNOTSUPP - If the NVM format of the @dev is not known by the + * kernel. + * * %ERR_PTR - In case of failure. */ struct tb_nvm *tb_nvm_alloc(struct device *dev) { @@ -347,9 +351,10 @@ struct tb_nvm *tb_nvm_alloc(struct device *dev) * tb_nvm_read_version() - Read and populate NVM version * @nvm: NVM structure * - * Uses vendor specific means to read out and fill in the existing - * active NVM version. Returns %0 in case of success and negative errno - * otherwise. + * Uses vendor specific means to read and fill out the existing + * active NVM version. + * + * Return: %0 on success, negative errno otherwise. */ int tb_nvm_read_version(struct tb_nvm *nvm) { @@ -365,12 +370,11 @@ int tb_nvm_read_version(struct tb_nvm *nvm) * tb_nvm_validate() - Validate new NVM image * @nvm: NVM structure * - * Runs vendor specific validation over the new NVM image and if all - * checks pass returns %0. As side effect updates @nvm->buf_data_start - * and @nvm->buf_data_size fields to match the actual data to be written - * to the NVM. + * Runs vendor specific validation over the new NVM image. As a + * side effect, updates @nvm->buf_data_start and @nvm->buf_data_size + * fields to match the actual data to be written to the NVM. * - * If the validation does not pass then returns negative errno. + * Return: %0 on successful validation, negative errno otherwise. */ int tb_nvm_validate(struct tb_nvm *nvm) { @@ -405,7 +409,7 @@ int tb_nvm_validate(struct tb_nvm *nvm) * the image, this function does that. Can be called even if the device * does not need this. * - * Returns %0 in case of success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_nvm_write_headers(struct tb_nvm *nvm) { @@ -423,7 +427,8 @@ int tb_nvm_write_headers(struct tb_nvm *nvm) * Registers new active NVmem device for @nvm. The @reg_read is called * directly from NVMem so it must handle possible concurrent access if * needed. The first parameter passed to @reg_read is @nvm structure. - * Returns %0 in success and negative errno otherwise. + * + * Return: %0 on success, negative errno otherwise. */ int tb_nvm_add_active(struct tb_nvm *nvm, nvmem_reg_read_t reg_read) { @@ -461,6 +466,11 @@ int tb_nvm_add_active(struct tb_nvm *nvm, nvmem_reg_read_t reg_read) * Helper function to cache the new NVM image before it is actually * written to the flash. Copies @bytes from @val to @nvm->buf starting * from @offset. + * + * Return: + * * %0 - On success. + * * %-ENOMEM - If buffer allocation failed. + * * Negative errno - Another error occurred. */ int tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val, size_t bytes) @@ -488,7 +498,7 @@ int tb_nvm_write_buf(struct tb_nvm *nvm, unsigned int offset, void *val, * needed. The first parameter passed to @reg_write is @nvm structure. * The size of the NVMem device is set to %NVM_MAX_SIZE. * - * Returns %0 in success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_nvm_add_non_active(struct tb_nvm *nvm, nvmem_reg_write_t reg_write) { @@ -545,7 +555,7 @@ void tb_nvm_free(struct tb_nvm *nvm) * This is a generic function that reads data from NVM or NVM like * device. * - * Returns %0 on success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_nvm_read_data(unsigned int address, void *buf, size_t size, unsigned int retries, read_block_fn read_block, @@ -592,7 +602,7 @@ int tb_nvm_read_data(unsigned int address, void *buf, size_t size, * * This is generic function that writes data to NVM or NVM like device. * - * Returns %0 on success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_nvm_write_data(unsigned int address, const void *buf, size_t size, unsigned int retries, write_block_fn write_block, diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c index e1a5f6e3d0b6..f9b11dadfbdd 100644 --- a/drivers/thunderbolt/path.c +++ b/drivers/thunderbolt/path.c @@ -96,7 +96,7 @@ static int tb_path_find_src_hopid(struct tb_port *src, * that the @dst port is the expected one. If it is not, the path can be * cleaned up by calling tb_path_deactivate() before tb_path_free(). * - * Return: Discovered path on success, %NULL in case of failure + * Return: Pointer to &struct tb_path, %NULL in case of failure. */ struct tb_path *tb_path_discover(struct tb_port *src, int src_hopid, struct tb_port *dst, int dst_hopid, @@ -233,7 +233,7 @@ err: * links on the path, prioritizes using @link_nr but takes into account * that the lanes may be bonded. * - * Return: Returns a tb_path on success or NULL on failure. + * Return: Pointer to &struct tb_path, %NULL in case of failure. */ struct tb_path *tb_path_alloc(struct tb *tb, struct tb_port *src, int src_hopid, struct tb_port *dst, int dst_hopid, int link_nr, @@ -452,7 +452,9 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index, * @hop_index: HopID of the path to be cleared * * This deactivates or clears a single path config space entry at - * @hop_index. Returns %0 in success and negative errno otherwise. + * @hop_index. + * + * Return: %0 on success, negative errno otherwise. */ int tb_path_deactivate_hop(struct tb_port *port, int hop_index) { @@ -498,7 +500,7 @@ void tb_path_deactivate(struct tb_path *path) * Activate a path starting with the last hop and iterating backwards. The * caller must fill path->hops before calling tb_path_activate(). * - * Return: Returns 0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ int tb_path_activate(struct tb_path *path) { @@ -592,7 +594,7 @@ err: * tb_path_is_invalid() - check whether any ports on the path are invalid * @path: Path to check * - * Return: Returns true if the path is invalid, false otherwise. + * Return: %true if the path is invalid, %false otherwise. */ bool tb_path_is_invalid(struct tb_path *path) { @@ -613,6 +615,8 @@ bool tb_path_is_invalid(struct tb_path *path) * * Goes over all hops on path and checks if @port is any of them. * Direction does not matter. + * + * Return: %true if port is on the path, %false otherwise. */ bool tb_path_port_on_path(const struct tb_path *path, const struct tb_port *port) { diff --git a/drivers/thunderbolt/property.c b/drivers/thunderbolt/property.c index dc555cda98e6..31aa0516932a 100644 --- a/drivers/thunderbolt/property.c +++ b/drivers/thunderbolt/property.c @@ -211,11 +211,13 @@ static struct tb_property_dir *__tb_property_parse_dir(const u32 *block, * * This function parses the XDomain properties data block into format that * can be traversed using the helper functions provided by this module. - * Upon success returns the parsed directory. In case of error returns - * %NULL. The resulting &struct tb_property_dir needs to be released by + * + * The resulting &struct tb_property_dir needs to be released by * calling tb_property_free_dir() when not needed anymore. * * The @block is expected to be root directory. + * + * Return: Pointer to &struct tb_property_dir, %NULL in case of failure. */ struct tb_property_dir *tb_property_parse_dir(const u32 *block, size_t block_len) @@ -238,6 +240,8 @@ struct tb_property_dir *tb_property_parse_dir(const u32 *block, * * Creates new, empty property directory. If @uuid is %NULL then the * directory is assumed to be root directory. + * + * Return: Pointer to &struct tb_property_dir, %NULL in case of failure. */ struct tb_property_dir *tb_property_create_dir(const uuid_t *uuid) { @@ -481,9 +485,11 @@ static ssize_t __tb_property_format_dir(const struct tb_property_dir *dir, * @block_len: Length of the property block * * This function formats the directory to the packed format that can be - * then send over the thunderbolt fabric to receiving host. Returns %0 in - * case of success and negative errno on faulure. Passing %NULL in @block - * returns number of entries the block takes. + * then sent over the thunderbolt fabric to receiving host. + * + * Passing %NULL in @block returns number of entries the block takes. + * + * Return: %0 on success, negative errno otherwise. */ ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block, size_t block_len) @@ -505,9 +511,9 @@ ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block, * tb_property_copy_dir() - Take a deep copy of directory * @dir: Directory to copy * - * This function takes a deep copy of @dir and returns back the copy. In - * case of error returns %NULL. The resulting directory needs to be - * released by calling tb_property_free_dir(). + * The resulting directory needs to be released by calling tb_property_free_dir(). + * + * Return: Pointer to &struct tb_property_dir, %NULL in case of failure. */ struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir) { @@ -577,6 +583,8 @@ err_free: * @parent: Directory to add the property * @key: Key for the property * @value: Immediate value to store with the property + * + * Return: %0 on success, negative errno otherwise. */ int tb_property_add_immediate(struct tb_property_dir *parent, const char *key, u32 value) @@ -606,6 +614,8 @@ EXPORT_SYMBOL_GPL(tb_property_add_immediate); * @buflen: Number of bytes in the data buffer * * Function takes a copy of @buf and adds it to the directory. + * + * Return: %0 on success, negative errno otherwise. */ int tb_property_add_data(struct tb_property_dir *parent, const char *key, const void *buf, size_t buflen) @@ -642,6 +652,8 @@ EXPORT_SYMBOL_GPL(tb_property_add_data); * @text: String to add * * Function takes a copy of @text and adds it to the directory. + * + * Return: %0 on success, negative errno otherwise. */ int tb_property_add_text(struct tb_property_dir *parent, const char *key, const char *text) @@ -676,6 +688,8 @@ EXPORT_SYMBOL_GPL(tb_property_add_text); * @parent: Directory to add the property * @key: Key for the property * @dir: Directory to add + * + * Return: %0 on success, negative errno otherwise. */ int tb_property_add_dir(struct tb_property_dir *parent, const char *key, struct tb_property_dir *dir) @@ -716,8 +730,10 @@ EXPORT_SYMBOL_GPL(tb_property_remove); * @key: Key to look for * @type: Type of the property * - * Finds and returns property from the given directory. Does not recurse - * into sub-directories. Returns %NULL if the property was not found. + * Finds and returns property from the given directory. Does not + * recurse into sub-directories. + * + * Return: Pointer to &struct tb_property, %NULL if the property was not found. */ struct tb_property *tb_property_find(struct tb_property_dir *dir, const char *key, enum tb_property_type type) @@ -737,6 +753,8 @@ EXPORT_SYMBOL_GPL(tb_property_find); * tb_property_get_next() - Get next property from directory * @dir: Directory holding properties * @prev: Previous property in the directory (%NULL returns the first) + * + * Return: Pointer to &struct tb_property, %NULL if property was not found. */ struct tb_property *tb_property_get_next(struct tb_property_dir *dir, struct tb_property *prev) diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c index 361fece3d818..3a0f486a24d5 100644 --- a/drivers/thunderbolt/retimer.c +++ b/drivers/thunderbolt/retimer.c @@ -27,8 +27,9 @@ * @buf: Data read from NVM is stored here * @size: Number of bytes to read * - * Reads retimer NVM and copies the contents to @buf. Returns %0 if the - * read was successful and negative errno in case of failure. + * Reads retimer NVM and copies the contents to @buf. + * + * Return: %0 if the read was successful, negative errno in case of failure. */ int tb_retimer_nvm_read(struct tb_retimer *rt, unsigned int address, void *buf, size_t size) @@ -503,6 +504,8 @@ static struct tb_retimer *tb_port_find_retimer(struct tb_port *port, u8 index) * Then Tries to enumerate on-board retimers connected to @port. Found * retimers are registered as children of @port if @add is set. Does * not scan for cable retimers for now. + * + * Return: %0 on success, negative errno otherwise. */ int tb_retimer_scan(struct tb_port *port, bool add) { diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index e9f4186f20f4..0e07904aa73b 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -290,8 +290,9 @@ static int nvm_authenticate(struct tb_switch *sw, bool auth_only) * @size: Size of the buffer in bytes * * Reads from router NVM and returns the requested data in @buf. Locking - * is up to the caller. Returns %0 in success and negative errno in case - * of failure. + * is up to the caller. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size) @@ -464,7 +465,7 @@ static void tb_dump_port(struct tb *tb, const struct tb_port *port) * * The port must have a TB_CAP_PHY (i.e. it should be a real port). * - * Return: Returns an enum tb_port_state on success or an error code on failure. + * Return: &enum tb_port_state or negative error code on failure. */ int tb_port_state(struct tb_port *port) { @@ -491,9 +492,11 @@ int tb_port_state(struct tb_port *port) * switch resume). Otherwise we only wait if a device is registered but the link * has not yet been established. * - * Return: Returns an error code on failure. Returns 0 if the port is not - * connected or failed to reach state TB_PORT_UP within one second. Returns 1 - * if the port is connected and in state TB_PORT_UP. + * Return: + * * %0 - If the port is not connected or failed to reach + * state %TB_PORT_UP within one second. + * * %1 - If the port is connected and in state %TB_PORT_UP. + * * Negative errno - An error occurred. */ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) { @@ -562,7 +565,7 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) * Change the number of NFC credits allocated to @port by @credits. To remove * NFC credits pass a negative amount of credits. * - * Return: Returns 0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ int tb_port_add_nfc_credits(struct tb_port *port, int credits) { @@ -599,7 +602,7 @@ int tb_port_add_nfc_credits(struct tb_port *port, int credits) * @port: Port whose counters to clear * @counter: Counter index to clear * - * Return: Returns 0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ int tb_port_clear_counter(struct tb_port *port, int counter) { @@ -614,6 +617,8 @@ int tb_port_clear_counter(struct tb_port *port, int counter) * * Needed for USB4 but can be called for any CIO/USB4 ports. Makes the * downstream router accessible for CM. + * + * Return: %0 on success, negative errno otherwise. */ int tb_port_unlock(struct tb_port *port) { @@ -659,6 +664,8 @@ static int __tb_port_enable(struct tb_port *port, bool enable) * @port: Port to enable (can be %NULL) * * This is used for lane 0 and 1 adapters to enable it. + * + * Return: %0 on success, negative errno otherwise. */ int tb_port_enable(struct tb_port *port) { @@ -670,6 +677,8 @@ int tb_port_enable(struct tb_port *port) * @port: Port to disable (can be %NULL) * * This is used for lane 0 and 1 adapters to disable it. + * + * Return: %0 on success, negative errno otherwise. */ int tb_port_disable(struct tb_port *port) { @@ -689,7 +698,7 @@ static int tb_port_reset(struct tb_port *port) * This is a helper method for tb_switch_alloc. Does not check or initialize * any downstream switches. * - * Return: Returns 0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ static int tb_init_port(struct tb_port *port) { @@ -847,9 +856,9 @@ static inline bool tb_switch_is_reachable(const struct tb_switch *parent, * link port, the function follows that link and returns another end on * that same link. * - * If the @end port has been reached, return %NULL. - * * Domain tb->lock must be held when this function is called. + * + * Return: Pointer to &struct tb_port, %NULL if the @end port has been reached. */ struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end, struct tb_port *prev) @@ -894,7 +903,7 @@ struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end, * tb_port_get_link_speed() - Get current link speed * @port: Port to check (USB4 or CIO) * - * Returns link speed in Gb/s or negative errno in case of failure. + * Return: Link speed in Gb/s or negative errno in case of failure. */ int tb_port_get_link_speed(struct tb_port *port) { @@ -926,9 +935,11 @@ int tb_port_get_link_speed(struct tb_port *port) * tb_port_get_link_generation() - Returns link generation * @port: Lane adapter * - * Returns link generation as number or negative errno in case of - * failure. Does not distinguish between Thunderbolt 1 and Thunderbolt 2 - * links so for those always returns 2. + * Return: Link generation as a number or negative errno in case of + * failure. + * + * Does not distinguish between Thunderbolt 1 and Thunderbolt 2 + * links so for those always returns %2. */ int tb_port_get_link_generation(struct tb_port *port) { @@ -952,8 +963,8 @@ int tb_port_get_link_generation(struct tb_port *port) * tb_port_get_link_width() - Get current link width * @port: Port to check (USB4 or CIO) * - * Returns link width. Return the link width as encoded in &enum - * tb_link_width or negative errno in case of failure. + * Return: Link width encoded in &enum tb_link_width or + * negative errno in case of failure. */ int tb_port_get_link_width(struct tb_port *port) { @@ -979,7 +990,9 @@ int tb_port_get_link_width(struct tb_port *port) * @width: Widths to check (bitmask) * * Can be called to any lane adapter. Checks if given @width is - * supported by the hardware and returns %true if it is. + * supported by the hardware. + * + * Return: %true if link width is supported, %false otherwise. */ bool tb_port_width_supported(struct tb_port *port, unsigned int width) { @@ -1016,7 +1029,7 @@ bool tb_port_width_supported(struct tb_port *port, unsigned int width) * Sets the target link width of the lane adapter to @width. Does not * enable/disable lane bonding. For that call tb_port_set_lane_bonding(). * - * Return: %0 in case of success and negative errno in case of error + * Return: %0 on success, negative errno otherwise. */ int tb_port_set_link_width(struct tb_port *port, enum tb_link_width width) { @@ -1070,7 +1083,7 @@ int tb_port_set_link_width(struct tb_port *port, enum tb_link_width width) * cases one should use tb_port_lane_bonding_enable() instead to enable * lane bonding. * - * Return: %0 in case of success and negative errno in case of error + * Return: %0 on success, negative errno otherwise. */ static int tb_port_set_lane_bonding(struct tb_port *port, bool bonding) { @@ -1104,7 +1117,7 @@ static int tb_port_set_lane_bonding(struct tb_port *port, bool bonding) * tb_port_wait_for_link_width() before enabling any paths through the * link to make sure the link is in expected state. * - * Return: %0 in case of success and negative errno in case of error + * Return: %0 on success, negative errno otherwise. */ int tb_port_lane_bonding_enable(struct tb_port *port) { @@ -1181,9 +1194,14 @@ void tb_port_lane_bonding_disable(struct tb_port *port) * * Should be used after both ends of the link have been bonded (or * bonding has been disabled) to wait until the link actually reaches - * the expected state. Returns %-ETIMEDOUT if the width was not reached - * within the given timeout, %0 if it did. Can be passed a mask of - * expected widths and succeeds if any of the widths is reached. + * the expected state. + * + * Can be passed a mask of expected widths. + * + * Return: + * * %0 - If link reaches any of the specified widths. + * * %-ETIMEDOUT - If link does not reach specified width. + * * Negative errno - Another error occurred. */ int tb_port_wait_for_link_width(struct tb_port *port, unsigned int width, int timeout_msec) @@ -1248,6 +1266,8 @@ static int tb_port_do_update_credits(struct tb_port *port) * After the link is bonded (or bonding was disabled) the port total * credits may change, so this function needs to be called to re-read * the credits. Updates also the second lane adapter. + * + * Return: %0 on success, negative errno otherwise. */ int tb_port_update_credits(struct tb_port *port) { @@ -1303,6 +1323,8 @@ static bool tb_port_resume(struct tb_port *port) /** * tb_port_is_enabled() - Is the adapter port enabled * @port: Port to check + * + * Return: %true if port is enabled, %false otherwise. */ bool tb_port_is_enabled(struct tb_port *port) { @@ -1327,6 +1349,8 @@ bool tb_port_is_enabled(struct tb_port *port) /** * tb_usb3_port_is_enabled() - Is the USB3 adapter port enabled * @port: USB3 adapter port to check + * + * Return: %true if port is enabled, %false otherwise. */ bool tb_usb3_port_is_enabled(struct tb_port *port) { @@ -1343,6 +1367,8 @@ bool tb_usb3_port_is_enabled(struct tb_port *port) * tb_usb3_port_enable() - Enable USB3 adapter port * @port: USB3 adapter port to enable * @enable: Enable/disable the USB3 adapter + * + * Return: %0 on success, negative errno otherwise. */ int tb_usb3_port_enable(struct tb_port *port, bool enable) { @@ -1358,6 +1384,8 @@ int tb_usb3_port_enable(struct tb_port *port, bool enable) /** * tb_pci_port_is_enabled() - Is the PCIe adapter port enabled * @port: PCIe port to check + * + * Return: %true if port is enabled, %false otherwise. */ bool tb_pci_port_is_enabled(struct tb_port *port) { @@ -1374,6 +1402,8 @@ bool tb_pci_port_is_enabled(struct tb_port *port) * tb_pci_port_enable() - Enable PCIe adapter port * @port: PCIe port to enable * @enable: Enable/disable the PCIe adapter + * + * Return: %0 on success, negative errno otherwise. */ int tb_pci_port_enable(struct tb_port *port, bool enable) { @@ -1389,6 +1419,8 @@ int tb_pci_port_enable(struct tb_port *port, bool enable) * @port: DP out port to check * * Checks if the DP OUT adapter port has HPD bit already set. + * + * Return: %1 if HPD is active, %0 otherwise. */ int tb_dp_port_hpd_is_active(struct tb_port *port) { @@ -1408,6 +1440,8 @@ int tb_dp_port_hpd_is_active(struct tb_port *port) * @port: Port to clear HPD * * If the DP IN port has HPD set, this function can be used to clear it. + * + * Return: %0 on success, negative errno otherwise. */ int tb_dp_port_hpd_clear(struct tb_port *port) { @@ -1434,6 +1468,8 @@ int tb_dp_port_hpd_clear(struct tb_port *port) * Programs specified Hop IDs for DP IN/OUT port. Can be called for USB4 * router DP adapters too but does not program the values as the fields * are read-only. + * + * Return: %0 on success, negative errno otherwise. */ int tb_dp_port_set_hops(struct tb_port *port, unsigned int video, unsigned int aux_tx, unsigned int aux_rx) @@ -1466,6 +1502,8 @@ int tb_dp_port_set_hops(struct tb_port *port, unsigned int video, /** * tb_dp_port_is_enabled() - Is DP adapter port enabled * @port: DP adapter port to check + * + * Return: %true if DP port is enabled, %false otherwise. */ bool tb_dp_port_is_enabled(struct tb_port *port) { @@ -1485,6 +1523,8 @@ bool tb_dp_port_is_enabled(struct tb_port *port) * * Once Hop IDs are programmed DP paths can be enabled or disabled by * calling this function. + * + * Return: %0 on success, negative errno otherwise. */ int tb_dp_port_enable(struct tb_port *port, bool enable) { @@ -1634,7 +1674,7 @@ static bool tb_switch_enumerated(struct tb_switch *sw) * * If the router is not enumerated does nothing. * - * Returns %0 on success or negative errno in case of failure. + * Return: %0 on success, negative errno otherwise. */ int tb_switch_reset(struct tb_switch *sw) { @@ -1670,8 +1710,12 @@ int tb_switch_reset(struct tb_switch *sw) * @timeout_msec: Timeout in ms how long to wait * * Wait till the specified bits in specified offset reach specified value. - * Returns %0 in case of success, %-ETIMEDOUT if the @value was not reached - * within the given timeout or a negative errno in case of failure. + * + * Return: + * * %0 - On success. + * * %-ETIMEDOUT - If the @value was not reached within + * the given timeout. + * * Negative errno - In case of failure. */ int tb_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit, u32 value, int timeout_msec) @@ -1700,7 +1744,7 @@ int tb_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit, * * Also configures a sane plug_events_delay of 255ms. * - * Return: Returns 0 on success or an error code on failure. + * Return: %0 on success, negative errno otherwise. */ static int tb_plug_events_active(struct tb_switch *sw, bool active) { @@ -2406,8 +2450,7 @@ static bool tb_switch_exceeds_max_depth(const struct tb_switch *sw, int depth) * separately. The returned switch should be released by calling * tb_switch_put(). * - * Return: Pointer to the allocated switch or ERR_PTR() in case of - * failure. + * Return: Pointer to &struct tb_switch or ERR_PTR() in case of failure. */ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent, u64 route) @@ -2526,7 +2569,7 @@ err_free_sw_ports: * * The returned switch must be released by calling tb_switch_put(). * - * Return: Pointer to the allocated switch or ERR_PTR() in case of failure + * Return: Pointer to &struct tb_switch or ERR_PTR() in case of failure. */ struct tb_switch * tb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route) @@ -2562,7 +2605,7 @@ tb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route) * connection manager to use. Can be called to the switch again after * resume from low power states to re-initialize it. * - * Return: %0 in case of success and negative errno in case of failure + * Return: %0 on success, negative errno otherwise. */ int tb_switch_configure(struct tb_switch *sw) { @@ -2625,7 +2668,7 @@ int tb_switch_configure(struct tb_switch *sw) * Needs to be called before any tunnels can be setup through the * router. Can be called to any router. * - * Returns %0 in success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_switch_configuration_valid(struct tb_switch *sw) { @@ -2900,6 +2943,8 @@ static void tb_switch_link_init(struct tb_switch *sw) * Connection manager can call this function to enable lane bonding of a * switch. If conditions are correct and both switches support the feature, * lanes are bonded. It is safe to call this to any switch. + * + * Return: %0 on success, negative errno otherwise. */ static int tb_switch_lane_bonding_enable(struct tb_switch *sw) { @@ -2950,6 +2995,8 @@ static int tb_switch_lane_bonding_enable(struct tb_switch *sw) * * Disables lane bonding between @sw and parent. This can be called even * if lanes were not bonded originally. + * + * Return: %0 on success, negative errno otherwise. */ static int tb_switch_lane_bonding_disable(struct tb_switch *sw) { @@ -3074,7 +3121,7 @@ static int tb_switch_asym_disable(struct tb_switch *sw) * * Does nothing for host router. * - * Returns %0 in case of success, negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_switch_set_link_width(struct tb_switch *sw, enum tb_link_width width) { @@ -3145,7 +3192,7 @@ int tb_switch_set_link_width(struct tb_switch *sw, enum tb_link_width width) * * It is recommended that this is called after lane bonding is enabled. * - * Returns %0 on success and negative errno in case of error. + * Return: %0 on success and negative errno otherwise. */ int tb_switch_configure_link(struct tb_switch *sw) { @@ -3245,7 +3292,7 @@ static int tb_switch_port_hotplug_enable(struct tb_switch *sw) * exposed to the userspace when this function successfully returns. To * remove and release the switch, call tb_switch_remove(). * - * Return: %0 in case of success and negative errno in case of failure + * Return: %0 on success, negative errno otherwise. */ int tb_switch_add(struct tb_switch *sw) { @@ -3467,6 +3514,8 @@ static void tb_switch_check_wakes(struct tb_switch *sw) * suspend. If this is resume from system sleep, notifies PM core about the * wakes occurred during suspend. Disables all wakes, except USB4 wake of * upstream port for USB4 routers that shall be always enabled. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_resume(struct tb_switch *sw, bool runtime) { @@ -3617,7 +3666,9 @@ void tb_switch_suspend(struct tb_switch *sw, bool runtime) * @in: DP IN port * * Queries availability of DP resource for DP tunneling using switch - * specific means. Returns %true if resource is available. + * specific means. + * + * Return: %true if resource is available, %false otherwise. */ bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in) { @@ -3633,7 +3684,8 @@ bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in) * * Allocates DP resource for DP tunneling. The resource must be * available for this to succeed (see tb_switch_query_dp_resource()). - * Returns %0 in success and negative errno otherwise. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in) { @@ -3718,6 +3770,8 @@ static int tb_switch_match(struct device *dev, const void *data) * * Returned switch has reference count increased so the caller needs to * call tb_switch_put() when done with the switch. + * + * Return: Pointer to &struct tb_switch, %NULL if not found. */ struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link, u8 depth) { @@ -3743,6 +3797,8 @@ struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link, u8 depth) * * Returned switch has reference count increased so the caller needs to * call tb_switch_put() when done with the switch. + * + * Return: Pointer to &struct tb_switch, %NULL if not found. */ struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid) { @@ -3767,6 +3823,8 @@ struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid) * * Returned switch has reference count increased so the caller needs to * call tb_switch_put() when done with the switch. + * + * Return: Pointer to &struct tb_switch, %NULL if not found. */ struct tb_switch *tb_switch_find_by_route(struct tb *tb, u64 route) { @@ -3791,6 +3849,8 @@ struct tb_switch *tb_switch_find_by_route(struct tb *tb, u64 route) * tb_switch_find_port() - return the first port of @type on @sw or NULL * @sw: Switch to find the port from * @type: Port type to look for + * + * Return: Pointer to &struct tb_port, %NULL if not found. */ struct tb_port *tb_switch_find_port(struct tb_switch *sw, enum tb_port_type type) @@ -3859,6 +3919,8 @@ static int tb_switch_pcie_bridge_write(struct tb_switch *sw, unsigned int bridge * entry to PCIe L1 state. Shall be called after the upstream PCIe tunnel * was configured. Due to Intel platforms limitation, shall be called only * for first hop switch. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_pcie_l1_enable(struct tb_switch *sw) { @@ -3893,6 +3955,8 @@ int tb_switch_pcie_l1_enable(struct tb_switch *sw) * connected to the type-C port. Call only after PCIe tunnel has been * established. The function only does the connect if not done already * so can be called several times for the same router. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_xhci_connect(struct tb_switch *sw) { diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index c14ab1fbeeaf..4a94cb406bdf 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -225,14 +225,12 @@ static int tb_enable_clx(struct tb_switch *sw) return ret == -EOPNOTSUPP ? 0 : ret; } -/** - * tb_disable_clx() - Disable CL states up to host router - * @sw: Router to start +/* + * Disables CL states from @sw up to the host router. * - * Disables CL states from @sw up to the host router. Returns true if - * any CL state were disabled. This can be used to figure out whether - * the link was setup by us or the boot firmware so we don't - * accidentally enable them if they were not enabled during discovery. + * This can be used to figure out whether the link was setup by us or the + * boot firmware so we don't accidentally enable them if they were not + * enabled during discovery. */ static bool tb_disable_clx(struct tb_switch *sw) { @@ -456,10 +454,8 @@ static void tb_scan_xdomain(struct tb_port *port) } } -/** - * tb_find_unused_port() - return the first inactive port on @sw - * @sw: Switch to find the port on - * @type: Port type to look for +/* + * Returns the first inactive port on @sw. */ static struct tb_port *tb_find_unused_port(struct tb_switch *sw, enum tb_port_type type) @@ -549,6 +545,8 @@ static struct tb_tunnel *tb_find_first_usb3_tunnel(struct tb *tb, * from @src_port to @dst_port. Does not take USB3 tunnel starting from * @src_port and ending on @src_port into account because that bandwidth is * already included in as part of the "first hop" USB3 tunnel. + * + * Return: %0 on success, negative errno otherwise. */ static int tb_consumed_usb3_pcie_bandwidth(struct tb *tb, struct tb_port *src_port, @@ -601,6 +599,8 @@ static int tb_consumed_usb3_pcie_bandwidth(struct tb *tb, * If there is bandwidth reserved for any of the groups between * @src_port and @dst_port (but not yet used) that is also taken into * account in the returned consumed bandwidth. + * + * Return: %0 on success, negative errno otherwise. */ static int tb_consumed_dp_bandwidth(struct tb *tb, struct tb_port *src_port, @@ -701,6 +701,8 @@ static bool tb_asym_supported(struct tb_port *src_port, struct tb_port *dst_port * single link at @port. If @include_asym is set then includes the * additional banwdith if the links are transitioned into asymmetric to * direction from @src_port to @dst_port. + * + * Return: %0 on success, negative errno otherwise. */ static int tb_maximum_bandwidth(struct tb *tb, struct tb_port *src_port, struct tb_port *dst_port, struct tb_port *port, @@ -807,6 +809,8 @@ static int tb_maximum_bandwidth(struct tb *tb, struct tb_port *src_port, * If @include_asym is true then includes also bandwidth that can be * added when the links are transitioned into asymmetric (but does not * transition the links). + * + * Return: %0 on success, negative errno otherwise. */ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port, struct tb_port *dst_port, int *available_up, @@ -1029,6 +1033,8 @@ static int tb_create_usb3_tunnels(struct tb_switch *sw) * (requested + currently consumed) on that link exceed @asym_threshold. * * Must be called with available >= requested over all links. + * + * Return: %0 on success, negative errno otherwise. */ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port, struct tb_port *dst_port, int requested_up, @@ -1135,6 +1141,8 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port, * Goes over each link from @src_port to @dst_port and tries to * transition the link to symmetric if the currently consumed bandwidth * allows and link asymmetric preference is ignored (if @keep_asym is %false). + * + * Return: %0 on success, negative errno otherwise. */ static int tb_configure_sym(struct tb *tb, struct tb_port *src_port, struct tb_port *dst_port, bool keep_asym) @@ -3336,7 +3344,7 @@ static bool tb_apple_add_links(struct tb_nhi *nhi) if (!pci_is_pcie(pdev)) continue; if (pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM || - !pdev->is_hotplug_bridge) + !pdev->is_pciehp) continue; link = device_link_add(&pdev->dev, &nhi->pdev->dev, diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index f503bad86413..8e2762ff8d51 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -324,7 +324,7 @@ struct usb4_port { }; /** - * tb_retimer: Thunderbolt retimer + * struct tb_retimer - Thunderbolt retimer * @dev: Device for the retimer * @tb: Pointer to the domain the retimer belongs to * @index: Retimer index facing the router USB4 port @@ -552,13 +552,14 @@ static inline void *tb_priv(struct tb *tb) /** * tb_upstream_port() - return the upstream port of a switch + * @sw: Router * * Every switch has an upstream port (for the root switch it is the NHI). * * During switch alloc/init tb_upstream_port()->remote may be NULL, even for * non root switches (on the NHI port remote is always NULL). * - * Return: Returns the upstream port of the switch. + * Return: Pointer to &struct tb_port. */ static inline struct tb_port *tb_upstream_port(struct tb_switch *sw) { @@ -569,8 +570,8 @@ static inline struct tb_port *tb_upstream_port(struct tb_switch *sw) * tb_is_upstream_port() - Is the port upstream facing * @port: Port to check * - * Returns true if @port is upstream facing port. In case of dual link - * ports both return true. + * Return: %true if @port is upstream facing port. In case of dual link + * ports, both return %true. */ static inline bool tb_is_upstream_port(const struct tb_port *port) { @@ -613,7 +614,7 @@ static inline const char *tb_width_name(enum tb_link_width width) * tb_port_has_remote() - Does the port have switch connected downstream * @port: Port to check * - * Returns true only when the port is primary port and has remote set. + * Return: %true only when the port is primary port and has remote set. */ static inline bool tb_port_has_remote(const struct tb_port *port) { @@ -905,8 +906,9 @@ static inline struct tb_switch *tb_switch_parent(struct tb_switch *sw) * tb_switch_downstream_port() - Return downstream facing port of parent router * @sw: Device router pointer * - * Only call for device routers. Returns the downstream facing port of - * the parent router. + * Call only for device routers. + * + * Return: Pointer to &struct tb_port or %NULL in case of failure. */ static inline struct tb_port *tb_switch_downstream_port(struct tb_switch *sw) { @@ -918,6 +920,8 @@ static inline struct tb_port *tb_switch_downstream_port(struct tb_switch *sw) /** * tb_switch_depth() - Returns depth of the connected router * @sw: Router + * + * Return: Router depth level as a number. */ static inline int tb_switch_depth(const struct tb_switch *sw) { @@ -1010,6 +1014,9 @@ static inline bool tb_switch_is_tiger_lake(const struct tb_switch *sw) * is handling @sw this function can be called. It is valid to call this * after tb_switch_alloc() and tb_switch_configure() has been called * (latter only for SW CM case). + * + * Return: %true if switch is handled by ICM, %false if handled by + * software CM. */ static inline bool tb_switch_is_icm(const struct tb_switch *sw) { @@ -1037,6 +1044,8 @@ int tb_switch_tmu_configure(struct tb_switch *sw, enum tb_switch_tmu_mode mode); * * Checks if given router TMU mode is configured to @mode. Note the * router TMU might not be enabled to this mode. + * + * Return: %true if TMU mode is equal to @mode, %false otherwise. */ static inline bool tb_switch_tmu_is_configured(const struct tb_switch *sw, enum tb_switch_tmu_mode mode) @@ -1048,8 +1057,8 @@ static inline bool tb_switch_tmu_is_configured(const struct tb_switch *sw, * tb_switch_tmu_is_enabled() - Checks if the specified TMU mode is enabled * @sw: Router whose TMU mode to check * - * Return true if hardware TMU configuration matches the requested - * configuration (and is not %TB_SWITCH_TMU_MODE_OFF). + * Return: %true if hardware TMU configuration matches the requested + * configuration (and is not %TB_SWITCH_TMU_MODE_OFF), %false otherwise. */ static inline bool tb_switch_tmu_is_enabled(const struct tb_switch *sw) { @@ -1069,9 +1078,10 @@ int tb_switch_clx_disable(struct tb_switch *sw); * @clx: The CLx states to check for * * Checks if the specified CLx is enabled on the router upstream link. - * Returns true if any of the given states is enabled. * * Not applicable for a host router. + * + * Return: %true if any of the given states is enabled, %false otherwise. */ static inline bool tb_switch_clx_is_enabled(const struct tb_switch *sw, unsigned int clx) @@ -1103,7 +1113,7 @@ struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end, * @src: Source adapter * @dst: Destination adapter * - * Returns %true only if the specified path from source adapter (@src) + * Return: %true only if the specified path from source adapter (@src) * to destination adapter (@dst) is directed downstream. */ static inline bool @@ -1232,10 +1242,11 @@ static inline int tb_route_length(u64 route) /** * tb_downstream_route() - get route to downstream switch + * @port: Port to check * * Port must not be the upstream port (otherwise a loop is created). * - * Return: Returns a route to the switch behind @port. + * Return: Route to the switch behind @port. */ static inline u64 tb_downstream_route(struct tb_port *port) { @@ -1263,7 +1274,7 @@ static inline struct tb_switch *tb_xdomain_parent(struct tb_xdomain *xd) * tb_xdomain_downstream_port() - Return downstream facing port of parent router * @xd: Xdomain pointer * - * Returns the downstream port the XDomain is connected to. + * Return: Pointer to &struct tb_port or %NULL in case of failure. */ static inline struct tb_port *tb_xdomain_downstream_port(struct tb_xdomain *xd) { @@ -1291,7 +1302,7 @@ static inline struct tb_retimer *tb_to_retimer(struct device *dev) * usb4_switch_version() - Returns USB4 version of the router * @sw: Router to check * - * Returns major version of USB4 router (%1 for v1, %2 for v2 and so + * Return: Major version of USB4 router (%1 for v1, %2 for v2 and so * on). Can be called to pre-USB4 router too and in that case returns %0. */ static inline unsigned int usb4_switch_version(const struct tb_switch *sw) @@ -1303,7 +1314,7 @@ static inline unsigned int usb4_switch_version(const struct tb_switch *sw) * tb_switch_is_usb4() - Is the switch USB4 compliant * @sw: Switch to check * - * Returns true if the @sw is USB4 compliant router, false otherwise. + * Return: %true if the @sw is USB4 compliant router, %false otherwise. */ static inline bool tb_switch_is_usb4(const struct tb_switch *sw) { @@ -1355,7 +1366,7 @@ int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width int usb4_port_asym_start(struct tb_port *port); /** - * enum tb_sb_target - Sideband transaction target + * enum usb4_sb_target - Sideband transaction target * @USB4_SB_TARGET_ROUTER: Target is the router itself * @USB4_SB_TARGET_PARTNER: Target is partner * @USB4_SB_TARGET_RETIMER: Target is retimer @@ -1400,6 +1411,8 @@ enum usb4_margining_lane { * @voltage_time_offset: Offset for voltage / time for software margining * @optional_voltage_offset_range: Enable optional extended voltage range * @right_high: %false if left/low margin test is performed, %true if right/high + * @upper_eye: %true if margin test is done on upper eye, %false if done on + * lower eye * @time: %true if time margining is used instead of voltage */ struct usb4_port_margining_params { diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c index 9a259c72e5a7..b22831b41ec0 100644 --- a/drivers/thunderbolt/tmu.c +++ b/drivers/thunderbolt/tmu.c @@ -405,6 +405,8 @@ static int tmu_mode_init(struct tb_switch *sw) * This function must be called before other TMU related functions to * makes the internal structures are filled in correctly. Does not * change any hardware configuration. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_tmu_init(struct tb_switch *sw) { @@ -439,6 +441,8 @@ int tb_switch_tmu_init(struct tb_switch *sw) * @sw: Switch whose time to update * * Updates switch local time using time posting procedure. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_tmu_post_time(struct tb_switch *sw) { @@ -555,6 +559,8 @@ static int disable_enhanced(struct tb_port *up, struct tb_port *down) * @sw: Switch whose TMU to disable * * Turns off TMU of @sw if it is enabled. If not enabled does nothing. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_tmu_disable(struct tb_switch *sw) { @@ -938,6 +944,8 @@ out: * Enables TMU of a router to be in uni-directional Normal/HiFi or * bi-directional HiFi mode. Calling tb_switch_tmu_configure() is * required before calling this function. + * + * Return: %0 on success, negative errno otherwise. */ int tb_switch_tmu_enable(struct tb_switch *sw) { @@ -1017,9 +1025,11 @@ int tb_switch_tmu_enable(struct tb_switch *sw) * Selects the TMU mode that is enabled when tb_switch_tmu_enable() is * next called. * - * Returns %0 in success and negative errno otherwise. Specifically - * returns %-EOPNOTSUPP if the requested mode is not possible (not - * supported by the router and/or topology). + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the requested mode is not possible (not supported by + * the router and/or topology). + * * Negative errno - Another error occurred. */ int tb_switch_tmu_configure(struct tb_switch *sw, enum tb_switch_tmu_mode mode) { diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index d52efe3f658c..bfa0607b5574 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -121,6 +121,8 @@ static inline unsigned int tb_usable_credits(const struct tb_port *port) * @port: Lane adapter to check * @max_dp_streams: If non-%NULL stores maximum number of simultaneous DP * streams possible through this lane adapter + * + * Return: Number of available credits. */ static unsigned int tb_available_credits(const struct tb_port *port, size_t *max_dp_streams) @@ -415,8 +417,9 @@ static int tb_pci_init_path(struct tb_path *path) * @alloc_hopid: Allocate HopIDs from visited ports * * If @down adapter is active, follows the tunnel to the PCIe upstream - * adapter and back. Returns the discovered tunnel or %NULL if there was - * no tunnel. + * adapter and back. + * + * Return: Pointer to &struct tb_tunnel or %NULL if there was no tunnel. */ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, bool alloc_hopid) @@ -496,7 +499,7 @@ err_free: * Allocate a PCI tunnel. The ports must be of type TB_TYPE_PCIE_UP and * TB_TYPE_PCIE_DOWN. * - * Return: Returns a tb_tunnel on success or NULL on failure. + * Return: Pointer to @struct tb_tunnel or %NULL on failure. */ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, struct tb_port *down) @@ -543,9 +546,12 @@ err_free: * * Can be called to any connected lane 0 adapter to find out how much * bandwidth needs to be left in reserve for possible PCIe bulk traffic. - * Returns true if there is something to be reserved and writes the - * amount to @reserved_down/@reserved_up. Otherwise returns false and - * does not touch the parameters. + * + * Return: + * * %true - If there is something to be reserved. Writes the amount to + * @reserved_down/@reserved_up. + * * %false - Nothing to be reserved. Leaves @reserved_down/@reserved_up + * unmodified. */ bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up, int *reserved_down) @@ -1073,6 +1079,7 @@ static void tb_dp_dprx_work(struct work_struct *work) if (tunnel->callback) tunnel->callback(tunnel, tunnel->callback_data); + tb_tunnel_put(tunnel); } static int tb_dp_dprx_start(struct tb_tunnel *tunnel) @@ -1100,8 +1107,8 @@ static void tb_dp_dprx_stop(struct tb_tunnel *tunnel) if (tunnel->dprx_started) { tunnel->dprx_started = false; tunnel->dprx_canceled = true; - cancel_delayed_work(&tunnel->dprx_work); - tb_tunnel_put(tunnel); + if (cancel_delayed_work(&tunnel->dprx_work)) + tb_tunnel_put(tunnel); } } @@ -1151,7 +1158,8 @@ static int tb_dp_activate(struct tb_tunnel *tunnel, bool active) * @tunnel: DP tunnel to check * @max_bw_rounded: Maximum bandwidth in Mb/s rounded up to the next granularity * - * Returns maximum possible bandwidth for this tunnel in Mb/s. + * Return: Maximum possible bandwidth for this tunnel in Mb/s, negative errno + * in case of failure. */ static int tb_dp_bandwidth_mode_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_bw_rounded) @@ -1547,7 +1555,7 @@ static void tb_dp_dump(struct tb_tunnel *tunnel) * and back. Returns the discovered tunnel or %NULL if there was no * tunnel. * - * Return: DP tunnel or %NULL if no tunnel found. + * Return: Pointer to &struct tb_tunnel or %NULL if no tunnel found. */ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, bool alloc_hopid) @@ -1648,7 +1656,7 @@ err_free: * successful (or if it returns %false there was some sort of issue). * The @callback is called without @tb->lock held. * - * Return: Returns a tb_tunnel on success or &NULL on failure. + * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. */ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, struct tb_port *out, int link_nr, @@ -1861,7 +1869,7 @@ static void tb_dma_destroy(struct tb_tunnel *tunnel) * @receive_ring: NHI ring number used to receive packets from the * other domain. Set to %-1 if RX path is not needed. * - * Return: Returns a tb_tunnel on success or NULL on failure. + * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. */ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, struct tb_port *dst, int transmit_path, @@ -1938,7 +1946,8 @@ err_free: * * This function can be used to match specific DMA tunnel, if there are * multiple DMA tunnels going through the same XDomain connection. - * Returns true if there is match and false otherwise. + * + * Return: %true if there is a match, %false otherwise. */ bool tb_tunnel_match_dma(const struct tb_tunnel *tunnel, int transmit_path, int transmit_ring, int receive_path, int receive_ring) @@ -2160,8 +2169,9 @@ static void tb_usb3_init_path(struct tb_path *path) * @alloc_hopid: Allocate HopIDs from visited ports * * If @down adapter is active, follows the tunnel to the USB3 upstream - * adapter and back. Returns the discovered tunnel or %NULL if there was - * no tunnel. + * adapter and back. + * + * Return: Pointer to &struct tb_tunnel or %NULL if there was no tunnel. */ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, bool alloc_hopid) @@ -2266,7 +2276,7 @@ err_free: * Allocate an USB3 tunnel. The ports must be of type @TB_TYPE_USB3_UP and * @TB_TYPE_USB3_DOWN. * - * Return: Returns a tb_tunnel on success or %NULL on failure. + * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. */ struct tb_tunnel *tb_tunnel_alloc_usb3(struct tb *tb, struct tb_port *up, struct tb_port *down, int max_up, @@ -2337,6 +2347,8 @@ err_free: /** * tb_tunnel_is_invalid - check whether an activated path is still valid * @tunnel: Tunnel to check + * + * Return: %true if path is valid, %false otherwise. */ bool tb_tunnel_is_invalid(struct tb_tunnel *tunnel) { @@ -2355,10 +2367,11 @@ bool tb_tunnel_is_invalid(struct tb_tunnel *tunnel) * tb_tunnel_activate() - activate a tunnel * @tunnel: Tunnel to activate * - * Return: 0 on success and negative errno in case if failure. - * Specifically returns %-EINPROGRESS if the tunnel activation is still - * in progress (that's for DP tunnels to complete DPRX capabilities - * read). + * Return: + * * %0 - On success. + * * %-EINPROGRESS - If the tunnel activation is still in progress (that's + * for DP tunnels to complete DPRX capabilities read). + * * Negative errno - Another error occurred. */ int tb_tunnel_activate(struct tb_tunnel *tunnel) { @@ -2438,8 +2451,8 @@ void tb_tunnel_deactivate(struct tb_tunnel *tunnel) * @tunnel: Tunnel to check * @port: Port to check * - * Returns true if @tunnel goes through @port (direction does not matter), - * false otherwise. + * Return: %true if @tunnel goes through @port (direction does not matter), + * %false otherwise. */ bool tb_tunnel_port_on_path(const struct tb_tunnel *tunnel, const struct tb_port *port) @@ -2469,9 +2482,11 @@ static bool tb_tunnel_is_activated(const struct tb_tunnel *tunnel) * @max_up: Maximum upstream bandwidth in Mb/s * @max_down: Maximum downstream bandwidth in Mb/s * - * Returns maximum possible bandwidth this tunnel can go if not limited - * by other bandwidth clients. If the tunnel does not support this - * returns %-EOPNOTSUPP. + * Return: + * * Maximum possible bandwidth this tunnel can support if not + * limited by other bandwidth clients. + * * %-EOPNOTSUPP - If the tunnel does not support this function. + * * %-ENOTCONN - If the tunnel is not active. */ int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up, int *max_down) @@ -2491,8 +2506,12 @@ int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up, * @allocated_down: Currently allocated downstream bandwidth in Mb/s is * stored here * - * Returns the bandwidth allocated for the tunnel. This may be higher - * than what the tunnel actually consumes. + * Return: + * * Bandwidth allocated for the tunnel. This may be higher than what the + * tunnel actually consumes. + * * %-EOPNOTSUPP - If the tunnel does not support this function. + * * %-ENOTCONN - If the tunnel is not active. + * * Negative errno - Another error occurred. */ int tb_tunnel_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up, int *allocated_down) @@ -2512,10 +2531,12 @@ int tb_tunnel_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up, * @alloc_up: New upstream bandwidth in Mb/s * @alloc_down: New downstream bandwidth in Mb/s * - * Tries to change tunnel bandwidth allocation. If succeeds returns %0 - * and updates @alloc_up and @alloc_down to that was actually allocated - * (it may not be the same as passed originally). Returns negative errno - * in case of failure. + * Tries to change tunnel bandwidth allocation. + * + * Return: + * * %0 - On success. Updates @alloc_up and @alloc_down to values that were + * actually allocated (it may not be the same as passed originally). + * * Negative errno - In case of failure. */ int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up, int *alloc_down) @@ -2546,8 +2567,9 @@ int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up, * Can be %NULL. * * Stores the amount of isochronous bandwidth @tunnel consumes in - * @consumed_up and @consumed_down. In case of success returns %0, - * negative errno otherwise. + * @consumed_up and @consumed_down. + * + * Return: %0 on success, negative errno otherwise. */ int tb_tunnel_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up, int *consumed_down) @@ -2585,7 +2607,7 @@ int tb_tunnel_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up, * If tunnel supports dynamic bandwidth management (USB3 tunnels at the * moment) this function makes it to release all the unused bandwidth. * - * Returns %0 in case of success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int tb_tunnel_release_unused_bandwidth(struct tb_tunnel *tunnel) { diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index 5e9fb73d5220..2c44fc8a10bc 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -142,10 +142,11 @@ void tb_tunnel_deactivate(struct tb_tunnel *tunnel); * tb_tunnel_is_active() - Is tunnel fully activated * @tunnel: Tunnel to check * - * Returns %true if @tunnel is fully activated. For other than DP - * tunnels this is pretty much once tb_tunnel_activate() returns - * successfully. However, for DP tunnels this returns %true only once the - * DPRX capabilities read has been issued successfully. + * Return: %true if @tunnel is fully activated. + * + * Note for DP tunnels this returns %true only once the DPRX capabilities + * read has been issued successfully. For other tunnels, this function + * returns %true pretty much once tb_tunnel_activate() returns successfully. */ static inline bool tb_tunnel_is_active(const struct tb_tunnel *tunnel) { diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index fdae76c8f728..76f01713a875 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -9,6 +9,7 @@ #include <linux/delay.h> #include <linux/ktime.h> +#include <linux/string_choices.h> #include <linux/units.h> #include "sb_regs.h" @@ -172,8 +173,8 @@ void usb4_switch_check_wakes(struct tb_switch *sw) return; tb_sw_dbg(sw, "PCIe wake: %s, USB3 wake: %s\n", - (val & ROUTER_CS_6_WOPS) ? "yes" : "no", - (val & ROUTER_CS_6_WOUS) ? "yes" : "no"); + str_yes_no(val & ROUTER_CS_6_WOPS), + str_yes_no(val & ROUTER_CS_6_WOUS)); wakeup = val & (ROUTER_CS_6_WOPS | ROUTER_CS_6_WOUS); } @@ -191,9 +192,9 @@ void usb4_switch_check_wakes(struct tb_switch *sw) break; tb_port_dbg(port, "USB4 wake: %s, connection wake: %s, disconnection wake: %s\n", - (val & PORT_CS_18_WOU4S) ? "yes" : "no", - (val & PORT_CS_18_WOCS) ? "yes" : "no", - (val & PORT_CS_18_WODS) ? "yes" : "no"); + str_yes_no(val & PORT_CS_18_WOU4S), + str_yes_no(val & PORT_CS_18_WOCS), + str_yes_no(val & PORT_CS_18_WODS)); wakeup_usb4 = val & (PORT_CS_18_WOU4S | PORT_CS_18_WOCS | PORT_CS_18_WODS); @@ -236,6 +237,8 @@ static bool link_is_usb4(struct tb_port *port) * * This does not set the configuration valid bit of the router. To do * that call usb4_switch_configuration_valid(). + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_setup(struct tb_switch *sw) { @@ -260,7 +263,7 @@ int usb4_switch_setup(struct tb_switch *sw) tbt3 = !(val & ROUTER_CS_6_TNS); tb_sw_dbg(sw, "TBT3 support: %s, xHCI: %s\n", - tbt3 ? "yes" : "no", xhci ? "yes" : "no"); + str_yes_no(tbt3), str_yes_no(xhci)); ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1); if (ret) @@ -303,7 +306,7 @@ int usb4_switch_setup(struct tb_switch *sw) * usb4_switch_setup() has been called. Can be called to host and device * routers (does nothing for the latter). * - * Returns %0 in success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_configuration_valid(struct tb_switch *sw) { @@ -333,6 +336,8 @@ int usb4_switch_configuration_valid(struct tb_switch *sw) * @uid: UID is stored here * * Reads 64-bit UID from USB4 router config space. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid) { @@ -370,6 +375,8 @@ static int usb4_switch_drom_read_block(void *data, * Uses USB4 router operations to read router DROM. For devices this * should always work but for hosts it may return %-EOPNOTSUPP in which * case the host router does not have DROM. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size) @@ -384,6 +391,8 @@ int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf, * * Checks whether conditions are met so that lane bonding can be * established with the upstream router. Call only for device routers. + * + * Return: %true if lane bonding is possible, %false otherwise. */ bool usb4_switch_lane_bonding_possible(struct tb_switch *sw) { @@ -406,6 +415,8 @@ bool usb4_switch_lane_bonding_possible(struct tb_switch *sw) * @runtime: Wake is being programmed during system runtime * * Enables/disables router to wake up from sleep. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags, bool runtime) { @@ -483,8 +494,10 @@ int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags, bool runtime) * usb4_switch_set_sleep() - Prepare the router to enter sleep * @sw: USB4 router * - * Sets sleep bit for the router. Returns when the router sleep ready + * Sets sleep bit for the router and waits until router sleep ready * bit has been asserted. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_set_sleep(struct tb_switch *sw) { @@ -510,9 +523,10 @@ int usb4_switch_set_sleep(struct tb_switch *sw) * usb4_switch_nvm_sector_size() - Return router NVM sector size * @sw: USB4 router * - * If the router supports NVM operations this function returns the NVM - * sector size in bytes. If NVM operations are not supported returns - * %-EOPNOTSUPP. + * Return: + * * NVM sector size in bytes if router supports NVM operations. + * * %-EOPNOTSUPP - If router does not support NVM operations. + * * Negative errno - Another error occurred. */ int usb4_switch_nvm_sector_size(struct tb_switch *sw) { @@ -559,8 +573,12 @@ static int usb4_switch_nvm_read_block(void *data, * @buf: Read data is placed here * @size: How many bytes to read * - * Reads NVM contents of the router. If NVM is not supported returns - * %-EOPNOTSUPP. + * Reads NVM contents of the router. + * + * Return: + * * %0 - Read completed successfully. + * * %-EOPNOTSUPP - NVM not supported. + * * Negative errno - Another error occurred. */ int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, size_t size) @@ -577,7 +595,7 @@ int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, * Explicitly sets NVM write offset. Normally when writing to NVM this * is done automatically by usb4_switch_nvm_write(). * - * Returns %0 in success and negative errno if there was a failure. + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_nvm_set_offset(struct tb_switch *sw, unsigned int address) { @@ -619,8 +637,12 @@ static int usb4_switch_nvm_write_next_block(void *data, unsigned int dwaddress, * @buf: Pointer to the data to write * @size: Size of @buf in bytes * - * Writes @buf to the router NVM using USB4 router operations. If NVM - * write is not supported returns %-EOPNOTSUPP. + * Writes @buf to the router NVM using USB4 router operations. + * + * Return: + * * %0 - Write completed successfully. + * * %-EOPNOTSUPP - NVM write not supported. + * * Negative errno - Another error occurred. */ int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address, const void *buf, size_t size) @@ -642,11 +664,13 @@ int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address, * After the new NVM has been written via usb4_switch_nvm_write(), this * function triggers NVM authentication process. The router gets power * cycled and if the authentication is successful the new NVM starts - * running. In case of failure returns negative errno. + * running. * * The caller should call usb4_switch_nvm_authenticate_status() to read * the status of the authentication after power cycle. It should be the * first router operation to avoid the status being lost. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_nvm_authenticate(struct tb_switch *sw) { @@ -674,11 +698,13 @@ int usb4_switch_nvm_authenticate(struct tb_switch *sw) * @status: Status code of the operation * * The function checks if there is status available from the last NVM - * authenticate router operation. If there is status then %0 is returned - * and the status code is placed in @status. Returns negative errno in case - * of failure. + * authenticate router operation. * * Must be called before any other router operation. + * + * Return: + * * %0 - If there is status. Status code is placed in @status. + * * Negative errno - Failure occurred. */ int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status) { @@ -722,7 +748,7 @@ int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status) * allocation fields accordingly. Specifically @sw->credits_allocation * is set to %true if these parameters can be used in tunneling. * - * Returns %0 on success and negative errno otherwise. + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_credits_init(struct tb_switch *sw) { @@ -861,8 +887,10 @@ err_invalid: * @in: DP IN adapter * * For DP tunneling this function can be used to query availability of - * DP IN resource. Returns true if the resource is available for DP - * tunneling, false otherwise. + * DP IN resource. + * + * Return: %true if the resource is available for DP tunneling, %false + * otherwise. */ bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in) { @@ -890,9 +918,12 @@ bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in) * @in: DP IN adapter * * Allocates DP IN resource for DP tunneling using USB4 router - * operations. If the resource was allocated returns %0. Otherwise - * returns negative errno, in particular %-EBUSY if the resource is - * already allocated. + * operations. + * + * Return: + * * %0 - Resource allocated successfully. + * * %-EBUSY - Resource is already allocated. + * * Negative errno - Other failure occurred. */ int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in) { @@ -916,6 +947,8 @@ int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in) * @in: DP IN adapter * * Releases the previously allocated DP IN resource. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in) { @@ -971,6 +1004,8 @@ int usb4_port_index(const struct tb_switch *sw, const struct tb_port *port) * downstream adapters where the PCIe topology is extended. This * function returns the corresponding downstream PCIe adapter or %NULL * if no such mapping was possible. + * + * Return: Pointer to &struct tb_port or %NULL if not found. */ struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw, const struct tb_port *port) @@ -1002,6 +1037,8 @@ struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw, * downstream adapters where the USB 3.x topology is extended. This * function returns the corresponding downstream USB 3.x adapter or * %NULL if no such mapping was possible. + * + * Return: Pointer to &struct tb_port or %NULL if not found. */ struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw, const struct tb_port *port) @@ -1031,7 +1068,7 @@ struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw, * For USB4 router finds all USB4 ports and registers devices for each. * Can be called to any router. * - * Return %0 in case of success and negative errno in case of failure. + * Return: %0 on success, negative errno otherwise. */ int usb4_switch_add_ports(struct tb_switch *sw) { @@ -1084,6 +1121,8 @@ void usb4_switch_remove_ports(struct tb_switch *sw) * * Unlocks USB4 downstream port so that the connection manager can * access the router below this port. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_unlock(struct tb_port *port) { @@ -1104,6 +1143,8 @@ int usb4_port_unlock(struct tb_port *port) * * Enables hot plug events on a given port. This is only intended * to be used on lane, DP-IN, and DP-OUT adapters. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_hotplug_enable(struct tb_port *port) { @@ -1123,6 +1164,8 @@ int usb4_port_hotplug_enable(struct tb_port *port) * @port: USB4 port to reset * * Issues downstream port reset to @port. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_reset(struct tb_port *port) { @@ -1184,6 +1227,8 @@ static int usb4_port_set_configured(struct tb_port *port, bool configured) * @port: USB4 router * * Sets the USB4 link to be configured for power management purposes. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_configure(struct tb_port *port) { @@ -1195,6 +1240,8 @@ int usb4_port_configure(struct tb_port *port) * @port: USB4 router * * Sets the USB4 link to be unconfigured for power management purposes. + * + * Return: %0 on success, negative errno otherwise. */ void usb4_port_unconfigure(struct tb_port *port) { @@ -1229,7 +1276,9 @@ static int usb4_set_xdomain_configured(struct tb_port *port, bool configured) * @xd: XDomain that is connected to the port * * Marks the USB4 port as being connected to another host and updates - * the link type. Returns %0 in success and negative errno in failure. + * the link type. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_configure_xdomain(struct tb_port *port, struct tb_xdomain *xd) { @@ -1299,7 +1348,8 @@ static int usb4_port_write_data(struct tb_port *port, const void *data, * @size: Size of @buf * * Reads data from sideband register @reg and copies it into @buf. - * Returns %0 in case of success and negative errno in case of failure. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target, u8 index, u8 reg, void *buf, u8 size) @@ -1350,8 +1400,9 @@ int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target, u8 index * @buf: Data to write * @size: Size of @buf * - * Writes @buf to sideband register @reg. Returns %0 in case of success - * and negative errno in case of failure. + * Writes @buf to sideband register @reg. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_sb_write(struct tb_port *port, enum usb4_sb_target target, u8 index, u8 reg, const void *buf, u8 size) @@ -1468,8 +1519,7 @@ static int usb4_port_set_router_offline(struct tb_port *port, bool offline) * port does not react on hotplug events anymore. This needs to be * called before retimer access is done when the USB4 links is not up. * - * Returns %0 in case of success and negative errno if there was an - * error. + * Return: %0 on success, negative errno otherwise. */ int usb4_port_router_offline(struct tb_port *port) { @@ -1481,6 +1531,8 @@ int usb4_port_router_offline(struct tb_port *port) * @port: USB4 port * * Makes the USB4 port functional again. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_router_online(struct tb_port *port) { @@ -1492,8 +1544,9 @@ int usb4_port_router_online(struct tb_port *port) * @port: USB4 port * * This forces the USB4 port to send broadcast RT transaction which - * makes the retimers on the link to assign index to themselves. Returns - * %0 in case of success and negative errno if there was an error. + * makes the retimers on the link assign index to themselves. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_enumerate_retimers(struct tb_port *port) { @@ -1510,6 +1563,8 @@ int usb4_port_enumerate_retimers(struct tb_port *port) * * PORT_CS_18_CPS bit reflects if the link supports CLx including * active cables (if connected on the link). + * + * Return: %true if Clx is supported, %false otherwise. */ bool usb4_port_clx_supported(struct tb_port *port) { @@ -1528,8 +1583,9 @@ bool usb4_port_clx_supported(struct tb_port *port) * usb4_port_asym_supported() - If the port supports asymmetric link * @port: USB4 port * - * Checks if the port and the cable supports asymmetric link and returns - * %true in that case. + * Checks if the port and the cable support asymmetric link. + * + * Return: %true if asymmetric link is supported, %false otherwise. */ bool usb4_port_asym_supported(struct tb_port *port) { @@ -1551,6 +1607,8 @@ bool usb4_port_asym_supported(struct tb_port *port) * * Sets USB4 port link width to @width. Can be called for widths where * usb4_port_asym_width_supported() returned @true. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width) { @@ -1595,8 +1653,10 @@ int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width * (according to what was previously set in tb_port_set_link_width(). * Wait for completion of the change. * - * Returns %0 in case of success, %-ETIMEDOUT if case of timeout or - * a negative errno in case of a failure. + * Return: + * * %0 - Symmetry change was successful. + * * %-ETIMEDOUT - Timeout occurred. + * * Negative errno - Other failure occurred. */ int usb4_port_asym_start(struct tb_port *port) { @@ -1640,6 +1700,8 @@ int usb4_port_asym_start(struct tb_port *port) * @ncaps: Number of elements in the caps array * * Reads the USB4 port lane margining capabilities into @caps. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target, u8 index, u32 *caps, size_t ncaps) @@ -1666,6 +1728,8 @@ int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target, * * Runs hardware lane margining on USB4 port and returns the result in * @results. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target, u8 index, const struct usb4_port_margining_params *params, @@ -1710,8 +1774,9 @@ int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target, * @results: Data word for the operation completion data * * Runs software lane margining on USB4 port. Read back the error - * counters by calling usb4_port_sw_margin_errors(). Returns %0 in - * success and negative errno otherwise. + * counters by calling usb4_port_sw_margin_errors(). + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target, u8 index, const struct usb4_port_margining_params *params, @@ -1758,7 +1823,8 @@ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target, * @errors: Error metadata is copied here. * * This reads back the software margining error counters from the port. - * Returns %0 in success and negative errno otherwise. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target, u8 index, u32 *errors) @@ -1789,6 +1855,8 @@ static inline int usb4_port_retimer_op(struct tb_port *port, u8 index, * * Enables sideband channel transations on SBTX. Can be used when USB4 * link does not go up, for example if there is no device connected. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index) { @@ -1816,6 +1884,8 @@ int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index) * * Disables sideband channel transations on SBTX. The reverse of * usb4_port_retimer_set_inbound_sbtx(). + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index) { @@ -1828,10 +1898,12 @@ int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index) * @port: USB4 port * @index: Retimer index * - * If the retimer at @index is last one (connected directly to the - * Type-C port) this function returns %1. If it is not returns %0. If - * the retimer is not present returns %-ENODEV. Otherwise returns - * negative errno. + * Return: + * * %1 - Retimer at @index is the last one (connected directly to the + * Type-C port). + * * %0 - Retimer at @index is not the last one. + * * %-ENODEV - Retimer is not present. + * * Negative errno - Other failure occurred. */ int usb4_port_retimer_is_last(struct tb_port *port, u8 index) { @@ -1853,9 +1925,11 @@ int usb4_port_retimer_is_last(struct tb_port *port, u8 index) * @port: USB4 port * @index: Retimer index * - * If the retimer at @index is last cable retimer this function returns - * %1 and %0 if it is on-board retimer. In case a retimer is not present - * at @index returns %-ENODEV. Otherwise returns negative errno. + * Return: + * * %1 - Retimer at @index is the last cable retimer. + * * %0 - Retimer at @index is on-board retimer. + * * %-ENODEV - Retimer is not present. + * * Negative errno - Other failure occurred. */ int usb4_port_retimer_is_cable(struct tb_port *port, u8 index) { @@ -1879,9 +1953,12 @@ int usb4_port_retimer_is_cable(struct tb_port *port, u8 index) * * Reads NVM sector size (in bytes) of a retimer at @index. This * operation can be used to determine whether the retimer supports NVM - * upgrade for example. Returns sector size in bytes or negative errno - * in case of error. Specifically returns %-ENODEV if there is no - * retimer at @index. + * upgrade for example. + * + * Return: + * * Sector size in bytes. + * * %-ENODEV - If there is no retimer at @index. + * * Negative errno - In case of an error. */ int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index) { @@ -1907,7 +1984,7 @@ int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index) * Exlicitly sets NVM write offset. Normally when writing to NVM this is * done automatically by usb4_port_retimer_nvm_write(). * - * Returns %0 in success and negative errno if there was a failure. + * Return: %0 on success, negative errno otherwise. */ int usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index, unsigned int address) @@ -1960,9 +2037,12 @@ static int usb4_port_retimer_nvm_write_next_block(void *data, * @size: Size in bytes how much to write * * Writes @size bytes from @buf to the retimer NVM. Used for NVM - * upgrade. Returns %0 if the data was written successfully and negative - * errno in case of failure. Specifically returns %-ENODEV if there is - * no retimer at @index. + * upgrade. + * + * Return: + * * %0 - If the data was written successfully. + * * %-ENODEV - If there is no retimer at @index. + * * Negative errno - In case of an error. */ int usb4_port_retimer_nvm_write(struct tb_port *port, u8 index, unsigned int address, const void *buf, size_t size) @@ -1988,6 +2068,8 @@ int usb4_port_retimer_nvm_write(struct tb_port *port, u8 index, unsigned int add * successful the retimer restarts with the new NVM and may not have the * index set so one needs to call usb4_port_enumerate_retimers() to * force index to be assigned. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_retimer_nvm_authenticate(struct tb_port *port, u8 index) { @@ -2012,9 +2094,9 @@ int usb4_port_retimer_nvm_authenticate(struct tb_port *port, u8 index) * This can be called after usb4_port_retimer_nvm_authenticate() and * usb4_port_enumerate_retimers() to fetch status of the NVM upgrade. * - * Returns %0 if the authentication status was successfully read. The + * Return: %0 if the authentication status was successfully read. The * completion metadata (the result) is then stored into @status. If - * reading the status fails, returns negative errno. + * status read fails, returns negative errno. */ int usb4_port_retimer_nvm_authenticate_status(struct tb_port *port, u8 index, u32 *status) @@ -2082,9 +2164,12 @@ static int usb4_port_retimer_nvm_read_block(void *data, unsigned int dwaddress, * @buf: Data read from NVM is stored here * @size: Number of bytes to read * - * Reads retimer NVM and copies the contents to @buf. Returns %0 if the - * read was successful and negative errno in case of failure. - * Specifically returns %-ENODEV if there is no retimer at @index. + * Reads retimer NVM and copies the contents to @buf. + * + * Return: + * * %0 - If the read was successful. + * * %-ENODEV - If there is no retimer at @index. + * * Negative errno - In case of an error. */ int usb4_port_retimer_nvm_read(struct tb_port *port, u8 index, unsigned int address, void *buf, size_t size) @@ -2108,8 +2193,8 @@ usb4_usb3_port_max_bandwidth(const struct tb_port *port, unsigned int bw) * usb4_usb3_port_max_link_rate() - Maximum support USB3 link rate * @port: USB3 adapter port * - * Return maximum supported link rate of a USB3 adapter in Mb/s. - * Negative errno in case of error. + * Return: Maximum supported link rate of a USB3 adapter in Mb/s. + * Negative errno in case of an error. */ int usb4_usb3_port_max_link_rate(struct tb_port *port) { @@ -2227,8 +2312,9 @@ static int usb4_usb3_port_read_allocated_bandwidth(struct tb_port *port, * @downstream_bw: Allocated downstream bandwidth is stored here * * Stores currently allocated USB3 bandwidth into @upstream_bw and - * @downstream_bw in Mb/s. Returns %0 in case of success and negative - * errno in failure. + * @downstream_bw in Mb/s. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_usb3_port_allocated_bandwidth(struct tb_port *port, int *upstream_bw, int *downstream_bw) @@ -2330,8 +2416,7 @@ static int usb4_usb3_port_write_allocated_bandwidth(struct tb_port *port, * cannot be taken away by CM). The actual new values are returned in * @upstream_bw and @downstream_bw. * - * Returns %0 in case of success and negative errno if there was a - * failure. + * Return: %0 on success, negative errno otherwise. */ int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw, int *downstream_bw) @@ -2373,7 +2458,7 @@ err_request: * Releases USB3 allocated bandwidth down to what is actually consumed. * The new bandwidth is returned in @upstream_bw and @downstream_bw. * - * Returns 0% in success and negative errno in case of failure. + * Return: %0 on success, negative errno otherwise. */ int usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw, int *downstream_bw) @@ -2425,9 +2510,12 @@ static bool is_usb4_dpin(const struct tb_port *port) * @port: DP IN adapter * @cm_id: CM ID to assign * - * Sets CM ID for the @port. Returns %0 on success and negative errno - * otherwise. Speficially returns %-EOPNOTSUPP if the @port does not - * support this. + * Sets CM ID for the @port. + * + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the @port does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id) { @@ -2454,8 +2542,10 @@ int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id) * supported * @port: DP IN adapter to check * - * Can be called to any DP IN adapter. Returns true if the adapter - * supports USB4 bandwidth allocation mode, false otherwise. + * Can be called to any DP IN adapter. + * + * Return: %true if the adapter supports USB4 bandwidth allocation mode, + * %false otherwise. */ bool usb4_dp_port_bandwidth_mode_supported(struct tb_port *port) { @@ -2478,8 +2568,10 @@ bool usb4_dp_port_bandwidth_mode_supported(struct tb_port *port) * enabled * @port: DP IN adapter to check * - * Can be called to any DP IN adapter. Returns true if the bandwidth - * allocation mode has been enabled, false otherwise. + * Can be called to any DP IN adapter. + * + * Return: %true if the bandwidth allocation mode has been enabled, + * %false otherwise. */ bool usb4_dp_port_bandwidth_mode_enabled(struct tb_port *port) { @@ -2504,9 +2596,12 @@ bool usb4_dp_port_bandwidth_mode_enabled(struct tb_port *port) * @supported: Does the CM support bandwidth allocation mode * * Can be called to any DP IN adapter. Sets or clears the CM support bit - * of the DP IN adapter. Returns %0 in success and negative errno - * otherwise. Specifically returns %-OPNOTSUPP if the passed in adapter - * does not support this. + * of the DP IN adapter. + * + * * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the passed IN adapter does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_set_cm_bandwidth_mode_supported(struct tb_port *port, bool supported) @@ -2536,8 +2631,12 @@ int usb4_dp_port_set_cm_bandwidth_mode_supported(struct tb_port *port, * @port: DP IN adapter * * Reads bandwidth allocation Group ID from the DP IN adapter and - * returns it. If the adapter does not support setting Group_ID - * %-EOPNOTSUPP is returned. + * returns it. + * + * Return: + * * Group ID assigned to adapter @port. + * * %-EOPNOTSUPP - If adapter does not support setting GROUP_ID. + * * Negative errno - Another error occurred. */ int usb4_dp_port_group_id(struct tb_port *port) { @@ -2561,9 +2660,11 @@ int usb4_dp_port_group_id(struct tb_port *port) * @group_id: Group ID for the adapter * * Sets bandwidth allocation mode Group ID for the DP IN adapter. - * Returns %0 in case of success and negative errno otherwise. - * Specifically returns %-EOPNOTSUPP if the adapter does not support - * this. + * + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the adapter does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_set_group_id(struct tb_port *port, int group_id) { @@ -2591,9 +2692,12 @@ int usb4_dp_port_set_group_id(struct tb_port *port, int group_id) * @rate: Non-reduced rate in Mb/s is placed here * @lanes: Non-reduced lanes are placed here * - * Reads the non-reduced rate and lanes from the DP IN adapter. Returns - * %0 in success and negative errno otherwise. Specifically returns - * %-EOPNOTSUPP if the adapter does not support this. + * Reads the non-reduced rate and lanes from the DP IN adapter. + * + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the adapter does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes) { @@ -2646,10 +2750,13 @@ int usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes) * @rate: Non-reduced rate in Mb/s * @lanes: Non-reduced lanes * - * Before the capabilities reduction this function can be used to set - * the non-reduced values for the DP IN adapter. Returns %0 in success - * and negative errno otherwise. If the adapter does not support this - * %-EOPNOTSUPP is returned. + * Before the capabilities reduction, this function can be used to set + * the non-reduced values for the DP IN adapter. + * + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the adapter does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes) { @@ -2708,9 +2815,13 @@ int usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes) * usb4_dp_port_granularity() - Return granularity for the bandwidth values * @port: DP IN adapter * - * Reads the programmed granularity from @port. If the DP IN adapter does - * not support bandwidth allocation mode returns %-EOPNOTSUPP and negative - * errno in other error cases. + * Reads the programmed granularity from @port. + * + * Return: + * * Granularity value of a @port. + * * %-EOPNOTSUPP - If the DP IN adapter does not support bandwidth + * allocation mode. + * * Negative errno - Another error occurred. */ int usb4_dp_port_granularity(struct tb_port *port) { @@ -2746,8 +2857,12 @@ int usb4_dp_port_granularity(struct tb_port *port) * @granularity: Granularity in Mb/s. Supported values: 1000, 500 and 250. * * Sets the granularity used with the estimated, allocated and requested - * bandwidth. Returns %0 in success and negative errno otherwise. If the - * adapter does not support this %-EOPNOTSUPP is returned. + * bandwidth. + * + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the adapter does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_set_granularity(struct tb_port *port, int granularity) { @@ -2788,10 +2903,13 @@ int usb4_dp_port_set_granularity(struct tb_port *port, int granularity) * @bw: Estimated bandwidth in Mb/s. * * Sets the estimated bandwidth to @bw. Set the granularity by calling - * usb4_dp_port_set_granularity() before calling this. The @bw is round - * down to the closest granularity multiplier. Returns %0 in success - * and negative errno otherwise. Specifically returns %-EOPNOTSUPP if - * the adapter does not support this. + * usb4_dp_port_set_granularity() before calling this. The @bw is rounded + * down to the closest granularity multiplier. + * + * Return: + * * %0 - On success. + * * %-EOPNOTSUPP - If the adapter does not support this. + * * Negative errno - Another error occurred. */ int usb4_dp_port_set_estimated_bandwidth(struct tb_port *port, int bw) { @@ -2822,9 +2940,10 @@ int usb4_dp_port_set_estimated_bandwidth(struct tb_port *port, int bw) * usb4_dp_port_allocated_bandwidth() - Return allocated bandwidth * @port: DP IN adapter * - * Reads and returns allocated bandwidth for @port in Mb/s (taking into - * account the programmed granularity). Returns negative errno in case - * of error. + * Reads the allocated bandwidth for @port in Mb/s (taking into account + * the programmed granularity). + * + * Return: Allocated bandwidth in Mb/s or negative errno in case of an error. */ int usb4_dp_port_allocated_bandwidth(struct tb_port *port) { @@ -2919,8 +3038,9 @@ static int usb4_dp_port_wait_and_clear_cm_ack(struct tb_port *port, * @bw: New allocated bandwidth in Mb/s * * Communicates the new allocated bandwidth with the DPCD (graphics - * driver). Takes into account the programmed granularity. Returns %0 in - * success and negative errno in case of error. + * driver). Takes into account the programmed granularity. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw) { @@ -2960,10 +3080,15 @@ int usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw) * @port: DP IN adapter * * Reads the DPCD (graphics driver) requested bandwidth and returns it - * in Mb/s. Takes the programmed granularity into account. In case of - * error returns negative errno. Specifically returns %-EOPNOTSUPP if - * the adapter does not support bandwidth allocation mode, and %ENODATA - * if there is no active bandwidth request from the graphics driver. + * in Mb/s. Takes the programmed granularity into account. + * + * Return: + * * Requested bandwidth in Mb/s - On success. + * * %-EOPNOTSUPP - If the adapter does not support bandwidth allocation + * mode. + * * %ENODATA - If there is no active bandwidth request from the graphics + * driver. + * * Negative errno - On failure. */ int usb4_dp_port_requested_bandwidth(struct tb_port *port) { @@ -2995,8 +3120,9 @@ int usb4_dp_port_requested_bandwidth(struct tb_port *port) * @enable: Enable/disable extended encapsulation * * Enables or disables extended encapsulation used in PCIe tunneling. Caller - * needs to make sure both adapters support this before enabling. Returns %0 on - * success and negative errno otherwise. + * needs to make sure both adapters support this before enabling. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable) { diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c index 852a45fcd19d..b5e06237261b 100644 --- a/drivers/thunderbolt/usb4_port.c +++ b/drivers/thunderbolt/usb4_port.c @@ -296,8 +296,9 @@ const struct device_type usb4_port_device_type = { * usb4_port_device_add() - Add USB4 port device * @port: Lane 0 adapter port to add the USB4 port * - * Creates and registers a USB4 port device for @port. Returns the new - * USB4 port device pointer or ERR_PTR() in case of error. + * Creates and registers a USB4 port device for @port. + * + * Return: Pointer to &struct usb4_port or ERR_PTR() in case of an error. */ struct usb4_port *usb4_port_device_add(struct tb_port *port) { @@ -356,6 +357,8 @@ void usb4_port_device_remove(struct usb4_port *usb4) * @usb4: USB4 port device * * Used to resume USB4 port device after sleep state. + * + * Return: %0 on success, negative errno otherwise. */ int usb4_port_device_resume(struct usb4_port *usb4) { diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index b0630e6d9472..9d220ba544ec 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -160,7 +160,7 @@ static int __tb_xdomain_response(struct tb_ctl *ctl, const void *response, * This can be used to send a XDomain response message to the other * domain. No response for the message is expected. * - * Return: %0 in case of success and negative errno in case of failure + * Return: %0 on success, negative errno otherwise. */ int tb_xdomain_response(struct tb_xdomain *xd, const void *response, size_t size, enum tb_cfg_pkg_type type) @@ -212,7 +212,7 @@ static int __tb_xdomain_request(struct tb_ctl *ctl, const void *request, * the other domain. The function waits until the response is received * or when timeout triggers. Whichever comes first. * - * Return: %0 in case of success and negative errno in case of failure + * Return: %0 on success, negative errno otherwise. */ int tb_xdomain_request(struct tb_xdomain *xd, const void *request, size_t request_size, enum tb_cfg_pkg_type request_type, @@ -613,6 +613,8 @@ static int tb_xdp_link_state_change_response(struct tb_ctl *ctl, u64 route, * messages. After this function is called the service driver needs to * be able to handle calls to callback whenever a package with the * registered protocol is received. + * + * Return: %0 on success, negative errno otherwise. */ int tb_register_protocol_handler(struct tb_protocol_handler *handler) { @@ -877,6 +879,8 @@ tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr, * @drv: Driver to register * * Registers new service driver from @drv to the bus. + * + * Return: %0 on success, negative errno otherwise. */ int tb_register_service_driver(struct tb_service_driver *drv) { @@ -1955,6 +1959,8 @@ static void tb_xdomain_link_exit(struct tb_xdomain *xd) * * Allocates new XDomain structure and returns pointer to that. The * object must be released by calling tb_xdomain_put(). + * + * Return: Pointer to &struct tb_xdomain, %NULL in case of failure. */ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent, u64 route, const uuid_t *local_uuid, @@ -2091,7 +2097,7 @@ void tb_xdomain_remove(struct tb_xdomain *xd) * to enable bonding by first enabling the port and waiting for the CL0 * state. * - * Return: %0 in case of success and negative errno in case of error. + * Return: %0 on success, negative errno otherwise. */ int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd) { @@ -2171,10 +2177,14 @@ EXPORT_SYMBOL_GPL(tb_xdomain_lane_bonding_disable); * @xd: XDomain connection * @hopid: Preferred HopID or %-1 for next available * - * Returns allocated HopID or negative errno. Specifically returns - * %-ENOSPC if there are no more available HopIDs. Returned HopID is - * guaranteed to be within range supported by the input lane adapter. + * Returned HopID is guaranteed to be within range supported by the input + * lane adapter. * Call tb_xdomain_release_in_hopid() to release the allocated HopID. + * + * Return: + * * Allocated HopID - On success. + * * %-ENOSPC - If there are no more available HopIDs. + * * Negative errno - Another error occurred. */ int tb_xdomain_alloc_in_hopid(struct tb_xdomain *xd, int hopid) { @@ -2193,10 +2203,14 @@ EXPORT_SYMBOL_GPL(tb_xdomain_alloc_in_hopid); * @xd: XDomain connection * @hopid: Preferred HopID or %-1 for next available * - * Returns allocated HopID or negative errno. Specifically returns - * %-ENOSPC if there are no more available HopIDs. Returned HopID is - * guaranteed to be within range supported by the output lane adapter. - * Call tb_xdomain_release_in_hopid() to release the allocated HopID. + * Returned HopID is guaranteed to be within range supported by the + * output lane adapter. + * Call tb_xdomain_release_out_hopid() to release the allocated HopID. + * + * Return: + * * Allocated HopID - On success. + * * %-ENOSPC - If there are no more available HopIDs. + * * Negative errno - Another error occurred. */ int tb_xdomain_alloc_out_hopid(struct tb_xdomain *xd, int hopid) { @@ -2245,7 +2259,7 @@ EXPORT_SYMBOL_GPL(tb_xdomain_release_out_hopid); * path. If a transmit or receive path is not needed, pass %-1 for those * parameters. * - * Return: %0 in case of success and negative errno in case of error + * Return: %0 on success, negative errno otherwise. */ int tb_xdomain_enable_paths(struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, @@ -2270,7 +2284,7 @@ EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths); * as path/ring parameter means don't care. Normally the callers should * pass the same values here as they do when paths are enabled. * - * Return: %0 in case of success and negative errno in case of error + * Return: %0 on success, negative errno otherwise. */ int tb_xdomain_disable_paths(struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, @@ -2335,6 +2349,8 @@ static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw, * to the bus (handshake is still in progress). * * The caller needs to hold @tb->lock. + * + * Return: Pointer to &struct tb_xdomain or %NULL if not found. */ struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const uuid_t *uuid) { @@ -2364,6 +2380,8 @@ EXPORT_SYMBOL_GPL(tb_xdomain_find_by_uuid); * to the bus (handshake is still in progress). * * The caller needs to hold @tb->lock. + * + * Return: Pointer to &struct tb_xdomain or %NULL if not found. */ struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link, u8 depth) @@ -2393,6 +2411,8 @@ struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link, * to the bus (handshake is still in progress). * * The caller needs to hold @tb->lock. + * + * Return: Pointer to &struct tb_xdomain or %NULL if not found. */ struct tb_xdomain *tb_xdomain_find_by_route(struct tb *tb, u64 route) { @@ -2491,7 +2511,7 @@ static bool remove_directory(const char *key, const struct tb_property_dir *dir) * notified so they can re-read properties of this host if they are * interested. * - * Return: %0 on success and negative errno on failure + * Return: %0 on success, negative errno otherwise. */ int tb_register_property_dir(const char *key, struct tb_property_dir *dir) { @@ -2562,10 +2582,9 @@ int tb_xdomain_init(void) * Rest of the properties are filled dynamically based on these * when the P2P connection is made. */ - tb_property_add_immediate(xdomain_property_dir, "vendorid", - PCI_VENDOR_ID_INTEL); - tb_property_add_text(xdomain_property_dir, "vendorid", "Intel Corp."); - tb_property_add_immediate(xdomain_property_dir, "deviceid", 0x1); + tb_property_add_immediate(xdomain_property_dir, "vendorid", 0x1d6b); + tb_property_add_text(xdomain_property_dir, "vendorid", "Linux"); + tb_property_add_immediate(xdomain_property_dir, "deviceid", 0x0004); tb_property_add_immediate(xdomain_property_dir, "devicerv", 0x80000100); xdomain_property_block_gen = get_random_u32(); diff --git a/drivers/tty/hvc/hvc_console.c b/drivers/tty/hvc/hvc_console.c index 13c663a154c4..6b58f340f210 100644 --- a/drivers/tty/hvc/hvc_console.c +++ b/drivers/tty/hvc/hvc_console.c @@ -184,7 +184,7 @@ static void hvc_console_print(struct console *co, const char *b, hvc_console_flush(cons_ops[index], vtermnos[index]); } - } else if (r > 0) { + } else { i -= r; if (i > 0) memmove(c, c+r, i); diff --git a/drivers/tty/mxser.c b/drivers/tty/mxser.c index 2fc13cc02cc5..94677fec685e 100644 --- a/drivers/tty/mxser.c +++ b/drivers/tty/mxser.c @@ -442,11 +442,8 @@ static void __mxser_start_tx(struct mxser_port *info) static void mxser_start_tx(struct mxser_port *info) { - unsigned long flags; - - spin_lock_irqsave(&info->slock, flags); + guard(spinlock_irqsave)(&info->slock); __mxser_start_tx(info); - spin_unlock_irqrestore(&info->slock, flags); } static void __mxser_stop_tx(struct mxser_port *info) @@ -465,17 +462,15 @@ static bool mxser_carrier_raised(struct tty_port *port) static void mxser_dtr_rts(struct tty_port *port, bool active) { struct mxser_port *mp = container_of(port, struct mxser_port, port); - unsigned long flags; u8 mcr; - spin_lock_irqsave(&mp->slock, flags); + guard(spinlock_irqsave)(&mp->slock); mcr = inb(mp->ioaddr + UART_MCR); if (active) mcr |= UART_MCR_DTR | UART_MCR_RTS; else mcr &= ~(UART_MCR_DTR | UART_MCR_RTS); outb(mcr, mp->ioaddr + UART_MCR); - spin_unlock_irqrestore(&mp->slock, flags); } static int mxser_set_baud(struct tty_struct *tty, speed_t newspd) @@ -828,32 +823,28 @@ static void mxser_stop_rx(struct mxser_port *info) static void mxser_shutdown_port(struct tty_port *port) { struct mxser_port *info = container_of(port, struct mxser_port, port); - unsigned long flags; - spin_lock_irqsave(&info->slock, flags); + scoped_guard(spinlock_irqsave, &info->slock) { + mxser_stop_rx(info); - mxser_stop_rx(info); - - /* - * clear delta_msr_wait queue to avoid mem leaks: we may free the irq - * here so the queue might never be waken up - */ - wake_up_interruptible(&info->port.delta_msr_wait); - - info->IER = 0; - outb(0x00, info->ioaddr + UART_IER); - - /* clear Rx/Tx FIFO's */ - mxser_disable_and_clear_FIFO(info); + /* + * clear delta_msr_wait queue to avoid mem leaks: we may free the irq + * here so the queue might never be waken up + */ + wake_up_interruptible(&info->port.delta_msr_wait); - /* read data port to reset things */ - (void) inb(info->ioaddr + UART_RX); + info->IER = 0; + outb(0x00, info->ioaddr + UART_IER); + /* clear Rx/Tx FIFO's */ + mxser_disable_and_clear_FIFO(info); - if (info->board->must_hwid) - mxser_must_no_sw_flow_control(info->ioaddr); + /* read data port to reset things */ + (void)inb(info->ioaddr + UART_RX); - spin_unlock_irqrestore(&info->slock, flags); + if (info->board->must_hwid) + mxser_must_no_sw_flow_control(info->ioaddr); + } /* make sure ISR is not running while we free the buffer */ synchronize_irq(info->board->irq); @@ -880,15 +871,13 @@ static int mxser_open(struct tty_struct *tty, struct file *filp) static void mxser_flush_buffer(struct tty_struct *tty) { struct mxser_port *info = tty->driver_data; - unsigned long flags; - - spin_lock_irqsave(&info->slock, flags); - kfifo_reset(&info->port.xmit_fifo); - outb(info->FCR | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT, - info->ioaddr + UART_FCR); + scoped_guard(spinlock_irqsave, &info->slock) { + kfifo_reset(&info->port.xmit_fifo); - spin_unlock_irqrestore(&info->slock, flags); + outb(info->FCR | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT, + info->ioaddr + UART_FCR); + } tty_wakeup(tty); } @@ -901,14 +890,13 @@ static void mxser_close(struct tty_struct *tty, struct file *filp) static ssize_t mxser_write(struct tty_struct *tty, const u8 *buf, size_t count) { struct mxser_port *info = tty->driver_data; - unsigned long flags; size_t written; bool is_empty; - spin_lock_irqsave(&info->slock, flags); - written = kfifo_in(&info->port.xmit_fifo, buf, count); - is_empty = kfifo_is_empty(&info->port.xmit_fifo); - spin_unlock_irqrestore(&info->slock, flags); + scoped_guard(spinlock_irqsave, &info->slock) { + written = kfifo_in(&info->port.xmit_fifo, buf, count); + is_empty = kfifo_is_empty(&info->port.xmit_fifo); + } if (!is_empty && !tty->flow.stopped) if (!tty->hw_stopped || mxser_16550A_or_MUST(info)) @@ -920,14 +908,9 @@ static ssize_t mxser_write(struct tty_struct *tty, const u8 *buf, size_t count) static int mxser_put_char(struct tty_struct *tty, u8 ch) { struct mxser_port *info = tty->driver_data; - unsigned long flags; - int ret; - - spin_lock_irqsave(&info->slock, flags); - ret = kfifo_put(&info->port.xmit_fifo, ch); - spin_unlock_irqrestore(&info->slock, flags); - return ret; + guard(spinlock_irqsave)(&info->slock); + return kfifo_put(&info->port.xmit_fifo, ch); } @@ -968,7 +951,7 @@ static int mxser_get_serial_info(struct tty_struct *tty, struct tty_port *port = &info->port; unsigned int closing_wait, close_delay; - mutex_lock(&port->mutex); + guard(mutex)(&port->mutex); close_delay = jiffies_to_msecs(info->port.close_delay) / 10; closing_wait = info->port.closing_wait; @@ -984,7 +967,7 @@ static int mxser_get_serial_info(struct tty_struct *tty, ss->close_delay = close_delay; ss->closing_wait = closing_wait; ss->custom_divisor = MXSER_CUSTOM_DIVISOR; - mutex_unlock(&port->mutex); + return 0; } @@ -994,20 +977,15 @@ static int mxser_set_serial_info(struct tty_struct *tty, struct mxser_port *info = tty->driver_data; struct tty_port *port = &info->port; speed_t baud; - unsigned long sl_flags; unsigned int old_speed, close_delay, closing_wait; - int retval = 0; if (tty_io_error(tty)) return -EIO; - mutex_lock(&port->mutex); + guard(mutex)(&port->mutex); - if (ss->irq != info->board->irq || - ss->port != info->ioaddr) { - mutex_unlock(&port->mutex); + if (ss->irq != info->board->irq || ss->port != info->ioaddr) return -EINVAL; - } old_speed = port->flags & ASYNC_SPD_MASK; @@ -1020,10 +998,9 @@ static int mxser_set_serial_info(struct tty_struct *tty, if ((ss->baud_base != MXSER_BAUD_BASE) || (close_delay != port->close_delay) || (closing_wait != port->closing_wait) || - ((ss->flags & ~ASYNC_USR_MASK) != (port->flags & ~ASYNC_USR_MASK))) { - mutex_unlock(&port->mutex); + ((ss->flags & ~ASYNC_USR_MASK) != (port->flags & ~ASYNC_USR_MASK))) return -EPERM; - } + port->flags = (port->flags & ~ASYNC_USR_MASK) | (ss->flags & ASYNC_USR_MASK); } else { @@ -1039,10 +1016,9 @@ static int mxser_set_serial_info(struct tty_struct *tty, (ss->baud_base != MXSER_BAUD_BASE || ss->custom_divisor != MXSER_CUSTOM_DIVISOR)) { - if (ss->custom_divisor == 0) { - mutex_unlock(&port->mutex); + if (ss->custom_divisor == 0) return -EINVAL; - } + baud = ss->baud_base / ss->custom_divisor; tty_encode_baud_rate(tty, baud, baud); } @@ -1054,16 +1030,17 @@ static int mxser_set_serial_info(struct tty_struct *tty, if (tty_port_initialized(port)) { if (old_speed != (port->flags & ASYNC_SPD_MASK)) { - spin_lock_irqsave(&info->slock, sl_flags); + guard(spinlock_irqsave)(&info->slock); mxser_change_speed(tty, NULL); - spin_unlock_irqrestore(&info->slock, sl_flags); } - } else { - retval = mxser_activate(port, tty); - if (retval == 0) - tty_port_set_initialized(port, true); + + return 0; } - mutex_unlock(&port->mutex); + + int retval = mxser_activate(port, tty); + if (retval == 0) + tty_port_set_initialized(port, true); + return retval; } @@ -1080,13 +1057,11 @@ static int mxser_set_serial_info(struct tty_struct *tty, static int mxser_get_lsr_info(struct mxser_port *info, unsigned int __user *value) { - unsigned char status; unsigned int result; - unsigned long flags; + u8 status; - spin_lock_irqsave(&info->slock, flags); - status = inb(info->ioaddr + UART_LSR); - spin_unlock_irqrestore(&info->slock, flags); + scoped_guard(spinlock_irqsave, &info->slock) + status = inb(info->ioaddr + UART_LSR); result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0); return put_user(result, value); } @@ -1095,16 +1070,15 @@ static int mxser_tiocmget(struct tty_struct *tty) { struct mxser_port *info = tty->driver_data; unsigned char control; - unsigned long flags; u8 msr; if (tty_io_error(tty)) return -EIO; - spin_lock_irqsave(&info->slock, flags); - control = info->MCR; - msr = mxser_check_modem_status(tty, info); - spin_unlock_irqrestore(&info->slock, flags); + scoped_guard(spinlock_irqsave, &info->slock) { + control = info->MCR; + msr = mxser_check_modem_status(tty, info); + } return ((control & UART_MCR_RTS) ? TIOCM_RTS : 0) | ((control & UART_MCR_DTR) ? TIOCM_DTR : 0) | @@ -1118,12 +1092,11 @@ static int mxser_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) { struct mxser_port *info = tty->driver_data; - unsigned long flags; if (tty_io_error(tty)) return -EIO; - spin_lock_irqsave(&info->slock, flags); + guard(spinlock_irqsave)(&info->slock); if (set & TIOCM_RTS) info->MCR |= UART_MCR_RTS; @@ -1136,7 +1109,7 @@ static int mxser_tiocmset(struct tty_struct *tty, info->MCR &= ~UART_MCR_DTR; outb(info->MCR, info->ioaddr + UART_MCR); - spin_unlock_irqrestore(&info->slock, flags); + return 0; } @@ -1144,12 +1117,11 @@ static int mxser_cflags_changed(struct mxser_port *info, unsigned long arg, struct async_icount *cprev) { struct async_icount cnow; - unsigned long flags; int ret; - spin_lock_irqsave(&info->slock, flags); - cnow = info->icount; /* atomic copy */ - spin_unlock_irqrestore(&info->slock, flags); + /* atomic copy */ + scoped_guard(spinlock_irqsave, &info->slock) + cnow = info->icount; ret = ((arg & TIOCM_RNG) && (cnow.rng != cprev->rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev->dsr)) || @@ -1179,19 +1151,17 @@ static int mxser_ioctl_op_mode(struct mxser_port *port, int index, bool set, if (opmode & ~OP_MODE_MASK) return -EINVAL; - spin_lock_irq(&port->slock); + guard(spinlock_irq)(&port->slock); val = inb(port->opmode_ioaddr); val &= ~(OP_MODE_MASK << shiftbit); val |= (opmode << shiftbit); outb(val, port->opmode_ioaddr); - spin_unlock_irq(&port->slock); return 0; } - spin_lock_irq(&port->slock); - opmode = inb(port->opmode_ioaddr) >> shiftbit; - spin_unlock_irq(&port->slock); + scoped_guard(spinlock_irq, &port->slock) + opmode = inb(port->opmode_ioaddr) >> shiftbit; return put_user(opmode & OP_MODE_MASK, u_opmode); } @@ -1201,7 +1171,6 @@ static int mxser_ioctl(struct tty_struct *tty, { struct mxser_port *info = tty->driver_data; struct async_icount cnow; - unsigned long flags; void __user *argp = (void __user *)arg; if (cmd == MOXA_SET_OP_MODE || cmd == MOXA_GET_OP_MODE) @@ -1221,9 +1190,9 @@ static int mxser_ioctl(struct tty_struct *tty, * Caller should use TIOCGICOUNT to see which one it was */ case TIOCMIWAIT: - spin_lock_irqsave(&info->slock, flags); - cnow = info->icount; /* note the counters on entry */ - spin_unlock_irqrestore(&info->slock, flags); + /* note the counters on entry */ + scoped_guard(spinlock_irqsave, &info->slock) + cnow = info->icount; return wait_event_interruptible(info->port.delta_msr_wait, mxser_cflags_changed(info, arg, &cnow)); @@ -1246,11 +1215,9 @@ static int mxser_get_icount(struct tty_struct *tty, { struct mxser_port *info = tty->driver_data; struct async_icount cnow; - unsigned long flags; - spin_lock_irqsave(&info->slock, flags); - cnow = info->icount; - spin_unlock_irqrestore(&info->slock, flags); + scoped_guard(spinlock_irqsave, &info->slock) + cnow = info->icount; icount->frame = cnow.frame; icount->brk = cnow.brk; @@ -1328,34 +1295,28 @@ static void mxser_unthrottle(struct tty_struct *tty) static void mxser_stop(struct tty_struct *tty) { struct mxser_port *info = tty->driver_data; - unsigned long flags; - spin_lock_irqsave(&info->slock, flags); + guard(spinlock_irqsave)(&info->slock); if (info->IER & UART_IER_THRI) __mxser_stop_tx(info); - spin_unlock_irqrestore(&info->slock, flags); } static void mxser_start(struct tty_struct *tty) { struct mxser_port *info = tty->driver_data; - unsigned long flags; - spin_lock_irqsave(&info->slock, flags); + guard(spinlock_irqsave)(&info->slock); if (!kfifo_is_empty(&info->port.xmit_fifo)) __mxser_start_tx(info); - spin_unlock_irqrestore(&info->slock, flags); } static void mxser_set_termios(struct tty_struct *tty, const struct ktermios *old_termios) { struct mxser_port *info = tty->driver_data; - unsigned long flags; - spin_lock_irqsave(&info->slock, flags); - mxser_change_speed(tty, old_termios); - spin_unlock_irqrestore(&info->slock, flags); + scoped_guard(spinlock_irqsave, &info->slock) + mxser_change_speed(tty, old_termios); if ((old_termios->c_cflag & CRTSCTS) && !C_CRTSCTS(tty)) { tty->hw_stopped = false; @@ -1367,9 +1328,8 @@ static void mxser_set_termios(struct tty_struct *tty, tty->flow.stopped = 0; if (info->board->must_hwid) { - spin_lock_irqsave(&info->slock, flags); + guard(spinlock_irqsave)(&info->slock); mxser_must_set_rx_sw_flow_control(info->ioaddr, false); - spin_unlock_irqrestore(&info->slock, flags); } mxser_start(tty); @@ -1378,14 +1338,8 @@ static void mxser_set_termios(struct tty_struct *tty, static bool mxser_tx_empty(struct mxser_port *info) { - unsigned long flags; - u8 lsr; - - spin_lock_irqsave(&info->slock, flags); - lsr = inb(info->ioaddr + UART_LSR); - spin_unlock_irqrestore(&info->slock, flags); - - return !(lsr & UART_LSR_TEMT); + guard(spinlock_irqsave)(&info->slock); + return !(inb(info->ioaddr + UART_LSR) & UART_LSR_TEMT); } /* @@ -1459,17 +1413,15 @@ static void mxser_hangup(struct tty_struct *tty) static int mxser_rs_break(struct tty_struct *tty, int break_state) { struct mxser_port *info = tty->driver_data; - unsigned long flags; u8 lcr; - spin_lock_irqsave(&info->slock, flags); + guard(spinlock_irqsave)(&info->slock); lcr = inb(info->ioaddr + UART_LCR); if (break_state == -1) lcr |= UART_LCR_SBC; else lcr &= ~UART_LCR_SBC; outb(lcr, info->ioaddr + UART_LCR); - spin_unlock_irqrestore(&info->slock, flags); return 0; } @@ -1600,54 +1552,50 @@ static void mxser_transmit_chars(struct tty_struct *tty, struct mxser_port *port static bool mxser_port_isr(struct mxser_port *port) { - struct tty_struct *tty; u8 iir, status; - bool error = false; iir = inb(port->ioaddr + UART_IIR); if (iir & UART_IIR_NO_INT) return true; iir &= MOXA_MUST_IIR_MASK; - tty = tty_port_tty_get(&port->port); - if (!tty) { - status = inb(port->ioaddr + UART_LSR); - outb(port->FCR | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT, - port->ioaddr + UART_FCR); - inb(port->ioaddr + UART_MSR); - error = true; - goto put_tty; - } + scoped_guard(tty_port_tty, &port->port) { + struct tty_struct *tty = scoped_tty(); - status = inb(port->ioaddr + UART_LSR); + status = inb(port->ioaddr + UART_LSR); - if (port->board->must_hwid) { - if (iir == MOXA_MUST_IIR_GDA || - iir == MOXA_MUST_IIR_RDA || - iir == MOXA_MUST_IIR_RTO || - iir == MOXA_MUST_IIR_LSR) - status = mxser_receive_chars(tty, port, status); - } else { - status &= port->read_status_mask; - if (status & UART_LSR_DR) - status = mxser_receive_chars(tty, port, status); - } + if (port->board->must_hwid) { + if (iir == MOXA_MUST_IIR_GDA || + iir == MOXA_MUST_IIR_RDA || + iir == MOXA_MUST_IIR_RTO || + iir == MOXA_MUST_IIR_LSR) + status = mxser_receive_chars(tty, port, status); + } else { + status &= port->read_status_mask; + if (status & UART_LSR_DR) + status = mxser_receive_chars(tty, port, status); + } - mxser_check_modem_status(tty, port); + mxser_check_modem_status(tty, port); - if (port->board->must_hwid) { - if (iir == 0x02 && (status & UART_LSR_THRE)) - mxser_transmit_chars(tty, port); - } else { - if (status & UART_LSR_THRE) - mxser_transmit_chars(tty, port); + if (port->board->must_hwid) { + if (iir == 0x02 && (status & UART_LSR_THRE)) + mxser_transmit_chars(tty, port); + } else { + if (status & UART_LSR_THRE) + mxser_transmit_chars(tty, port); + } + + return false; } -put_tty: - tty_kref_put(tty); + status = inb(port->ioaddr + UART_LSR); + outb(port->FCR | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT, + port->ioaddr + UART_FCR); + inb(port->ioaddr + UART_MSR); - return error; + return true; } /* @@ -1676,12 +1624,11 @@ static irqreturn_t mxser_interrupt(int irq, void *dev_id) port = &brd->ports[i]; int_cnt = 0; - spin_lock(&port->slock); + guard(spinlock)(&port->slock); do { if (mxser_port_isr(port)) break; } while (int_cnt++ < MXSER_ISR_PASS_LIMIT); - spin_unlock(&port->slock); } } diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index 7fc535452c0b..553d8c70352b 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -461,6 +461,7 @@ static int gsm_send_packet(struct gsm_mux *gsm, struct gsm_msg *msg); static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr); static void gsmld_write_trigger(struct gsm_mux *gsm); static void gsmld_write_task(struct work_struct *work); +static int gsm_modem_send_initial_msc(struct gsm_dlci *dlci); /** * gsm_fcs_add - update FCS @@ -2174,7 +2175,7 @@ static void gsm_dlci_open(struct gsm_dlci *dlci) pr_debug("DLCI %d goes open.\n", dlci->addr); /* Send current modem state */ if (dlci->addr) { - gsm_modem_update(dlci, 0); + gsm_modem_send_initial_msc(dlci); } else { /* Start keep-alive control */ gsm->ka_num = 0; @@ -4162,6 +4163,28 @@ static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk) } /** + * gsm_modem_send_initial_msc - Send initial modem status message + * + * @dlci channel + * + * Send an initial MSC message after DLCI open to set the initial + * modem status lines. This is only done for basic mode. + * Does not wait for a response as we cannot block the input queue + * processing. + */ +static int gsm_modem_send_initial_msc(struct gsm_dlci *dlci) +{ + u8 modembits[2]; + + if (dlci->adaption != 1 || dlci->gsm->encoding != GSM_BASIC_OPT) + return 0; + + modembits[0] = (dlci->addr << 2) | 2 | EA; /* DLCI, Valid, EA */ + modembits[1] = (gsm_encode_modem(dlci) << 1) | EA; + return gsm_control_command(dlci->gsm, CMD_MSC, (const u8 *)&modembits, 2); +} + +/** * gsm_modem_update - send modem status line state * @dlci: channel * @brk: break signal diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c index d16c207a1a9b..b33e708cb245 100644 --- a/drivers/tty/serdev/core.c +++ b/drivers/tty/serdev/core.c @@ -399,15 +399,12 @@ static int serdev_drv_probe(struct device *dev) const struct serdev_device_driver *sdrv = to_serdev_device_driver(dev->driver); int ret; - ret = dev_pm_domain_attach(dev, PD_FLAG_ATTACH_POWER_ON); + ret = dev_pm_domain_attach(dev, PD_FLAG_ATTACH_POWER_ON | + PD_FLAG_DETACH_POWER_OFF); if (ret) return ret; - ret = sdrv->probe(to_serdev_device(dev)); - if (ret) - dev_pm_domain_detach(dev, true); - - return ret; + return sdrv->probe(to_serdev_device(dev)); } static void serdev_drv_remove(struct device *dev) @@ -415,8 +412,6 @@ static void serdev_drv_remove(struct device *dev) const struct serdev_device_driver *sdrv = to_serdev_device_driver(dev->driver); if (sdrv->remove) sdrv->remove(to_serdev_device(dev)); - - dev_pm_domain_detach(dev, true); } static const struct bus_type serdev_bus_type = { diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index cfe6ba286b45..58e64c4e1e3a 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -186,6 +186,11 @@ static unsigned int __maybe_unused serial_icr_read(struct uart_8250_port *up, void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p); +void serial8250_rpm_get(struct uart_8250_port *p); +void serial8250_rpm_put(struct uart_8250_port *p); +DEFINE_GUARD(serial8250_rpm, struct uart_8250_port *, + serial8250_rpm_get(_T), serial8250_rpm_put(_T)); + static inline u32 serial_dl_read(struct uart_8250_port *up) { return up->dl_read(up); diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index feb920c5b2e8..bfa421ab3253 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -72,7 +72,7 @@ static irqreturn_t serial8250_interrupt(int irq, void *dev_id) struct list_head *l, *end = NULL; int pass_counter = 0, handled = 0; - spin_lock(&i->lock); + guard(spinlock)(&i->lock); l = i->head; do { @@ -91,8 +91,6 @@ static irqreturn_t serial8250_interrupt(int irq, void *dev_id) break; } while (l != end); - spin_unlock(&i->lock); - return IRQ_RETVAL(handled); } @@ -132,22 +130,19 @@ static struct irq_info *serial_get_or_create_irq_info(const struct uart_8250_por { struct irq_info *i; - mutex_lock(&hash_mutex); + guard(mutex)(&hash_mutex); hash_for_each_possible(irq_lists, i, node, up->port.irq) if (i->irq == up->port.irq) - goto unlock; + return i; i = kzalloc(sizeof(*i), GFP_KERNEL); - if (i == NULL) { - i = ERR_PTR(-ENOMEM); - goto unlock; - } + if (i == NULL) + return ERR_PTR(-ENOMEM); + spin_lock_init(&i->lock); i->irq = up->port.irq; hash_add(irq_lists, &i->node, i->irq); -unlock: - mutex_unlock(&hash_mutex); return i; } @@ -161,23 +156,21 @@ static int serial_link_irq_chain(struct uart_8250_port *up) if (IS_ERR(i)) return PTR_ERR(i); - spin_lock_irq(&i->lock); + scoped_guard(spinlock_irq, &i->lock) { + if (i->head) { + list_add(&up->list, i->head); - if (i->head) { - list_add(&up->list, i->head); - spin_unlock_irq(&i->lock); + return 0; + } - ret = 0; - } else { INIT_LIST_HEAD(&up->list); i->head = &up->list; - spin_unlock_irq(&i->lock); - ret = request_irq(up->port.irq, serial8250_interrupt, - up->port.irqflags, up->port.name, i); - if (ret < 0) - serial_do_unlink(i, up); } + ret = request_irq(up->port.irq, serial8250_interrupt, up->port.irqflags, up->port.name, i); + if (ret < 0) + serial_do_unlink(i, up); + return ret; } @@ -185,20 +178,22 @@ static void serial_unlink_irq_chain(struct uart_8250_port *up) { struct irq_info *i; - mutex_lock(&hash_mutex); + guard(mutex)(&hash_mutex); hash_for_each_possible(irq_lists, i, node, up->port.irq) - if (i->irq == up->port.irq) - break; + if (i->irq == up->port.irq) { + if (WARN_ON(i->head == NULL)) + return; - BUG_ON(i == NULL); - BUG_ON(i->head == NULL); + if (list_empty(i->head)) + free_irq(up->port.irq, i); - if (list_empty(i->head)) - free_irq(up->port.irq, i); + serial_do_unlink(i, up); + + return; + } - serial_do_unlink(i, up); - mutex_unlock(&hash_mutex); + WARN_ON(1); } /* @@ -307,7 +302,7 @@ static void univ8250_release_irq(struct uart_8250_port *up) serial_unlink_irq_chain(up); } -const struct uart_ops *univ8250_port_base_ops = NULL; +const struct uart_ops *univ8250_port_base_ops; struct uart_ops univ8250_port_ops; static const struct uart_8250_ops univ8250_driver_ops = { @@ -670,16 +665,12 @@ static struct uart_8250_port *serial8250_find_match_or_unused(const struct uart_ static void serial_8250_overrun_backoff_work(struct work_struct *work) { - struct uart_8250_port *up = - container_of(to_delayed_work(work), struct uart_8250_port, - overrun_backoff); - struct uart_port *port = &up->port; - unsigned long flags; + struct uart_8250_port *up = container_of(to_delayed_work(work), struct uart_8250_port, + overrun_backoff); - uart_port_lock_irqsave(port, &flags); + guard(uart_port_lock_irqsave)(&up->port); up->ier |= UART_IER_RLSI | UART_IER_RDI; serial_out(up, UART_IER, up->ier); - uart_port_unlock_irqrestore(port, flags); } /** @@ -698,12 +689,12 @@ static void serial_8250_overrun_backoff_work(struct work_struct *work) int serial8250_register_8250_port(const struct uart_8250_port *up) { struct uart_8250_port *uart; - int ret = -ENOSPC; + int ret; if (up->port.uartclk == 0) return -EINVAL; - mutex_lock(&serial_mutex); + guard(mutex)(&serial_mutex); uart = serial8250_find_match_or_unused(&up->port); if (!uart) { @@ -713,15 +704,13 @@ int serial8250_register_8250_port(const struct uart_8250_port *up) */ uart = serial8250_setup_port(nr_uarts); if (!uart) - goto unlock; + return -ENOSPC; nr_uarts++; } /* Check if it is CIR already. We check this below again, see there why. */ - if (uart->port.type == PORT_8250_CIR) { - ret = -ENODEV; - goto unlock; - } + if (uart->port.type == PORT_8250_CIR) + return -ENODEV; if (uart->port.dev) uart_remove_one_port(&serial8250_reg, &uart->port); @@ -855,14 +844,10 @@ int serial8250_register_8250_port(const struct uart_8250_port *up) uart->overrun_backoff_time_ms = 0; } -unlock: - mutex_unlock(&serial_mutex); - return ret; err: uart->port.dev = NULL; - mutex_unlock(&serial_mutex); return ret; } EXPORT_SYMBOL(serial8250_register_8250_port); @@ -878,14 +863,11 @@ void serial8250_unregister_port(int line) { struct uart_8250_port *uart = &serial8250_ports[line]; - mutex_lock(&serial_mutex); + guard(mutex)(&serial_mutex); if (uart->em485) { - unsigned long flags; - - uart_port_lock_irqsave(&uart->port, &flags); + guard(uart_port_lock_irqsave)(&uart->port); serial8250_em485_destroy(uart); - uart_port_unlock_irqrestore(&uart->port, flags); } uart_remove_one_port(&serial8250_reg, &uart->port); @@ -901,7 +883,6 @@ void serial8250_unregister_port(int line) } else { uart->port.dev = NULL; } - mutex_unlock(&serial_mutex); } EXPORT_SYMBOL(serial8250_unregister_port); diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 6707f55bdbe7..9e49ef48b851 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -27,6 +27,8 @@ #include <linux/pm_wakeirq.h> #include <linux/dma-mapping.h> #include <linux/sys_soc.h> +#include <linux/reboot.h> +#include <linux/pinctrl/consumer.h> #include "8250.h" @@ -145,6 +147,9 @@ struct omap8250_priv { spinlock_t rx_dma_lock; bool rx_dma_broken; bool throttled; + + struct pinctrl *pinctrl; + struct pinctrl_state *pinctrl_wakeup; }; struct omap8250_dma_params { @@ -369,18 +374,12 @@ static void omap8250_restore_regs(struct uart_8250_port *up) serial8250_em485_stop_tx(up, true); } -/* - * OMAP can use "CLK / (16 or 13) / div" for baud rate. And then we have have - * some differences in how we want to handle flow control. - */ -static void omap_8250_set_termios(struct uart_port *port, - struct ktermios *termios, - const struct ktermios *old) +static void omap_8250_set_termios_atomic(struct uart_port *port, struct ktermios *termios, + const struct ktermios *old, unsigned int baud) { struct uart_8250_port *up = up_to_u8250p(port); struct omap8250_priv *priv = port->private_data; - unsigned char cval = 0; - unsigned int baud; + u8 cval; cval = UART_LCR_WLEN(tty_get_char_size(termios->c_cflag)); @@ -393,20 +392,14 @@ static void omap_8250_set_termios(struct uart_port *port, if (termios->c_cflag & CMSPAR) cval |= UART_LCR_SPAR; - /* - * Ask the core to calculate the divisor for us. - */ - baud = uart_get_baud_rate(port, termios, old, - port->uartclk / 16 / UART_DIV_MAX, - port->uartclk / 13); omap_8250_get_divisor(port, baud, priv); /* * Ok, we're now changing the port state. Do it with * interrupts disabled. */ - pm_runtime_get_sync(port->dev); - uart_port_lock_irq(port); + guard(serial8250_rpm)(up); + guard(uart_port_lock_irq)(port); /* * Update the per-port timeout. @@ -514,10 +507,27 @@ static void omap_8250_set_termios(struct uart_port *port, } } omap8250_restore_regs(up); +} - uart_port_unlock_irq(&up->port); - pm_runtime_mark_last_busy(port->dev); - pm_runtime_put_autosuspend(port->dev); +/* + * OMAP can use "CLK / (16 or 13) / div" for baud rate. And then we have have + * some differences in how we want to handle flow control. + */ +static void omap_8250_set_termios(struct uart_port *port, + struct ktermios *termios, + const struct ktermios *old) +{ + struct omap8250_priv *priv = port->private_data; + unsigned int baud; + + /* + * Ask the core to calculate the divisor for us. + */ + baud = uart_get_baud_rate(port, termios, old, + port->uartclk / 16 / UART_DIV_MAX, + port->uartclk / 13); + + omap_8250_set_termios_atomic(port, termios, old, baud); /* calculate wakeup latency constraint */ priv->calc_latency = USEC_PER_SEC * 64 * 8 / baud; @@ -537,10 +547,9 @@ static void omap_8250_pm(struct uart_port *port, unsigned int state, struct uart_8250_port *up = up_to_u8250p(port); u8 efr; - pm_runtime_get_sync(port->dev); - + guard(serial8250_rpm)(up); /* Synchronize UART_IER access against the console. */ - uart_port_lock_irq(port); + guard(uart_port_lock_irq)(port); serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); efr = serial_in(up, UART_EFR); @@ -551,11 +560,6 @@ static void omap_8250_pm(struct uart_port *port, unsigned int state, serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B); serial_out(up, UART_EFR, efr); serial_out(up, UART_LCR, 0); - - uart_port_unlock_irq(port); - - pm_runtime_mark_last_busy(port->dev); - pm_runtime_put_autosuspend(port->dev); } static void omap_serial_fill_features_erratas(struct uart_8250_port *up, @@ -727,7 +731,11 @@ static int omap_8250_startup(struct uart_port *port) return ret; } - pm_runtime_get_sync(port->dev); +#ifdef CONFIG_PM + up->capabilities |= UART_CAP_RPM; +#endif + + guard(serial8250_rpm)(up); serial_out(up, UART_FCR, UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); @@ -750,14 +758,10 @@ static int omap_8250_startup(struct uart_port *port) } /* Synchronize UART_IER access against the console. */ - uart_port_lock_irq(port); - up->ier = UART_IER_RLSI | UART_IER_RDI; - serial_out(up, UART_IER, up->ier); - uart_port_unlock_irq(port); - -#ifdef CONFIG_PM - up->capabilities |= UART_CAP_RPM; -#endif + scoped_guard(uart_port_lock_irq, port) { + up->ier = UART_IER_RLSI | UART_IER_RDI; + serial_out(up, UART_IER, up->ier); + } /* Enable module level wake up */ priv->wer = OMAP_UART_WER_MOD_WKUP; @@ -766,15 +770,12 @@ static int omap_8250_startup(struct uart_port *port) serial_out(up, UART_OMAP_WER, priv->wer); if (up->dma && !(priv->habit & UART_HAS_EFR2)) { - uart_port_lock_irq(port); + guard(uart_port_lock_irq)(port); up->dma->rx_dma(up); - uart_port_unlock_irq(port); } enable_irq(port->irq); - pm_runtime_mark_last_busy(port->dev); - pm_runtime_put_autosuspend(port->dev); return 0; } @@ -783,7 +784,7 @@ static void omap_8250_shutdown(struct uart_port *port) struct uart_8250_port *up = up_to_u8250p(port); struct omap8250_priv *priv = port->private_data; - pm_runtime_get_sync(port->dev); + guard(serial8250_rpm)(up); flush_work(&priv->qos_work); if (up->dma) @@ -794,10 +795,11 @@ static void omap_8250_shutdown(struct uart_port *port) serial_out(up, UART_OMAP_EFR2, 0x0); /* Synchronize UART_IER access against the console. */ - uart_port_lock_irq(port); - up->ier = 0; - serial_out(up, UART_IER, 0); - uart_port_unlock_irq(port); + scoped_guard(uart_port_lock_irq, port) { + up->ier = 0; + serial_out(up, UART_IER, 0); + } + disable_irq_nosync(port->irq); dev_pm_clear_wake_irq(port->dev); @@ -810,46 +812,33 @@ static void omap_8250_shutdown(struct uart_port *port) if (up->lcr & UART_LCR_SBC) serial_out(up, UART_LCR, up->lcr & ~UART_LCR_SBC); serial_out(up, UART_FCR, UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); - - pm_runtime_mark_last_busy(port->dev); - pm_runtime_put_autosuspend(port->dev); } static void omap_8250_throttle(struct uart_port *port) { struct omap8250_priv *priv = port->private_data; - unsigned long flags; - pm_runtime_get_sync(port->dev); + guard(serial8250_rpm)(up_to_u8250p(port)); + guard(uart_port_lock_irqsave)(port); - uart_port_lock_irqsave(port, &flags); port->ops->stop_rx(port); priv->throttled = true; - uart_port_unlock_irqrestore(port, flags); - - pm_runtime_mark_last_busy(port->dev); - pm_runtime_put_autosuspend(port->dev); } static void omap_8250_unthrottle(struct uart_port *port) { struct omap8250_priv *priv = port->private_data; struct uart_8250_port *up = up_to_u8250p(port); - unsigned long flags; - - pm_runtime_get_sync(port->dev); + guard(serial8250_rpm)(up); /* Synchronize UART_IER access against the console. */ - uart_port_lock_irqsave(port, &flags); + guard(uart_port_lock_irqsave)(port); + priv->throttled = false; if (up->dma) up->dma->rx_dma(up); up->ier |= UART_IER_RLSI | UART_IER_RDI; serial_out(up, UART_IER, up->ier); - uart_port_unlock_irqrestore(port, flags); - - pm_runtime_mark_last_busy(port->dev); - pm_runtime_put_autosuspend(port->dev); } static int omap8250_rs485_config(struct uart_port *port, @@ -987,30 +976,26 @@ static void __dma_rx_complete(void *param) struct omap8250_priv *priv = p->port.private_data; struct uart_8250_dma *dma = p->dma; struct dma_tx_state state; - unsigned long flags; /* Synchronize UART_IER access against the console. */ - uart_port_lock_irqsave(&p->port, &flags); + guard(uart_port_lock_irqsave)(&p->port); /* * If the tx status is not DMA_COMPLETE, then this is a delayed * completion callback. A previous RX timeout flush would have * already pushed the data, so exit. */ - if (dmaengine_tx_status(dma->rxchan, dma->rx_cookie, &state) != - DMA_COMPLETE) { - uart_port_unlock_irqrestore(&p->port, flags); + if (dmaengine_tx_status(dma->rxchan, dma->rx_cookie, &state) != DMA_COMPLETE) return; - } + __dma_rx_do_complete(p); - if (!priv->throttled) { - p->ier |= UART_IER_RLSI | UART_IER_RDI; - serial_out(p, UART_IER, p->ier); - if (!(priv->habit & UART_HAS_EFR2)) - omap_8250_rx_dma(p); - } + if (priv->throttled) + return; - uart_port_unlock_irqrestore(&p->port, flags); + p->ier |= UART_IER_RLSI | UART_IER_RDI; + serial_out(p, UART_IER, p->ier); + if (!(priv->habit & UART_HAS_EFR2)) + omap_8250_rx_dma(p); } static void omap_8250_rx_dma_flush(struct uart_8250_port *p) @@ -1108,14 +1093,13 @@ static void omap_8250_dma_tx_complete(void *param) struct uart_8250_port *p = param; struct uart_8250_dma *dma = p->dma; struct tty_port *tport = &p->port.state->port; - unsigned long flags; bool en_thri = false; struct omap8250_priv *priv = p->port.private_data; dma_sync_single_for_cpu(dma->txchan->device->dev, dma->tx_addr, UART_XMIT_SIZE, DMA_TO_DEVICE); - uart_port_lock_irqsave(&p->port, &flags); + guard(uart_port_lock_irqsave)(&p->port); dma->tx_running = 0; @@ -1143,8 +1127,6 @@ static void omap_8250_dma_tx_complete(void *param) dma->tx_err = 1; serial8250_set_THRI(p); } - - uart_port_unlock_irqrestore(&p->port, flags); } static int omap_8250_tx_dma(struct uart_8250_port *p) @@ -1372,6 +1354,18 @@ static int omap8250_no_handle_irq(struct uart_port *port) return 0; } +static int omap8250_select_wakeup_pinctrl(struct device *dev, + struct omap8250_priv *priv) +{ + if (IS_ERR_OR_NULL(priv->pinctrl_wakeup)) + return 0; + + if (!device_may_wakeup(dev)) + return 0; + + return pinctrl_select_state(priv->pinctrl, priv->pinctrl_wakeup); +} + static struct omap8250_dma_params am654_dma = { .rx_size = SZ_2K, .rx_trigger = 1, @@ -1596,6 +1590,11 @@ static int omap8250_probe(struct platform_device *pdev) priv->line = ret; pm_runtime_mark_last_busy(&pdev->dev); pm_runtime_put_autosuspend(&pdev->dev); + + priv->pinctrl = devm_pinctrl_get(&pdev->dev); + if (!IS_ERR_OR_NULL(priv->pinctrl)) + priv->pinctrl_wakeup = pinctrl_lookup_state(priv->pinctrl, "wakeup"); + return 0; err: pm_runtime_dont_use_autosuspend(&pdev->dev); @@ -1653,6 +1652,13 @@ static int omap8250_suspend(struct device *dev) struct uart_8250_port *up = serial8250_get_port(priv->line); int err = 0; + err = omap8250_select_wakeup_pinctrl(dev, priv); + if (err) { + dev_err(dev, "Failed to select wakeup pinctrl, aborting suspend %pe\n", + ERR_PTR(err)); + return err; + } + serial8250_suspend_port(priv->line); err = pm_runtime_resume_and_get(dev); @@ -1674,6 +1680,13 @@ static int omap8250_resume(struct device *dev) struct uart_8250_port *up = serial8250_get_port(priv->line); int err; + err = pinctrl_select_default_state(dev); + if (err) { + dev_err(dev, "Failed to select default pinctrl state on resume: %pe\n", + ERR_PTR(err)); + return err; + } + if (uart_console(&up->port) && console_suspend_enabled) { err = pm_runtime_force_resume(dev); if (err) @@ -1795,15 +1808,13 @@ static int omap8250_runtime_resume(struct device *dev) up = serial8250_get_port(priv->line); if (up && omap8250_lost_context(up)) { - uart_port_lock_irq(&up->port); + guard(uart_port_lock_irq)(&up->port); omap8250_restore_regs(up); - uart_port_unlock_irq(&up->port); } if (up && up->dma && up->dma->rxchan && !(priv->habit & UART_HAS_EFR2)) { - uart_port_lock_irq(&up->port); + guard(uart_port_lock_irq)(&up->port); omap_8250_rx_dma(up); - uart_port_unlock_irq(&up->port); } atomic_set(&priv->active, 1); diff --git a/drivers/tty/serial/8250/8250_platform.c b/drivers/tty/serial/8250/8250_platform.c index c0343bfb8064..b27981340e76 100644 --- a/drivers/tty/serial/8250/8250_platform.c +++ b/drivers/tty/serial/8250/8250_platform.c @@ -10,6 +10,7 @@ */ #include <linux/acpi.h> #include <linux/array_size.h> +#include <linux/cleanup.h> #include <linux/io.h> #include <linux/module.h> #include <linux/moduleparam.h> @@ -110,41 +111,44 @@ void __init serial8250_isa_init_ports(void) static int serial8250_probe_acpi(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct uart_8250_port uart = { }; struct resource *regs; int ret, line; + struct uart_8250_port *uart __free(kfree) = kzalloc(sizeof(*uart), GFP_KERNEL); + if (!uart) + return -ENOMEM; + regs = platform_get_mem_or_io(pdev, 0); if (!regs) return dev_err_probe(dev, -EINVAL, "no registers defined\n"); switch (resource_type(regs)) { case IORESOURCE_IO: - uart.port.iobase = regs->start; + uart->port.iobase = regs->start; break; case IORESOURCE_MEM: - uart.port.mapbase = regs->start; - uart.port.mapsize = resource_size(regs); - uart.port.flags = UPF_IOREMAP; + uart->port.mapbase = regs->start; + uart->port.mapsize = resource_size(regs); + uart->port.flags = UPF_IOREMAP; break; default: return -EINVAL; } /* default clock frequency */ - uart.port.uartclk = 1843200; - uart.port.type = PORT_16550A; - uart.port.dev = &pdev->dev; - uart.port.flags |= UPF_SKIP_TEST | UPF_BOOT_AUTOCONF; + uart->port.uartclk = 1843200; + uart->port.type = PORT_16550A; + uart->port.dev = &pdev->dev; + uart->port.flags |= UPF_SKIP_TEST | UPF_BOOT_AUTOCONF; - ret = uart_read_and_validate_port_properties(&uart.port); + ret = uart_read_and_validate_port_properties(&uart->port); /* no interrupt -> fall back to polling */ if (ret == -ENXIO) ret = 0; if (ret) return ret; - line = serial8250_register_8250_port(&uart); + line = serial8250_register_8250_port(uart); if (line < 0) return line; @@ -153,43 +157,44 @@ static int serial8250_probe_acpi(struct platform_device *pdev) static int serial8250_probe_platform(struct platform_device *dev, struct plat_serial8250_port *p) { - struct uart_8250_port uart; int ret, i, irqflag = 0; - memset(&uart, 0, sizeof(uart)); + struct uart_8250_port *uart __free(kfree) = kzalloc(sizeof(*uart), GFP_KERNEL); + if (!uart) + return -ENOMEM; if (share_irqs) irqflag = IRQF_SHARED; for (i = 0; p && p->flags != 0; p++, i++) { - uart.port.iobase = p->iobase; - uart.port.membase = p->membase; - uart.port.irq = p->irq; - uart.port.irqflags = p->irqflags; - uart.port.uartclk = p->uartclk; - uart.port.regshift = p->regshift; - uart.port.iotype = p->iotype; - uart.port.flags = p->flags; - uart.port.mapbase = p->mapbase; - uart.port.mapsize = p->mapsize; - uart.port.hub6 = p->hub6; - uart.port.has_sysrq = p->has_sysrq; - uart.port.private_data = p->private_data; - uart.port.type = p->type; - uart.bugs = p->bugs; - uart.port.serial_in = p->serial_in; - uart.port.serial_out = p->serial_out; - uart.dl_read = p->dl_read; - uart.dl_write = p->dl_write; - uart.port.handle_irq = p->handle_irq; - uart.port.handle_break = p->handle_break; - uart.port.set_termios = p->set_termios; - uart.port.set_ldisc = p->set_ldisc; - uart.port.get_mctrl = p->get_mctrl; - uart.port.pm = p->pm; - uart.port.dev = &dev->dev; - uart.port.irqflags |= irqflag; - ret = serial8250_register_8250_port(&uart); + uart->port.iobase = p->iobase; + uart->port.membase = p->membase; + uart->port.irq = p->irq; + uart->port.irqflags = p->irqflags; + uart->port.uartclk = p->uartclk; + uart->port.regshift = p->regshift; + uart->port.iotype = p->iotype; + uart->port.flags = p->flags; + uart->port.mapbase = p->mapbase; + uart->port.mapsize = p->mapsize; + uart->port.hub6 = p->hub6; + uart->port.has_sysrq = p->has_sysrq; + uart->port.private_data = p->private_data; + uart->port.type = p->type; + uart->bugs = p->bugs; + uart->port.serial_in = p->serial_in; + uart->port.serial_out = p->serial_out; + uart->dl_read = p->dl_read; + uart->dl_write = p->dl_write; + uart->port.handle_irq = p->handle_irq; + uart->port.handle_break = p->handle_break; + uart->port.set_termios = p->set_termios; + uart->port.set_ldisc = p->set_ldisc; + uart->port.get_mctrl = p->get_mctrl; + uart->port.pm = p->pm; + uart->port.dev = &dev->dev; + uart->port.irqflags |= irqflag; + ret = serial8250_register_8250_port(uart); if (ret < 0) { dev_err(&dev->dev, "unable to register port at index %d " "(IO%lx MEM%llx IRQ%d): %d\n", i, diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 2da9db960d09..719faf92aa8a 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -508,20 +508,22 @@ void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p) } EXPORT_SYMBOL_GPL(serial8250_clear_and_reinit_fifos); -static void serial8250_rpm_get(struct uart_8250_port *p) +void serial8250_rpm_get(struct uart_8250_port *p) { if (!(p->capabilities & UART_CAP_RPM)) return; pm_runtime_get_sync(p->port.dev); } +EXPORT_SYMBOL_GPL(serial8250_rpm_get); -static void serial8250_rpm_put(struct uart_8250_port *p) +void serial8250_rpm_put(struct uart_8250_port *p) { if (!(p->capabilities & UART_CAP_RPM)) return; pm_runtime_mark_last_busy(p->port.dev); pm_runtime_put_autosuspend(p->port.dev); } +EXPORT_SYMBOL_GPL(serial8250_rpm_put); /** * serial8250_em485_init() - put uart_8250_port into rs485 emulating @@ -672,28 +674,27 @@ static void serial8250_set_sleep(struct uart_8250_port *p, int sleep) { unsigned char lcr = 0, efr = 0; - serial8250_rpm_get(p); - - if (p->capabilities & UART_CAP_SLEEP) { - /* Synchronize UART_IER access against the console. */ - uart_port_lock_irq(&p->port); - if (p->capabilities & UART_CAP_EFR) { - lcr = serial_in(p, UART_LCR); - efr = serial_in(p, UART_EFR); - serial_out(p, UART_LCR, UART_LCR_CONF_MODE_B); - serial_out(p, UART_EFR, UART_EFR_ECB); - serial_out(p, UART_LCR, 0); - } - serial_out(p, UART_IER, sleep ? UART_IERX_SLEEP : 0); - if (p->capabilities & UART_CAP_EFR) { - serial_out(p, UART_LCR, UART_LCR_CONF_MODE_B); - serial_out(p, UART_EFR, efr); - serial_out(p, UART_LCR, lcr); - } - uart_port_unlock_irq(&p->port); - } + guard(serial8250_rpm)(p); + + if (!(p->capabilities & UART_CAP_SLEEP)) + return; + + /* Synchronize UART_IER access against the console. */ + guard(uart_port_lock_irq)(&p->port); - serial8250_rpm_put(p); + if (p->capabilities & UART_CAP_EFR) { + lcr = serial_in(p, UART_LCR); + efr = serial_in(p, UART_EFR); + serial_out(p, UART_LCR, UART_LCR_CONF_MODE_B); + serial_out(p, UART_EFR, UART_EFR_ECB); + serial_out(p, UART_LCR, 0); + } + serial_out(p, UART_IER, sleep ? UART_IERX_SLEEP : 0); + if (p->capabilities & UART_CAP_EFR) { + serial_out(p, UART_LCR, UART_LCR_CONF_MODE_B); + serial_out(p, UART_EFR, efr); + serial_out(p, UART_LCR, lcr); + } } /* Clear the interrupt registers. */ @@ -1229,9 +1230,8 @@ static void autoconfig_irq(struct uart_8250_port *up) probe_irq_off(probe_irq_on()); save_mcr = serial8250_in_MCR(up); /* Synchronize UART_IER access against the console. */ - uart_port_lock_irq(port); - save_ier = serial_in(up, UART_IER); - uart_port_unlock_irq(port); + scoped_guard(uart_port_lock_irq, port) + save_ier = serial_in(up, UART_IER); serial8250_out_MCR(up, UART_MCR_OUT1 | UART_MCR_OUT2); irqs = probe_irq_on(); @@ -1244,9 +1244,8 @@ static void autoconfig_irq(struct uart_8250_port *up) UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2); } /* Synchronize UART_IER access against the console. */ - uart_port_lock_irq(port); - serial_out(up, UART_IER, UART_IER_ALL_INTR); - uart_port_unlock_irq(port); + scoped_guard(uart_port_lock_irq, port) + serial_out(up, UART_IER, UART_IER_ALL_INTR); serial8250_clear_interrupts(port); serial_out(up, UART_TX, 0xFF); udelay(20); @@ -1254,9 +1253,8 @@ static void autoconfig_irq(struct uart_8250_port *up) serial8250_out_MCR(up, save_mcr); /* Synchronize UART_IER access against the console. */ - uart_port_lock_irq(port); - serial_out(up, UART_IER, save_ier); - uart_port_unlock_irq(port); + scoped_guard(uart_port_lock_irq, port) + serial_out(up, UART_IER, save_ier); if (port->flags & UPF_FOURPORT) outb_p(save_ICP, ICP); @@ -1271,12 +1269,10 @@ static void serial8250_stop_rx(struct uart_port *port) /* Port locked to synchronize UART_IER access against the console. */ lockdep_assert_held_once(&port->lock); - serial8250_rpm_get(up); + guard(serial8250_rpm)(up); up->ier &= ~(UART_IER_RLSI | UART_IER_RDI); serial_port_out(port, UART_IER, up->ier); - - serial8250_rpm_put(up); } /** @@ -1320,17 +1316,15 @@ static enum hrtimer_restart serial8250_em485_handle_stop_tx(struct hrtimer *t) struct uart_8250_em485 *em485 = container_of(t, struct uart_8250_em485, stop_tx_timer); struct uart_8250_port *p = em485->port; - unsigned long flags; - serial8250_rpm_get(p); - uart_port_lock_irqsave(&p->port, &flags); + guard(serial8250_rpm)(p); + guard(uart_port_lock_irqsave)(&p->port); + if (em485->active_timer == &em485->stop_tx_timer) { p->rs485_stop_tx(p, true); em485->active_timer = NULL; em485->tx_stopped = true; } - uart_port_unlock_irqrestore(&p->port, flags); - serial8250_rpm_put(p); return HRTIMER_NORESTART; } @@ -1405,7 +1399,7 @@ static void serial8250_stop_tx(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); - serial8250_rpm_get(up); + guard(serial8250_rpm)(up); __stop_tx(up); /* @@ -1415,7 +1409,6 @@ static void serial8250_stop_tx(struct uart_port *port) up->acr |= UART_ACR_TXDIS; serial_icr_write(up, UART_ACR, up->acr); } - serial8250_rpm_put(up); } static inline void __start_tx(struct uart_port *port) @@ -1510,14 +1503,13 @@ static enum hrtimer_restart serial8250_em485_handle_start_tx(struct hrtimer *t) struct uart_8250_em485 *em485 = container_of(t, struct uart_8250_em485, start_tx_timer); struct uart_8250_port *p = em485->port; - unsigned long flags; - uart_port_lock_irqsave(&p->port, &flags); + guard(uart_port_lock_irqsave)(&p->port); + if (em485->active_timer == &em485->start_tx_timer) { __start_tx(&p->port); em485->active_timer = NULL; } - uart_port_unlock_irqrestore(&p->port, flags); return HRTIMER_NORESTART; } @@ -1585,9 +1577,8 @@ static void serial8250_enable_ms(struct uart_port *port) up->ier |= UART_IER_MSI; - serial8250_rpm_get(up); + guard(serial8250_rpm)(up); serial_port_out(port, UART_IER, up->ier); - serial8250_rpm_put(up); } void serial8250_read_char(struct uart_8250_port *up, u16 lsr) @@ -1848,15 +1839,11 @@ static int serial8250_default_handle_irq(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); unsigned int iir; - int ret; - serial8250_rpm_get(up); + guard(serial8250_rpm)(up); iir = serial_port_in(port, UART_IIR); - ret = serial8250_handle_irq(port, iir); - - serial8250_rpm_put(up); - return ret; + return serial8250_handle_irq(port, iir); } /* @@ -1867,16 +1854,14 @@ static int serial8250_default_handle_irq(struct uart_port *port) */ static int serial8250_tx_threshold_handle_irq(struct uart_port *port) { - unsigned long flags; unsigned int iir = serial_port_in(port, UART_IIR); /* TX Threshold IRQ triggered so load up FIFO */ if ((iir & UART_IIR_ID) == UART_IIR_THRI) { struct uart_8250_port *up = up_to_u8250p(port); - uart_port_lock_irqsave(port, &flags); + guard(uart_port_lock_irqsave)(port); serial8250_tx_chars(up); - uart_port_unlock_irqrestore(port, flags); } iir = serial_port_in(port, UART_IIR); @@ -1886,19 +1871,14 @@ static int serial8250_tx_threshold_handle_irq(struct uart_port *port) static unsigned int serial8250_tx_empty(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); - unsigned int result = 0; - unsigned long flags; - serial8250_rpm_get(up); + guard(serial8250_rpm)(up); + guard(uart_port_lock_irqsave)(port); - uart_port_lock_irqsave(port, &flags); if (!serial8250_tx_dma_running(up) && uart_lsr_tx_empty(serial_lsr_in(up))) - result = TIOCSER_TEMT; - uart_port_unlock_irqrestore(port, flags); - - serial8250_rpm_put(up); + return TIOCSER_TEMT; - return result; + return 0; } unsigned int serial8250_do_get_mctrl(struct uart_port *port) @@ -1907,9 +1887,8 @@ unsigned int serial8250_do_get_mctrl(struct uart_port *port) unsigned int status; unsigned int val; - serial8250_rpm_get(up); - status = serial8250_modem_status(up); - serial8250_rpm_put(up); + scoped_guard(serial8250_rpm, up) + status = serial8250_modem_status(up); val = serial8250_MSR_to_TIOCM(status); if (up->gpios) @@ -1953,17 +1932,15 @@ static void serial8250_set_mctrl(struct uart_port *port, unsigned int mctrl) static void serial8250_break_ctl(struct uart_port *port, int break_state) { struct uart_8250_port *up = up_to_u8250p(port); - unsigned long flags; - serial8250_rpm_get(up); - uart_port_lock_irqsave(port, &flags); + guard(serial8250_rpm)(up); + guard(uart_port_lock_irqsave)(port); + if (break_state == -1) up->lcr |= UART_LCR_SBC; else up->lcr &= ~UART_LCR_SBC; serial_port_out(port, UART_LCR, up->lcr); - uart_port_unlock_irqrestore(port, flags); - serial8250_rpm_put(up); } /* Returns true if @bits were set, false on timeout */ @@ -2023,22 +2000,15 @@ static void wait_for_xmitr(struct uart_8250_port *up, int bits) static int serial8250_get_poll_char(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); - int status; u16 lsr; - serial8250_rpm_get(up); + guard(serial8250_rpm)(up); lsr = serial_port_in(port, UART_LSR); + if (!(lsr & UART_LSR_DR)) + return NO_POLL_CHAR; - if (!(lsr & UART_LSR_DR)) { - status = NO_POLL_CHAR; - goto out; - } - - status = serial_port_in(port, UART_RX); -out: - serial8250_rpm_put(up); - return status; + return serial_port_in(port, UART_RX); } @@ -2056,7 +2026,7 @@ static void serial8250_put_poll_char(struct uart_port *port, * should allow safe lockless usage here. */ - serial8250_rpm_get(up); + guard(serial8250_rpm)(up); /* * First save the IER then disable the interrupts */ @@ -2075,7 +2045,6 @@ static void serial8250_put_poll_char(struct uart_port *port, */ wait_for_xmitr(up, UART_LSR_BOTH_EMPTY); serial_port_out(port, UART_IER, ier); - serial8250_rpm_put(up); } #endif /* CONFIG_CONSOLE_POLL */ @@ -2083,16 +2052,15 @@ static void serial8250_put_poll_char(struct uart_port *port, static void serial8250_startup_special(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); - unsigned long flags; switch (port->type) { - case PORT_16C950: + case PORT_16C950: { /* * Wake up and initialize UART * * Synchronize UART_IER access against the console. */ - uart_port_lock_irqsave(port, &flags); + guard(uart_port_lock_irqsave)(port); up->acr = 0; serial_port_out(port, UART_LCR, UART_LCR_CONF_MODE_B); serial_port_out(port, UART_EFR, UART_EFR_ECB); @@ -2102,18 +2070,18 @@ static void serial8250_startup_special(struct uart_port *port) serial_port_out(port, UART_LCR, UART_LCR_CONF_MODE_B); serial_port_out(port, UART_EFR, UART_EFR_ECB); serial_port_out(port, UART_LCR, 0); - uart_port_unlock_irqrestore(port, flags); break; + } case PORT_DA830: /* * Reset the port * * Synchronize UART_IER access against the console. */ - uart_port_lock_irqsave(port, &flags); - serial_port_out(port, UART_IER, 0); - serial_port_out(port, UART_DA830_PWREMU_MGMT, 0); - uart_port_unlock_irqrestore(port, flags); + scoped_guard(uart_port_lock_irqsave, port) { + serial_port_out(port, UART_IER, 0); + serial_port_out(port, UART_DA830_PWREMU_MGMT, 0); + } mdelay(10); /* Enable Tx, Rx and free run mode */ @@ -2171,7 +2139,6 @@ static void serial8250_set_TRG_levels(struct uart_port *port) static void serial8250_THRE_test(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); - unsigned long flags; bool iir_noint1, iir_noint2; if (!port->irq) @@ -2191,19 +2158,17 @@ static void serial8250_THRE_test(struct uart_port *port) * * Synchronize UART_IER access against the console. */ - uart_port_lock_irqsave(port, &flags); - - wait_for_xmitr(up, UART_LSR_THRE); - serial_port_out_sync(port, UART_IER, UART_IER_THRI); - udelay(1); /* allow THRE to set */ - iir_noint1 = serial_port_in(port, UART_IIR) & UART_IIR_NO_INT; - serial_port_out(port, UART_IER, 0); - serial_port_out_sync(port, UART_IER, UART_IER_THRI); - udelay(1); /* allow a working UART time to re-assert THRE */ - iir_noint2 = serial_port_in(port, UART_IIR) & UART_IIR_NO_INT; - serial_port_out(port, UART_IER, 0); - - uart_port_unlock_irqrestore(port, flags); + scoped_guard(uart_port_lock_irqsave, port) { + wait_for_xmitr(up, UART_LSR_THRE); + serial_port_out_sync(port, UART_IER, UART_IER_THRI); + udelay(1); /* allow THRE to set */ + iir_noint1 = serial_port_in(port, UART_IIR) & UART_IIR_NO_INT; + serial_port_out(port, UART_IER, 0); + serial_port_out_sync(port, UART_IER, UART_IER_THRI); + udelay(1); /* allow a working UART time to re-assert THRE */ + iir_noint2 = serial_port_in(port, UART_IIR) & UART_IIR_NO_INT; + serial_port_out(port, UART_IER, 0); + } if (port->irqflags & IRQF_SHARED) enable_irq(port->irq); @@ -2267,14 +2232,11 @@ static void serial8250_iir_txen_test(struct uart_port *port) static void serial8250_initialize(struct uart_port *port) { - unsigned long flags; - - uart_port_lock_irqsave(port, &flags); + guard(uart_port_lock_irqsave)(port); serial_port_out(port, UART_LCR, UART_LCR_WLEN8); serial8250_init_mctrl(port); serial8250_iir_txen_test(port); - uart_port_unlock_irqrestore(port, flags); } int serial8250_do_startup(struct uart_port *port) @@ -2293,7 +2255,7 @@ int serial8250_do_startup(struct uart_port *port) if (port->iotype != up->cur_iotype) set_io_from_upio(port); - serial8250_rpm_get(up); + guard(serial8250_rpm)(up); serial8250_startup_special(port); @@ -2313,8 +2275,7 @@ int serial8250_do_startup(struct uart_port *port) if (!(port->flags & UPF_BUGGY_UART) && (serial_port_in(port, UART_LSR) == 0xff)) { dev_info_ratelimited(port->dev, "LSR safety check engaged!\n"); - retval = -ENODEV; - goto out; + return -ENODEV; } serial8250_set_TRG_levels(port); @@ -2325,7 +2286,7 @@ int serial8250_do_startup(struct uart_port *port) retval = up->ops->setup_irq(up); if (retval) - goto out; + return retval; serial8250_THRE_test(port); @@ -2374,10 +2335,8 @@ int serial8250_do_startup(struct uart_port *port) outb_p(0x80, icp); inb_p(icp); } - retval = 0; -out: - serial8250_rpm_put(up); - return retval; + + return 0; } EXPORT_SYMBOL_GPL(serial8250_do_startup); @@ -2391,7 +2350,6 @@ static int serial8250_startup(struct uart_port *port) void serial8250_do_shutdown(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); - unsigned long flags; serial8250_rpm_get(up); /* @@ -2399,26 +2357,26 @@ void serial8250_do_shutdown(struct uart_port *port) * * Synchronize UART_IER access against the console. */ - uart_port_lock_irqsave(port, &flags); - up->ier = 0; - serial_port_out(port, UART_IER, 0); - uart_port_unlock_irqrestore(port, flags); + scoped_guard(uart_port_lock_irqsave, port) { + up->ier = 0; + serial_port_out(port, UART_IER, 0); + } synchronize_irq(port->irq); if (up->dma) serial8250_release_dma(up); - uart_port_lock_irqsave(port, &flags); - if (port->flags & UPF_FOURPORT) { - /* reset interrupts on the AST Fourport board */ - inb((port->iobase & 0xfe0) | 0x1f); - port->mctrl |= TIOCM_OUT1; - } else - port->mctrl &= ~TIOCM_OUT2; + scoped_guard(uart_port_lock_irqsave, port) { + if (port->flags & UPF_FOURPORT) { + /* reset interrupts on the AST Fourport board */ + inb((port->iobase & 0xfe0) | 0x1f); + port->mctrl |= TIOCM_OUT1; + } else + port->mctrl &= ~TIOCM_OUT2; - serial8250_set_mctrl(port, port->mctrl); - uart_port_unlock_irqrestore(port, flags); + serial8250_set_mctrl(port, port->mctrl); + } /* * Disable break condition and FIFOs @@ -2610,33 +2568,27 @@ static unsigned int serial8250_get_baud_rate(struct uart_port *port, void serial8250_update_uartclk(struct uart_port *port, unsigned int uartclk) { struct tty_port *tport = &port->state->port; - struct tty_struct *tty; - tty = tty_port_tty_get(tport); - if (!tty) { - mutex_lock(&tport->mutex); - port->uartclk = uartclk; - mutex_unlock(&tport->mutex); - return; - } + scoped_guard(tty_port_tty, tport) { + struct tty_struct *tty = scoped_tty(); - down_write(&tty->termios_rwsem); - mutex_lock(&tport->mutex); + guard(rwsem_write)(&tty->termios_rwsem); + guard(mutex)(&tport->mutex); - if (port->uartclk == uartclk) - goto out_unlock; + if (port->uartclk == uartclk) + return; - port->uartclk = uartclk; + port->uartclk = uartclk; - if (!tty_port_initialized(tport)) - goto out_unlock; + if (!tty_port_initialized(tport)) + return; - serial8250_do_set_termios(port, &tty->termios, NULL); + serial8250_do_set_termios(port, &tty->termios, NULL); -out_unlock: - mutex_unlock(&tport->mutex); - up_write(&tty->termios_rwsem); - tty_kref_put(tty); + return; + } + guard(mutex)(&tport->mutex); + port->uartclk = uartclk; } EXPORT_SYMBOL_GPL(serial8250_update_uartclk); @@ -2791,7 +2743,6 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, const struct ktermios *old) { struct uart_8250_port *up = up_to_u8250p(port); - unsigned long flags; unsigned int baud, quot, frac = 0; u8 lcr; @@ -2801,27 +2752,24 @@ serial8250_do_set_termios(struct uart_port *port, struct ktermios *termios, quot = serial8250_get_divisor(port, baud, &frac); /* - * Ok, we're now changing the port state. Do it with - * interrupts disabled. + * Ok, we're now changing the port state. Do it with interrupts disabled. * * Synchronize UART_IER access against the console. */ - serial8250_rpm_get(up); - uart_port_lock_irqsave(port, &flags); - - up->lcr = lcr; - serial8250_set_trigger_for_slow_speed(port, termios, baud); - serial8250_set_afe(port, termios); - uart_update_timeout(port, termios->c_cflag, baud); - serial8250_set_errors_and_ignores(port, termios); - serial8250_set_ier(port, termios); - serial8250_set_efr(port, termios); - serial8250_set_divisor(port, baud, quot, frac); - serial8250_set_fcr(port, termios); - serial8250_set_mctrl(port, port->mctrl); + scoped_guard(serial8250_rpm, up) { + guard(uart_port_lock_irqsave)(port); - uart_port_unlock_irqrestore(port, flags); - serial8250_rpm_put(up); + up->lcr = lcr; + serial8250_set_trigger_for_slow_speed(port, termios, baud); + serial8250_set_afe(port, termios); + uart_update_timeout(port, termios->c_cflag, baud); + serial8250_set_errors_and_ignores(port, termios); + serial8250_set_ier(port, termios); + serial8250_set_efr(port, termios); + serial8250_set_divisor(port, baud, quot, frac); + serial8250_set_fcr(port, termios); + serial8250_set_mctrl(port, port->mctrl); + } /* Don't rewrite B0 */ if (tty_termios_baud_rate(termios)) @@ -2843,15 +2791,13 @@ void serial8250_do_set_ldisc(struct uart_port *port, struct ktermios *termios) { if (termios->c_line == N_PPS) { port->flags |= UPF_HARDPPS_CD; - uart_port_lock_irq(port); + guard(uart_port_lock_irq)(port); serial8250_enable_ms(port); - uart_port_unlock_irq(port); } else { port->flags &= ~UPF_HARDPPS_CD; if (!UART_ENABLE_MS(port, termios->c_cflag)) { - uart_port_lock_irq(port); + guard(uart_port_lock_irq)(port); serial8250_disable_ms(port); - uart_port_unlock_irq(port); } } } diff --git a/drivers/tty/serial/8250/8250_rsa.c b/drivers/tty/serial/8250/8250_rsa.c index 12a65b79583c..40a3dbd9e452 100644 --- a/drivers/tty/serial/8250/8250_rsa.c +++ b/drivers/tty/serial/8250/8250_rsa.c @@ -140,9 +140,8 @@ void rsa_enable(struct uart_8250_port *up) return; if (up->port.uartclk != SERIAL_RSA_BAUD_BASE * 16) { - uart_port_lock_irq(&up->port); + guard(uart_port_lock_irq)(&up->port); __rsa_enable(up); - uart_port_unlock_irq(&up->port); } if (up->port.uartclk == SERIAL_RSA_BAUD_BASE * 16) serial_out(up, UART_RSA_FRR, 0); @@ -165,7 +164,8 @@ void rsa_disable(struct uart_8250_port *up) if (up->port.uartclk != SERIAL_RSA_BAUD_BASE * 16) return; - uart_port_lock_irq(&up->port); + guard(uart_port_lock_irq)(&up->port); + mode = serial_in(up, UART_RSA_MSR); result = !(mode & UART_RSA_MSR_FIFO); @@ -177,7 +177,6 @@ void rsa_disable(struct uart_8250_port *up) if (result) up->port.uartclk = SERIAL_RSA_BAUD_BASE_LO * 16; - uart_port_unlock_irq(&up->port); } EXPORT_SYMBOL_FOR_MODULES(rsa_disable, "8250_base"); diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 724ad4f3cbee..282116765e64 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -782,7 +782,7 @@ config SERIAL_CPM depends on CPM2 || CPM1 select SERIAL_CORE help - This driver supports the SCC and SMC serial ports on Motorola + This driver supports the SCC and SMC serial ports on Motorola embedded PowerPC that contain a CPM1 (8xx) or CPM2 (8xxx) config SERIAL_CPM_CONSOLE @@ -928,6 +928,14 @@ config SERIAL_QCOM_GENI_CONSOLE Serial console driver for Qualcomm Technologies Inc's GENI based QUP hardware. +config SERIAL_QCOM_GENI_UART_PORTS + int "Maximum number of GENI UART ports" + depends on SERIAL_QCOM_GENI + default "8" + help + Set this to the maximum number of serial ports you want the driver + to support. + config SERIAL_VT8500 bool "VIA VT8500 on-chip serial port support" depends on ARCH_VT8500 || COMPILE_TEST @@ -1412,7 +1420,7 @@ config SERIAL_STM32 config SERIAL_STM32_CONSOLE bool "Support for console on STM32" - depends on SERIAL_STM32=y + depends on SERIAL_STM32 select SERIAL_CORE_CONSOLE select SERIAL_EARLYCON diff --git a/drivers/tty/serial/ip22zilog.c b/drivers/tty/serial/ip22zilog.c index c2cae50f06f3..6e19c6713849 100644 --- a/drivers/tty/serial/ip22zilog.c +++ b/drivers/tty/serial/ip22zilog.c @@ -30,6 +30,7 @@ #include <linux/console.h> #include <linux/spinlock.h> #include <linux/init.h> +#include <linux/platform_device.h> #include <linux/io.h> #include <asm/irq.h> @@ -50,8 +51,9 @@ #define ZSDELAY_LONG() udelay(20) #define ZS_WSYNC(channel) do { } while (0) -#define NUM_IP22ZILOG 1 -#define NUM_CHANNELS (NUM_IP22ZILOG * 2) +#define NUM_CHANNELS 2 +#define CHANNEL_B 0 +#define CHANNEL_A 1 #define ZS_CLOCK 3672000 /* Zilog input clock rate. */ #define ZS_CLOCK_DIVISOR 16 /* Divisor this driver uses. */ @@ -62,9 +64,6 @@ struct uart_ip22zilog_port { struct uart_port port; - /* IRQ servicing chain. */ - struct uart_ip22zilog_port *next; - /* Current values of Zilog write registers. */ unsigned char curregs[NUM_ZSREGS]; @@ -72,7 +71,6 @@ struct uart_ip22zilog_port { #define IP22ZILOG_FLAG_IS_CONS 0x00000004 #define IP22ZILOG_FLAG_IS_KGDB 0x00000008 #define IP22ZILOG_FLAG_MODEM_STATUS 0x00000010 -#define IP22ZILOG_FLAG_IS_CHANNEL_A 0x00000020 #define IP22ZILOG_FLAG_REGS_HELD 0x00000040 #define IP22ZILOG_FLAG_TX_STOPPED 0x00000080 #define IP22ZILOG_FLAG_TX_ACTIVE 0x00000100 @@ -84,6 +82,8 @@ struct uart_ip22zilog_port { unsigned char prev_status; }; +static struct uart_ip22zilog_port ip22zilog_port_table[NUM_CHANNELS]; + #define ZILOG_CHANNEL_FROM_PORT(PORT) ((struct zilog_channel *)((PORT)->membase)) #define UART_ZILOG(PORT) ((struct uart_ip22zilog_port *)(PORT)) #define IP22ZILOG_GET_CURR_REG(PORT, REGNUM) \ @@ -93,7 +93,6 @@ struct uart_ip22zilog_port { #define ZS_IS_CONS(UP) ((UP)->flags & IP22ZILOG_FLAG_IS_CONS) #define ZS_IS_KGDB(UP) ((UP)->flags & IP22ZILOG_FLAG_IS_KGDB) #define ZS_WANTS_MODEM_STATUS(UP) ((UP)->flags & IP22ZILOG_FLAG_MODEM_STATUS) -#define ZS_IS_CHANNEL_A(UP) ((UP)->flags & IP22ZILOG_FLAG_IS_CHANNEL_A) #define ZS_REGS_HELD(UP) ((UP)->flags & IP22ZILOG_FLAG_REGS_HELD) #define ZS_TX_STOPPED(UP) ((UP)->flags & IP22ZILOG_FLAG_TX_STOPPED) #define ZS_TX_ACTIVE(UP) ((UP)->flags & IP22ZILOG_FLAG_TX_ACTIVE) @@ -423,60 +422,57 @@ ack_tx_int: static irqreturn_t ip22zilog_interrupt(int irq, void *dev_id) { - struct uart_ip22zilog_port *up = dev_id; - - while (up) { - struct zilog_channel *channel - = ZILOG_CHANNEL_FROM_PORT(&up->port); - unsigned char r3; - bool push = false; - - uart_port_lock(&up->port); - r3 = read_zsreg(channel, R3); + struct uart_ip22zilog_port *up; + struct zilog_channel *channel; + unsigned char r3; + bool push = false; - /* Channel A */ - if (r3 & (CHAEXT | CHATxIP | CHARxIP)) { - writeb(RES_H_IUS, &channel->control); - ZSDELAY(); - ZS_WSYNC(channel); + up = &ip22zilog_port_table[CHANNEL_A]; + channel = ZILOG_CHANNEL_FROM_PORT(&up->port); - if (r3 & CHARxIP) - push = ip22zilog_receive_chars(up, channel); - if (r3 & CHAEXT) - ip22zilog_status_handle(up, channel); - if (r3 & CHATxIP) - ip22zilog_transmit_chars(up, channel); - } - uart_port_unlock(&up->port); + uart_port_lock(&up->port); + r3 = read_zsreg(channel, R3); - if (push) - tty_flip_buffer_push(&up->port.state->port); + /* Channel A */ + if (r3 & (CHAEXT | CHATxIP | CHARxIP)) { + writeb(RES_H_IUS, &channel->control); + ZSDELAY(); + ZS_WSYNC(channel); - /* Channel B */ - up = up->next; - channel = ZILOG_CHANNEL_FROM_PORT(&up->port); - push = false; + if (r3 & CHARxIP) + push = ip22zilog_receive_chars(up, channel); + if (r3 & CHAEXT) + ip22zilog_status_handle(up, channel); + if (r3 & CHATxIP) + ip22zilog_transmit_chars(up, channel); + } + uart_port_unlock(&up->port); - uart_port_lock(&up->port); - if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) { - writeb(RES_H_IUS, &channel->control); - ZSDELAY(); - ZS_WSYNC(channel); + if (push) + tty_flip_buffer_push(&up->port.state->port); - if (r3 & CHBRxIP) - push = ip22zilog_receive_chars(up, channel); - if (r3 & CHBEXT) - ip22zilog_status_handle(up, channel); - if (r3 & CHBTxIP) - ip22zilog_transmit_chars(up, channel); - } - uart_port_unlock(&up->port); + /* Channel B */ + up = &ip22zilog_port_table[CHANNEL_B]; + channel = ZILOG_CHANNEL_FROM_PORT(&up->port); + push = false; - if (push) - tty_flip_buffer_push(&up->port.state->port); + uart_port_lock(&up->port); + if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) { + writeb(RES_H_IUS, &channel->control); + ZSDELAY(); + ZS_WSYNC(channel); - up = up->next; + if (r3 & CHBRxIP) + push = ip22zilog_receive_chars(up, channel); + if (r3 & CHBEXT) + ip22zilog_status_handle(up, channel); + if (r3 & CHBTxIP) + ip22zilog_transmit_chars(up, channel); } + uart_port_unlock(&up->port); + + if (push) + tty_flip_buffer_push(&up->port.state->port); return IRQ_HANDLED; } @@ -692,16 +688,16 @@ static void __ip22zilog_reset(struct uart_ip22zilog_port *up) udelay(100); } - if (!ZS_IS_CHANNEL_A(up)) { - up++; - channel = ZILOG_CHANNEL_FROM_PORT(&up->port); - } + up = &ip22zilog_port_table[CHANNEL_A]; + channel = ZILOG_CHANNEL_FROM_PORT(&up->port); + write_zsreg(channel, R9, FHWRES); ZSDELAY_LONG(); (void) read_zsreg(channel, R0); up->flags |= IP22ZILOG_FLAG_RESET_DONE; - up->next->flags |= IP22ZILOG_FLAG_RESET_DONE; + up = &ip22zilog_port_table[CHANNEL_B]; + up->flags |= IP22ZILOG_FLAG_RESET_DONE; } static void __ip22zilog_startup(struct uart_ip22zilog_port *up) @@ -942,47 +938,6 @@ static const struct uart_ops ip22zilog_pops = { .verify_port = ip22zilog_verify_port, }; -static struct uart_ip22zilog_port *ip22zilog_port_table; -static struct zilog_layout **ip22zilog_chip_regs; - -static struct uart_ip22zilog_port *ip22zilog_irq_chain; -static int zilog_irq = -1; - -static void * __init alloc_one_table(unsigned long size) -{ - return kzalloc(size, GFP_KERNEL); -} - -static void __init ip22zilog_alloc_tables(void) -{ - ip22zilog_port_table = (struct uart_ip22zilog_port *) - alloc_one_table(NUM_CHANNELS * sizeof(struct uart_ip22zilog_port)); - ip22zilog_chip_regs = (struct zilog_layout **) - alloc_one_table(NUM_IP22ZILOG * sizeof(struct zilog_layout *)); - - if (ip22zilog_port_table == NULL || ip22zilog_chip_regs == NULL) { - panic("IP22-Zilog: Cannot allocate IP22-Zilog tables."); - } -} - -/* Get the address of the registers for IP22-Zilog instance CHIP. */ -static struct zilog_layout * __init get_zs(int chip) -{ - unsigned long base; - - if (chip < 0 || chip >= NUM_IP22ZILOG) { - panic("IP22-Zilog: Illegal chip number %d in get_zs.", chip); - } - - /* Not probe-able, hard code it. */ - base = (unsigned long) &sgioc->uart; - - zilog_irq = SGI_SERIAL_IRQ; - request_mem_region(base, 8, "IP22-Zilog"); - - return (struct zilog_layout *) base; -} - #define ZS_PUT_CHAR_MAX_DELAY 2000 /* 10 ms */ #ifdef CONFIG_SERIAL_IP22_ZILOG_CONSOLE @@ -1070,144 +1025,123 @@ static struct uart_driver ip22zilog_reg = { #endif }; -static void __init ip22zilog_prepare(void) +static void __init ip22zilog_prepare(struct uart_ip22zilog_port *up) { unsigned char sysrq_on = IS_ENABLED(CONFIG_SERIAL_IP22_ZILOG_CONSOLE); + int brg; + + spin_lock_init(&up->port.lock); + + up->port.iotype = UPIO_MEM; + up->port.uartclk = ZS_CLOCK; + up->port.fifosize = 1; + up->port.has_sysrq = sysrq_on; + up->port.ops = &ip22zilog_pops; + up->port.type = PORT_IP22ZILOG; + + /* Normal serial TTY. */ + up->parity_mask = 0xff; + up->curregs[R1] = EXT_INT_ENAB | INT_ALL_Rx | TxINT_ENAB; + up->curregs[R4] = PAR_EVEN | X16CLK | SB1; + up->curregs[R3] = RxENAB | Rx8; + up->curregs[R5] = TxENAB | Tx8; + up->curregs[R9] = NV | MIE; + up->curregs[R10] = NRZ; + up->curregs[R11] = TCBR | RCBR; + brg = BPS_TO_BRG(9600, ZS_CLOCK / ZS_CLOCK_DIVISOR); + up->curregs[R12] = (brg & 0xff); + up->curregs[R13] = (brg >> 8) & 0xff; + up->curregs[R14] = BRENAB; +} + +static int ip22zilog_probe(struct platform_device *pdev) +{ struct uart_ip22zilog_port *up; - struct zilog_layout *rp; - int channel, chip; + char __iomem *membase; + struct resource *res; + int irq; + int i; - /* - * Temporary fix. - */ - for (channel = 0; channel < NUM_CHANNELS; channel++) - spin_lock_init(&ip22zilog_port_table[channel].port.lock); - - ip22zilog_irq_chain = &ip22zilog_port_table[NUM_CHANNELS - 1]; - up = &ip22zilog_port_table[0]; - for (channel = NUM_CHANNELS - 1 ; channel > 0; channel--) - up[channel].next = &up[channel - 1]; - up[channel].next = NULL; - - for (chip = 0; chip < NUM_IP22ZILOG; chip++) { - if (!ip22zilog_chip_regs[chip]) { - ip22zilog_chip_regs[chip] = rp = get_zs(chip); - - up[(chip * 2) + 0].port.membase = (char *) &rp->channelB; - up[(chip * 2) + 1].port.membase = (char *) &rp->channelA; - - /* In theory mapbase is the physical address ... */ - up[(chip * 2) + 0].port.mapbase = - (unsigned long) ioremap((unsigned long) &rp->channelB, 8); - up[(chip * 2) + 1].port.mapbase = - (unsigned long) ioremap((unsigned long) &rp->channelA, 8); - } + up = &ip22zilog_port_table[CHANNEL_B]; + if (up->port.dev) + return -ENOSPC; - /* Channel A */ - up[(chip * 2) + 0].port.iotype = UPIO_MEM; - up[(chip * 2) + 0].port.irq = zilog_irq; - up[(chip * 2) + 0].port.uartclk = ZS_CLOCK; - up[(chip * 2) + 0].port.fifosize = 1; - up[(chip * 2) + 0].port.has_sysrq = sysrq_on; - up[(chip * 2) + 0].port.ops = &ip22zilog_pops; - up[(chip * 2) + 0].port.type = PORT_IP22ZILOG; - up[(chip * 2) + 0].port.flags = 0; - up[(chip * 2) + 0].port.line = (chip * 2) + 0; - up[(chip * 2) + 0].flags = 0; - - /* Channel B */ - up[(chip * 2) + 1].port.iotype = UPIO_MEM; - up[(chip * 2) + 1].port.irq = zilog_irq; - up[(chip * 2) + 1].port.uartclk = ZS_CLOCK; - up[(chip * 2) + 1].port.fifosize = 1; - up[(chip * 2) + 1].port.has_sysrq = sysrq_on; - up[(chip * 2) + 1].port.ops = &ip22zilog_pops; - up[(chip * 2) + 1].port.type = PORT_IP22ZILOG; - up[(chip * 2) + 1].port.line = (chip * 2) + 1; - up[(chip * 2) + 1].flags |= IP22ZILOG_FLAG_IS_CHANNEL_A; - } + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; - for (channel = 0; channel < NUM_CHANNELS; channel++) { - struct uart_ip22zilog_port *up = &ip22zilog_port_table[channel]; - int brg; + membase = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(membase)) + return PTR_ERR(membase); - /* Normal serial TTY. */ - up->parity_mask = 0xff; - up->curregs[R1] = EXT_INT_ENAB | INT_ALL_Rx | TxINT_ENAB; - up->curregs[R4] = PAR_EVEN | X16CLK | SB1; - up->curregs[R3] = RxENAB | Rx8; - up->curregs[R5] = TxENAB | Tx8; - up->curregs[R9] = NV | MIE; - up->curregs[R10] = NRZ; - up->curregs[R11] = TCBR | RCBR; - brg = BPS_TO_BRG(9600, ZS_CLOCK / ZS_CLOCK_DIVISOR); - up->curregs[R12] = (brg & 0xff); - up->curregs[R13] = (brg >> 8) & 0xff; - up->curregs[R14] = BRENAB; - } -} + ip22zilog_prepare(up); -static int __init ip22zilog_ports_init(void) -{ - int ret; + up->port.mapbase = res->start + offsetof(struct zilog_layout, channelB); + up->port.membase = membase + offsetof(struct zilog_layout, channelB); + up->port.line = 0; + up->port.dev = &pdev->dev; + up->port.irq = irq; - printk(KERN_INFO "Serial: IP22 Zilog driver (%d chips).\n", NUM_IP22ZILOG); + up = &ip22zilog_port_table[CHANNEL_A]; + ip22zilog_prepare(up); - ip22zilog_prepare(); + up->port.mapbase = res->start + offsetof(struct zilog_layout, channelA); + up->port.membase = membase + offsetof(struct zilog_layout, channelA); + up->port.line = 1; + up->port.dev = &pdev->dev; + up->port.irq = irq; - if (request_irq(zilog_irq, ip22zilog_interrupt, 0, - "IP22-Zilog", ip22zilog_irq_chain)) { + if (request_irq(irq, ip22zilog_interrupt, 0, + "IP22-Zilog", NULL)) { panic("IP22-Zilog: Unable to register zs interrupt handler.\n"); } - ret = uart_register_driver(&ip22zilog_reg); - if (ret == 0) { - int i; - - for (i = 0; i < NUM_CHANNELS; i++) { - struct uart_ip22zilog_port *up = &ip22zilog_port_table[i]; - - uart_add_one_port(&ip22zilog_reg, &up->port); - } - } - - return ret; -} - -static int __init ip22zilog_init(void) -{ - /* IP22 Zilog setup is hard coded, no probing to do. */ - ip22zilog_alloc_tables(); - ip22zilog_ports_init(); + for (i = 0; i < NUM_CHANNELS; i++) + uart_add_one_port(&ip22zilog_reg, + &ip22zilog_port_table[i].port); return 0; } -static void __exit ip22zilog_exit(void) +static void ip22zilog_remove(struct platform_device *pdev) { int i; - struct uart_ip22zilog_port *up; for (i = 0; i < NUM_CHANNELS; i++) { - up = &ip22zilog_port_table[i]; - - uart_remove_one_port(&ip22zilog_reg, &up->port); + uart_remove_one_port(&ip22zilog_reg, + &ip22zilog_port_table[i].port); + ip22zilog_port_table[i].port.dev = NULL; } +} - /* Free IO mem */ - up = &ip22zilog_port_table[0]; - for (i = 0; i < NUM_IP22ZILOG; i++) { - if (up[(i * 2) + 0].port.mapbase) { - iounmap((void*)up[(i * 2) + 0].port.mapbase); - up[(i * 2) + 0].port.mapbase = 0; - } - if (up[(i * 2) + 1].port.mapbase) { - iounmap((void*)up[(i * 2) + 1].port.mapbase); - up[(i * 2) + 1].port.mapbase = 0; - } +static struct platform_driver ip22zilog_driver = { + .probe = ip22zilog_probe, + .remove = ip22zilog_remove, + .driver = { + .name = "ip22zilog" } +}; + +static int __init ip22zilog_init(void) +{ + int ret; + + ret = uart_register_driver(&ip22zilog_reg); + if (ret) + return ret; + + ret = platform_driver_register(&ip22zilog_driver); + if (ret) + uart_unregister_driver(&ip22zilog_reg); + return ret; + +} + +static void __exit ip22zilog_exit(void) +{ uart_unregister_driver(&ip22zilog_reg); + platform_driver_unregister(&ip22zilog_driver); } module_init(ip22zilog_init); diff --git a/drivers/tty/serial/max3100.c b/drivers/tty/serial/max3100.c index 67d80f8f801e..3faa1b6aa3ee 100644 --- a/drivers/tty/serial/max3100.c +++ b/drivers/tty/serial/max3100.c @@ -705,7 +705,7 @@ static int max3100_probe(struct spi_device *spi) break; if (i == MAX_MAX3100) { mutex_unlock(&max3100s_lock); - return dev_err_probe(dev, -ENOMEM, "too many MAX3100 chips\n"); + return dev_err_probe(dev, -ENOSPC, "too many MAX3100 chips\n"); } max3100s[i] = kzalloc(sizeof(struct max3100_port), GFP_KERNEL); diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index ce260e9949c3..ac7d3f197c3a 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -823,17 +823,28 @@ static irqreturn_t max310x_ist(int irq, void *dev_id) bool handled = false; if (s->devtype->nr > 1) { + bool done; + do { unsigned int val = ~0; + unsigned long irq; + unsigned int port; + + done = true; WARN_ON_ONCE(regmap_read(s->regmap, MAX310X_GLOBALIRQ_REG, &val)); - val = ((1 << s->devtype->nr) - 1) & ~val; - if (!val) - break; - if (max310x_port_irq(s, fls(val) - 1) == IRQ_HANDLED) - handled = true; - } while (1); + + irq = val; + + for_each_clear_bit(port, &irq, s->devtype->nr) { + done = false; + + if (max310x_port_irq(s, port) == IRQ_HANDLED) + handled = true; + } + + } while (!done); } else { if (max310x_port_irq(s, 0) == IRQ_HANDLED) handled = true; @@ -1269,8 +1280,7 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty /* Alloc port structure */ s = devm_kzalloc(dev, struct_size(s, p, devtype->nr), GFP_KERNEL); if (!s) - return dev_err_probe(dev, -ENOMEM, - "Error allocating port structure\n"); + return -ENOMEM; /* Always ask for fixed clock rate from a property. */ device_property_read_u32(dev, "clock-frequency", &uartclk); @@ -1644,6 +1654,8 @@ static int max310x_i2c_probe(struct i2c_client *client) port_client = devm_i2c_new_dummy_device(&client->dev, client->adapter, port_addr); + if (IS_ERR(port_client)) + return PTR_ERR(port_client); regcfg_i2c.name = max310x_regmap_name(i); regmaps[i] = devm_regmap_init_i2c(port_client, ®cfg_i2c); diff --git a/drivers/tty/serial/msm_serial.c b/drivers/tty/serial/msm_serial.c index 3449945493ce..2e999cb9c974 100644 --- a/drivers/tty/serial/msm_serial.c +++ b/drivers/tty/serial/msm_serial.c @@ -1102,7 +1102,7 @@ msm_find_best_baud(struct uart_port *port, unsigned int baud, if (result == baud) break; - } else if (entry->divisor > divisor) { + } else { old = target; target = clk_round_rate(msm_port->clk, old + 1); /* diff --git a/drivers/tty/serial/mvebu-uart.c b/drivers/tty/serial/mvebu-uart.c index 5de57b77abdb..8e52be2b34ea 100644 --- a/drivers/tty/serial/mvebu-uart.c +++ b/drivers/tty/serial/mvebu-uart.c @@ -1264,14 +1264,16 @@ static unsigned long mvebu_uart_clock_recalc_rate(struct clk_hw *hw, return parent_rate / uart_clock_base->div; } -static long mvebu_uart_clock_round_rate(struct clk_hw *hw, unsigned long rate, - unsigned long *parent_rate) +static int mvebu_uart_clock_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) { struct mvebu_uart_clock *uart_clock = to_uart_clock(hw); struct mvebu_uart_clock_base *uart_clock_base = to_uart_clock_base(uart_clock); - return *parent_rate / uart_clock_base->div; + req->rate = req->best_parent_rate / uart_clock_base->div; + + return 0; } static int mvebu_uart_clock_set_rate(struct clk_hw *hw, unsigned long rate, @@ -1293,7 +1295,7 @@ static const struct clk_ops mvebu_uart_clock_ops = { .is_enabled = mvebu_uart_clock_is_enabled, .save_context = mvebu_uart_clock_save_context, .restore_context = mvebu_uart_clock_restore_context, - .round_rate = mvebu_uart_clock_round_rate, + .determine_rate = mvebu_uart_clock_determine_rate, .set_rate = mvebu_uart_clock_set_rate, .recalc_rate = mvebu_uart_clock_recalc_rate, }; diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 3f5b87c4cc54..ce5cb97d60a7 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1,5 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 -// Copyright (c) 2017-2018, The Linux foundation. All rights reserved. +/* + * Copyright (c) 2017-2018, The Linux foundation. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ /* Disable MMIO tracing to prevent excessive logging of unwanted MMIO traces */ #define __DISABLE_TRACE_MMIO__ @@ -77,7 +80,6 @@ #define STALE_TIMEOUT 16 #define DEFAULT_BITS_PER_CHAR 10 #define GENI_UART_CONS_PORTS 1 -#define GENI_UART_PORTS 3 #define DEF_FIFO_DEPTH_WORDS 16 #define DEF_TX_WM 2 #define DEF_FIFO_WIDTH_BITS 32 @@ -164,33 +166,6 @@ static inline struct qcom_geni_serial_port *to_dev_port(struct uart_port *uport) return container_of(uport, struct qcom_geni_serial_port, uport); } -static struct qcom_geni_serial_port qcom_geni_uart_ports[GENI_UART_PORTS] = { - [0] = { - .uport = { - .iotype = UPIO_MEM, - .ops = &qcom_geni_uart_pops, - .flags = UPF_BOOT_AUTOCONF, - .line = 0, - }, - }, - [1] = { - .uport = { - .iotype = UPIO_MEM, - .ops = &qcom_geni_uart_pops, - .flags = UPF_BOOT_AUTOCONF, - .line = 1, - }, - }, - [2] = { - .uport = { - .iotype = UPIO_MEM, - .ops = &qcom_geni_uart_pops, - .flags = UPF_BOOT_AUTOCONF, - .line = 2, - }, - }, -}; - static struct qcom_geni_serial_port qcom_geni_console_port = { .uport = { .iotype = UPIO_MEM, @@ -285,10 +260,10 @@ static const char *qcom_geni_serial_get_type(struct uart_port *uport) return "MSM"; } -static struct qcom_geni_serial_port *get_port_from_line(int line, bool console) +static struct qcom_geni_serial_port *get_port_from_line(int line, bool console, struct device *dev) { struct qcom_geni_serial_port *port; - int nr_ports = console ? GENI_UART_CONS_PORTS : GENI_UART_PORTS; + int nr_ports = console ? GENI_UART_CONS_PORTS : CONFIG_SERIAL_QCOM_GENI_UART_PORTS; if (console) { if (line < 0 || line >= nr_ports) @@ -299,14 +274,23 @@ static struct qcom_geni_serial_port *get_port_from_line(int line, bool console) int max_alias_num = of_alias_get_highest_id("serial"); if (line < 0 || line >= nr_ports) - line = ida_alloc_range(&port_ida, max_alias_num + 1, nr_ports, GFP_KERNEL); + line = ida_alloc_range(&port_ida, max_alias_num + 1, + nr_ports - 1, GFP_KERNEL); else - line = ida_alloc_range(&port_ida, line, nr_ports, GFP_KERNEL); + line = ida_alloc_range(&port_ida, line, + nr_ports - 1, GFP_KERNEL); if (line < 0) return ERR_PTR(-ENXIO); - port = &qcom_geni_uart_ports[line]; + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return ERR_PTR(-ENOMEM); + + port->uport.iotype = UPIO_MEM; + port->uport.ops = &qcom_geni_uart_pops; + port->uport.flags = UPF_BOOT_AUTOCONF; + port->uport.line = line; } return port; } @@ -554,7 +538,7 @@ static void qcom_geni_serial_console_write(struct console *co, const char *s, WARN_ON(co->index < 0 || co->index >= GENI_UART_CONS_PORTS); - port = get_port_from_line(co->index, true); + port = get_port_from_line(co->index, true, NULL); if (IS_ERR(port)) return; @@ -1267,75 +1251,15 @@ static int qcom_geni_serial_startup(struct uart_port *uport) return 0; } -static unsigned long find_clk_rate_in_tol(struct clk *clk, unsigned int desired_clk, - unsigned int *clk_div, unsigned int percent_tol) -{ - unsigned long freq; - unsigned long div, maxdiv; - u64 mult; - unsigned long offset, abs_tol, achieved; - - abs_tol = div_u64((u64)desired_clk * percent_tol, 100); - maxdiv = CLK_DIV_MSK >> CLK_DIV_SHFT; - div = 1; - while (div <= maxdiv) { - mult = (u64)div * desired_clk; - if (mult != (unsigned long)mult) - break; - - offset = div * abs_tol; - freq = clk_round_rate(clk, mult - offset); - - /* Can only get lower if we're done */ - if (freq < mult - offset) - break; - - /* - * Re-calculate div in case rounding skipped rates but we - * ended up at a good one, then check for a match. - */ - div = DIV_ROUND_CLOSEST(freq, desired_clk); - achieved = DIV_ROUND_CLOSEST(freq, div); - if (achieved <= desired_clk + abs_tol && - achieved >= desired_clk - abs_tol) { - *clk_div = div; - return freq; - } - - div = DIV_ROUND_UP(freq, desired_clk); - } - - return 0; -} - -static unsigned long get_clk_div_rate(struct clk *clk, unsigned int baud, - unsigned int sampling_rate, unsigned int *clk_div) -{ - unsigned long ser_clk; - unsigned long desired_clk; - - desired_clk = baud * sampling_rate; - if (!desired_clk) - return 0; - - /* - * try to find a clock rate within 2% tolerance, then within 5% - */ - ser_clk = find_clk_rate_in_tol(clk, desired_clk, clk_div, 2); - if (!ser_clk) - ser_clk = find_clk_rate_in_tol(clk, desired_clk, clk_div, 5); - - return ser_clk; -} - static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud) { struct qcom_geni_serial_port *port = to_dev_port(uport); unsigned long clk_rate; - unsigned int avg_bw_core; + unsigned int avg_bw_core, clk_idx; unsigned int clk_div; u32 ver, sampling_rate; u32 ser_clk_cfg; + int ret; sampling_rate = UART_OVERSAMPLING; /* Sampling rate is halved for IP versions >= 2.5 */ @@ -1343,17 +1267,22 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud) if (ver >= QUP_SE_VERSION_2_5) sampling_rate /= 2; - clk_rate = get_clk_div_rate(port->se.clk, baud, - sampling_rate, &clk_div); - if (!clk_rate) { - dev_err(port->se.dev, - "Couldn't find suitable clock rate for %u\n", - baud * sampling_rate); + ret = geni_se_clk_freq_match(&port->se, baud * sampling_rate, &clk_idx, &clk_rate, false); + if (ret) { + dev_err(port->se.dev, "Failed to find src clk for baud rate: %d ret: %d\n", + baud, ret); + return ret; + } + + clk_div = DIV_ROUND_UP(clk_rate, baud * sampling_rate); + /* Check if calculated divider exceeds maximum allowed value */ + if (clk_div > (CLK_DIV_MSK >> CLK_DIV_SHFT)) { + dev_err(port->se.dev, "Calculated clock divider %u exceeds maximum\n", clk_div); return -EINVAL; } - dev_dbg(port->se.dev, "desired_rate = %u, clk_rate = %lu, clk_div = %u\n", - baud * sampling_rate, clk_rate, clk_div); + dev_dbg(port->se.dev, "desired_rate = %u, clk_rate = %lu, clk_div = %u\n, clk_idx = %u\n", + baud * sampling_rate, clk_rate, clk_div, clk_idx); uport->uartclk = clk_rate; port->clk_rate = clk_rate; @@ -1373,6 +1302,8 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud) writel(ser_clk_cfg, uport->membase + GENI_SER_M_CLK_CFG); writel(ser_clk_cfg, uport->membase + GENI_SER_S_CLK_CFG); + /* Configure clock selection register with the selected clock index */ + writel(clk_idx & CLK_SEL_MSK, uport->membase + SE_GENI_CLK_SEL); return 0; } @@ -1517,7 +1448,7 @@ static int qcom_geni_console_setup(struct console *co, char *options) if (co->index >= GENI_UART_CONS_PORTS || co->index < 0) return -ENXIO; - port = get_port_from_line(co->index, true); + port = get_port_from_line(co->index, true, NULL); if (IS_ERR(port)) { pr_err("Invalid line %d\n", co->index); return PTR_ERR(port); @@ -1678,7 +1609,7 @@ static struct uart_driver qcom_geni_uart_driver = { .owner = THIS_MODULE, .driver_name = "qcom_geni_uart", .dev_name = "ttyHS", - .nr = GENI_UART_PORTS, + .nr = CONFIG_SERIAL_QCOM_GENI_UART_PORTS, }; static int geni_serial_resources_on(struct uart_port *uport) @@ -1872,7 +1803,7 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) line = of_alias_get_id(pdev->dev.of_node, "hsuart"); } - port = get_port_from_line(line, data->console); + port = get_port_from_line(line, data->console, &pdev->dev); if (IS_ERR(port)) { dev_err(&pdev->dev, "Invalid line %d\n", line); return PTR_ERR(port); diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index a668e0bb26b3..1a2c4c14f6aa 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -626,7 +626,7 @@ static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen, { struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); unsigned int lsr = 0, bytes_read, i; - bool read_lsr = (iir == SC16IS7XX_IIR_RLSE_SRC) ? true : false; + bool read_lsr = (iir == SC16IS7XX_IIR_RLSE_SRC); u8 ch, flag; if (unlikely(rxlen >= sizeof(one->buf))) { diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 86d404d649a3..4757293ece8c 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -177,15 +177,13 @@ static void uart_start(struct tty_struct *tty) static void uart_update_mctrl(struct uart_port *port, unsigned int set, unsigned int clear) { - unsigned long flags; unsigned int old; - uart_port_lock_irqsave(port, &flags); + guard(uart_port_lock_irqsave)(port); old = port->mctrl; port->mctrl = (old & ~clear) | set; if (old != port->mctrl && !(port->rs485.flags & SER_RS485_ENABLED)) port->ops->set_mctrl(port, port->mctrl); - uart_port_unlock_irqrestore(port, flags); } #define uart_set_mctrl(port, set) uart_update_mctrl(port, set, 0) @@ -220,7 +218,7 @@ static void uart_change_line_settings(struct tty_struct *tty, struct uart_state /* * Set modem status enables based on termios cflag */ - uart_port_lock_irq(uport); + guard(uart_port_lock_irq)(uport); if (termios->c_cflag & CRTSCTS) uport->status |= UPSTAT_CTS_ENABLE; else @@ -241,7 +239,6 @@ static void uart_change_line_settings(struct tty_struct *tty, struct uart_state else __uart_start(state); } - uart_port_unlock_irq(uport); } static int uart_alloc_xmit_buf(struct tty_port *port) @@ -711,7 +708,6 @@ static void uart_send_xchar(struct tty_struct *tty, u8 ch) { struct uart_state *state = tty->driver_data; struct uart_port *port; - unsigned long flags; port = uart_port_ref(state); if (!port) @@ -720,11 +716,10 @@ static void uart_send_xchar(struct tty_struct *tty, u8 ch) if (port->ops->send_xchar) port->ops->send_xchar(port, ch); else { - uart_port_lock_irqsave(port, &flags); + guard(uart_port_lock_irqsave)(port); port->x_char = ch; if (ch) port->ops->start_tx(port); - uart_port_unlock_irqrestore(port, flags); } uart_port_deref(port); } @@ -1089,7 +1084,6 @@ static int uart_tiocmget(struct tty_struct *tty) struct uart_state *state = tty->driver_data; struct tty_port *port = &state->port; struct uart_port *uport; - int result; guard(mutex)(&port->mutex); @@ -1097,12 +1091,9 @@ static int uart_tiocmget(struct tty_struct *tty) if (!uport || tty_io_error(tty)) return -EIO; - uart_port_lock_irq(uport); - result = uport->mctrl; - result |= uport->ops->get_mctrl(uport); - uart_port_unlock_irq(uport); + guard(uart_port_lock_irq)(uport); - return result; + return uport->mctrl | uport->ops->get_mctrl(uport); } static int @@ -1226,16 +1217,15 @@ static int uart_wait_modem_status(struct uart_state *state, unsigned long arg) uport = uart_port_ref(state); if (!uport) return -EIO; - uart_port_lock_irq(uport); - memcpy(&cprev, &uport->icount, sizeof(struct uart_icount)); - uart_enable_ms(uport); - uart_port_unlock_irq(uport); + scoped_guard(uart_port_lock_irq, uport) { + memcpy(&cprev, &uport->icount, sizeof(struct uart_icount)); + uart_enable_ms(uport); + } add_wait_queue(&port->delta_msr_wait, &wait); for (;;) { - uart_port_lock_irq(uport); - memcpy(&cnow, &uport->icount, sizeof(struct uart_icount)); - uart_port_unlock_irq(uport); + scoped_guard(uart_port_lock_irq, uport) + memcpy(&cnow, &uport->icount, sizeof(struct uart_icount)); set_current_state(TASK_INTERRUPTIBLE); @@ -1430,7 +1420,6 @@ static void uart_set_rs485_rx_during_tx(struct uart_port *port, static int uart_rs485_config(struct uart_port *port) { struct serial_rs485 *rs485 = &port->rs485; - unsigned long flags; int ret; if (!(rs485->flags & SER_RS485_ENABLED)) @@ -1440,9 +1429,8 @@ static int uart_rs485_config(struct uart_port *port) uart_set_rs485_termination(port, rs485); uart_set_rs485_rx_during_tx(port, rs485); - uart_port_lock_irqsave(port, &flags); - ret = port->rs485_config(port, NULL, rs485); - uart_port_unlock_irqrestore(port, flags); + scoped_guard(uart_port_lock_irqsave, port) + ret = port->rs485_config(port, NULL, rs485); if (ret) { memset(rs485, 0, sizeof(*rs485)); /* unset GPIOs */ @@ -1456,12 +1444,10 @@ static int uart_rs485_config(struct uart_port *port) static int uart_get_rs485_config(struct uart_port *port, struct serial_rs485 __user *rs485) { - unsigned long flags; struct serial_rs485 aux; - uart_port_lock_irqsave(port, &flags); - aux = port->rs485; - uart_port_unlock_irqrestore(port, flags); + scoped_guard(uart_port_lock_irqsave, port) + aux = port->rs485; if (copy_to_user(rs485, &aux, sizeof(aux))) return -EFAULT; @@ -1474,7 +1460,6 @@ static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port, { struct serial_rs485 rs485; int ret; - unsigned long flags; if (!(port->rs485_supported.flags & SER_RS485_ENABLED)) return -ENOTTY; @@ -1489,16 +1474,16 @@ static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port, uart_set_rs485_termination(port, &rs485); uart_set_rs485_rx_during_tx(port, &rs485); - uart_port_lock_irqsave(port, &flags); - ret = port->rs485_config(port, &tty->termios, &rs485); - if (!ret) { - port->rs485 = rs485; + scoped_guard(uart_port_lock_irqsave, port) { + ret = port->rs485_config(port, &tty->termios, &rs485); + if (!ret) { + port->rs485 = rs485; - /* Reset RTS and other mctrl lines when disabling RS485 */ - if (!(rs485.flags & SER_RS485_ENABLED)) - port->ops->set_mctrl(port, port->mctrl); + /* Reset RTS and other mctrl lines when disabling RS485 */ + if (!(rs485.flags & SER_RS485_ENABLED)) + port->ops->set_mctrl(port, port->mctrl); + } } - uart_port_unlock_irqrestore(port, flags); if (ret) { /* restore old GPIO settings */ gpiod_set_value_cansleep(port->rs485_term_gpio, @@ -1517,15 +1502,13 @@ static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port, static int uart_get_iso7816_config(struct uart_port *port, struct serial_iso7816 __user *iso7816) { - unsigned long flags; struct serial_iso7816 aux; if (!port->iso7816_config) return -ENOTTY; - uart_port_lock_irqsave(port, &flags); - aux = port->iso7816; - uart_port_unlock_irqrestore(port, flags); + scoped_guard(uart_port_lock_irqsave, port) + aux = port->iso7816; if (copy_to_user(iso7816, &aux, sizeof(aux))) return -EFAULT; @@ -1537,8 +1520,7 @@ static int uart_set_iso7816_config(struct uart_port *port, struct serial_iso7816 __user *iso7816_user) { struct serial_iso7816 iso7816; - int i, ret; - unsigned long flags; + int i; if (!port->iso7816_config) return -ENOTTY; @@ -1554,11 +1536,11 @@ static int uart_set_iso7816_config(struct uart_port *port, if (iso7816.reserved[i]) return -EINVAL; - uart_port_lock_irqsave(port, &flags); - ret = port->iso7816_config(port, &iso7816); - uart_port_unlock_irqrestore(port, flags); - if (ret) - return ret; + scoped_guard(uart_port_lock_irqsave, port) { + int ret = port->iso7816_config(port, &iso7816); + if (ret) + return ret; + } if (copy_to_user(iso7816_user, &port->iso7816, sizeof(port->iso7816))) return -EFAULT; @@ -1770,9 +1752,8 @@ static void uart_tty_port_shutdown(struct tty_port *port) if (WARN(!uport, "detached port still initialized!\n")) return; - uart_port_lock_irq(uport); - uport->ops->stop_rx(uport); - uart_port_unlock_irq(uport); + scoped_guard(uart_port_lock_irq, uport) + uport->ops->stop_rx(uport); serial_base_port_shutdown(uport); uart_port_shutdown(port); @@ -2044,9 +2025,8 @@ static void uart_line_info(struct seq_file *m, struct uart_state *state) pm_state = state->pm_state; if (pm_state != UART_PM_STATE_ON) uart_change_pm(state, UART_PM_STATE_ON); - uart_port_lock_irq(uport); - status = uport->ops->get_mctrl(uport); - uart_port_unlock_irq(uport); + scoped_guard(uart_port_lock_irq, uport) + status = uport->ops->get_mctrl(uport); if (pm_state != UART_PM_STATE_ON) uart_change_pm(state, pm_state); @@ -2355,9 +2335,8 @@ int uart_suspend_port(struct uart_driver *drv, struct uart_port *uport) */ if (!console_suspend_enabled && uart_console(uport)) { if (uport->ops->start_rx) { - uart_port_lock_irq(uport); + guard(uart_port_lock_irq)(uport); uport->ops->stop_rx(uport); - uart_port_unlock_irq(uport); } device_set_awake_path(uport->dev); return 0; @@ -2373,15 +2352,15 @@ int uart_suspend_port(struct uart_driver *drv, struct uart_port *uport) tty_port_set_suspended(port, true); tty_port_set_initialized(port, false); - uart_port_lock_irq(uport); - ops->stop_tx(uport); - if (!(uport->rs485.flags & SER_RS485_ENABLED)) - ops->set_mctrl(uport, 0); - /* save mctrl so it can be restored on resume */ - mctrl = uport->mctrl; - uport->mctrl = 0; - ops->stop_rx(uport); - uart_port_unlock_irq(uport); + scoped_guard(uart_port_lock_irq, uport) { + ops->stop_tx(uport); + if (!(uport->rs485.flags & SER_RS485_ENABLED)) + ops->set_mctrl(uport, 0); + /* save mctrl so it can be restored on resume */ + mctrl = uport->mctrl; + uport->mctrl = 0; + ops->stop_rx(uport); + } /* * Wait for the transmitter to empty. @@ -2450,9 +2429,8 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport) uart_change_pm(state, UART_PM_STATE_ON); uport->ops->set_termios(uport, &termios, NULL); if (!console_suspend_enabled && uport->ops->start_rx) { - uart_port_lock_irq(uport); + guard(uart_port_lock_irq)(uport); uport->ops->start_rx(uport); - uart_port_unlock_irq(uport); } if (console_suspend_enabled) console_resume(uport->cons); @@ -2463,10 +2441,9 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport) int ret; uart_change_pm(state, UART_PM_STATE_ON); - uart_port_lock_irq(uport); - if (!(uport->rs485.flags & SER_RS485_ENABLED)) - ops->set_mctrl(uport, 0); - uart_port_unlock_irq(uport); + scoped_guard(uart_port_lock_irq, uport) + if (!(uport->rs485.flags & SER_RS485_ENABLED)) + ops->set_mctrl(uport, 0); if (console_suspend_enabled || !uart_console(uport)) { /* Protected by port mutex for now */ struct tty_struct *tty = port->tty; @@ -2476,11 +2453,11 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport) if (tty) uart_change_line_settings(tty, state, NULL); uart_rs485_config(uport); - uart_port_lock_irq(uport); - if (!(uport->rs485.flags & SER_RS485_ENABLED)) - ops->set_mctrl(uport, uport->mctrl); - ops->start_tx(uport); - uart_port_unlock_irq(uport); + scoped_guard(uart_port_lock_irq, uport) { + if (!(uport->rs485.flags & SER_RS485_ENABLED)) + ops->set_mctrl(uport, uport->mctrl); + ops->start_tx(uport); + } tty_port_set_initialized(port, true); } else { /* @@ -2574,8 +2551,6 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state, } if (port->type != PORT_UNKNOWN) { - unsigned long flags; - uart_report_port(drv, port); /* Synchronize with possible boot console. */ @@ -2590,11 +2565,11 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state, * keep the DTR setting that is set in uart_set_options() * We probably don't need a spinlock around this, but */ - uart_port_lock_irqsave(port, &flags); - port->mctrl &= TIOCM_DTR; - if (!(port->rs485.flags & SER_RS485_ENABLED)) - port->ops->set_mctrl(port, port->mctrl); - uart_port_unlock_irqrestore(port, flags); + scoped_guard(uart_port_lock_irqsave, port) { + port->mctrl &= TIOCM_DTR; + if (!(port->rs485.flags & SER_RS485_ENABLED)) + port->ops->set_mctrl(port, port->mctrl); + } uart_rs485_config(port); diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c index 97f8a9a52285..1f78b0db3b25 100644 --- a/drivers/tty/sysrq.c +++ b/drivers/tty/sysrq.c @@ -1133,8 +1133,7 @@ static int sysrq_sysctl_handler(const struct ctl_table *table, int write, * Behaves like do_proc_dointvec as t does not have min nor max. */ ret = proc_dointvec_minmax(&t, write, buffer, lenp, ppos); - - if (ret || !write) + if (ret) return ret; if (write) diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c index 5b4d5fb99a59..fe67c5cb0a3f 100644 --- a/drivers/tty/tty_port.c +++ b/drivers/tty/tty_port.c @@ -63,12 +63,8 @@ static void tty_port_default_lookahead_buf(struct tty_port *port, const u8 *p, static void tty_port_default_wakeup(struct tty_port *port) { - struct tty_struct *tty = tty_port_tty_get(port); - - if (tty) { - tty_wakeup(tty); - tty_kref_put(tty); - } + scoped_guard(tty_port_tty, port) + tty_wakeup(scoped_tty()); } const struct tty_port_client_operations tty_port_default_client_ops = { @@ -225,26 +221,27 @@ EXPORT_SYMBOL_GPL(tty_port_unregister_device); int tty_port_alloc_xmit_buf(struct tty_port *port) { /* We may sleep in get_zeroed_page() */ - mutex_lock(&port->buf_mutex); - if (port->xmit_buf == NULL) { - port->xmit_buf = (u8 *)get_zeroed_page(GFP_KERNEL); - if (port->xmit_buf) - kfifo_init(&port->xmit_fifo, port->xmit_buf, PAGE_SIZE); - } - mutex_unlock(&port->buf_mutex); + guard(mutex)(&port->buf_mutex); + + if (port->xmit_buf) + return 0; + + port->xmit_buf = (u8 *)get_zeroed_page(GFP_KERNEL); if (port->xmit_buf == NULL) return -ENOMEM; + + kfifo_init(&port->xmit_fifo, port->xmit_buf, PAGE_SIZE); + return 0; } EXPORT_SYMBOL(tty_port_alloc_xmit_buf); void tty_port_free_xmit_buf(struct tty_port *port) { - mutex_lock(&port->buf_mutex); + guard(mutex)(&port->buf_mutex); free_page((unsigned long)port->xmit_buf); port->xmit_buf = NULL; INIT_KFIFO(port->xmit_fifo); - mutex_unlock(&port->buf_mutex); } EXPORT_SYMBOL(tty_port_free_xmit_buf); @@ -301,13 +298,8 @@ EXPORT_SYMBOL(tty_port_put); */ struct tty_struct *tty_port_tty_get(struct tty_port *port) { - unsigned long flags; - struct tty_struct *tty; - - spin_lock_irqsave(&port->lock, flags); - tty = tty_kref_get(port->tty); - spin_unlock_irqrestore(&port->lock, flags); - return tty; + guard(spinlock_irqsave)(&port->lock); + return tty_kref_get(port->tty); } EXPORT_SYMBOL(tty_port_tty_get); @@ -321,12 +313,9 @@ EXPORT_SYMBOL(tty_port_tty_get); */ void tty_port_tty_set(struct tty_port *port, struct tty_struct *tty) { - unsigned long flags; - - spin_lock_irqsave(&port->lock, flags); + guard(spinlock_irqsave)(&port->lock); tty_kref_put(port->tty); port->tty = tty_kref_get(tty); - spin_unlock_irqrestore(&port->lock, flags); } EXPORT_SYMBOL(tty_port_tty_set); @@ -342,24 +331,24 @@ EXPORT_SYMBOL(tty_port_tty_set); */ static void tty_port_shutdown(struct tty_port *port, struct tty_struct *tty) { - mutex_lock(&port->mutex); + guard(mutex)(&port->mutex); + if (port->console) - goto out; + return; - if (tty_port_initialized(port)) { - tty_port_set_initialized(port, false); - /* - * Drop DTR/RTS if HUPCL is set. This causes any attached - * modem to hang up the line. - */ - if (tty && C_HUPCL(tty)) - tty_port_lower_dtr_rts(port); + if (!tty_port_initialized(port)) + return; - if (port->ops->shutdown) - port->ops->shutdown(port); - } -out: - mutex_unlock(&port->mutex); + tty_port_set_initialized(port, false); + /* + * Drop DTR/RTS if HUPCL is set. This causes any attached + * modem to hang up the line. + */ + if (tty && C_HUPCL(tty)) + tty_port_lower_dtr_rts(port); + + if (port->ops->shutdown) + port->ops->shutdown(port); } /** @@ -374,15 +363,15 @@ out: void tty_port_hangup(struct tty_port *port) { struct tty_struct *tty; - unsigned long flags; - spin_lock_irqsave(&port->lock, flags); - port->count = 0; - tty = port->tty; - if (tty) - set_bit(TTY_IO_ERROR, &tty->flags); - port->tty = NULL; - spin_unlock_irqrestore(&port->lock, flags); + scoped_guard(spinlock_irqsave, &port->lock) { + port->count = 0; + tty = port->tty; + if (tty) + set_bit(TTY_IO_ERROR, &tty->flags); + port->tty = NULL; + } + tty_port_set_active(port, false); tty_port_shutdown(port, tty); tty_kref_put(tty); @@ -393,15 +382,16 @@ EXPORT_SYMBOL(tty_port_hangup); void __tty_port_tty_hangup(struct tty_port *port, bool check_clocal, bool async) { - struct tty_struct *tty = tty_port_tty_get(port); + scoped_guard(tty_port_tty, port) { + struct tty_struct *tty = scoped_tty(); - if (tty && (!check_clocal || !C_CLOCAL(tty))) { - if (async) - tty_hangup(tty); - else - tty_vhangup(tty); + if (!check_clocal || !C_CLOCAL(tty)) { + if (async) + tty_hangup(tty); + else + tty_vhangup(tty); + } } - tty_kref_put(tty); } EXPORT_SYMBOL_GPL(__tty_port_tty_hangup); @@ -490,7 +480,6 @@ int tty_port_block_til_ready(struct tty_port *port, struct tty_struct *tty, struct file *filp) { int do_clocal = 0, retval; - unsigned long flags; DEFINE_WAIT(wait); /* if non-blocking mode is set we can pass directly to open unless @@ -519,10 +508,10 @@ int tty_port_block_til_ready(struct tty_port *port, retval = 0; /* The port lock protects the port counts */ - spin_lock_irqsave(&port->lock, flags); - port->count--; - port->blocked_open++; - spin_unlock_irqrestore(&port->lock, flags); + scoped_guard(spinlock_irqsave, &port->lock) { + port->count--; + port->blocked_open++; + } while (1) { /* Indicate we are open */ @@ -561,11 +550,11 @@ int tty_port_block_til_ready(struct tty_port *port, /* Update counts. A parallel hangup will have set count to zero and * we must not mess that up further. */ - spin_lock_irqsave(&port->lock, flags); - if (!tty_hung_up_p(filp)) - port->count++; - port->blocked_open--; - spin_unlock_irqrestore(&port->lock, flags); + scoped_guard(spinlock_irqsave, &port->lock) { + if (!tty_hung_up_p(filp)) + port->count++; + port->blocked_open--; + } if (retval == 0) tty_port_set_active(port, true); return retval; @@ -604,28 +593,24 @@ static void tty_port_drain_delay(struct tty_port *port, struct tty_struct *tty) int tty_port_close_start(struct tty_port *port, struct tty_struct *tty, struct file *filp) { - unsigned long flags; - if (tty_hung_up_p(filp)) return 0; - spin_lock_irqsave(&port->lock, flags); - if (tty->count == 1 && port->count != 1) { - tty_warn(tty, "%s: tty->count = 1 port count = %d\n", __func__, - port->count); - port->count = 1; - } - if (--port->count < 0) { - tty_warn(tty, "%s: bad port count (%d)\n", __func__, - port->count); - port->count = 0; - } + scoped_guard(spinlock_irqsave, &port->lock) { + if (tty->count == 1 && port->count != 1) { + tty_warn(tty, "%s: tty->count = 1 port count = %d\n", __func__, + port->count); + port->count = 1; + } + if (--port->count < 0) { + tty_warn(tty, "%s: bad port count (%d)\n", __func__, + port->count); + port->count = 0; + } - if (port->count) { - spin_unlock_irqrestore(&port->lock, flags); - return 0; + if (port->count) + return 0; } - spin_unlock_irqrestore(&port->lock, flags); tty->closing = 1; @@ -744,9 +729,8 @@ EXPORT_SYMBOL_GPL(tty_port_install); int tty_port_open(struct tty_port *port, struct tty_struct *tty, struct file *filp) { - spin_lock_irq(&port->lock); - ++port->count; - spin_unlock_irq(&port->lock); + scoped_guard(spinlock_irq, &port->lock) + ++port->count; tty_port_tty_set(port, tty); /* @@ -755,21 +739,17 @@ int tty_port_open(struct tty_port *port, struct tty_struct *tty, * port mutex. */ - mutex_lock(&port->mutex); - - if (!tty_port_initialized(port)) { + scoped_guard(mutex, &port->mutex) { + if (tty_port_initialized(port)) + break; clear_bit(TTY_IO_ERROR, &tty->flags); if (port->ops->activate) { int retval = port->ops->activate(port, tty); - - if (retval) { - mutex_unlock(&port->mutex); + if (retval) return retval; - } } tty_port_set_initialized(port, true); } - mutex_unlock(&port->mutex); return tty_port_block_til_ready(port, tty, filp); } EXPORT_SYMBOL(tty_port_open); diff --git a/drivers/tty/vt/consolemap.c b/drivers/tty/vt/consolemap.c index bb4bb272ebec..7a11c3f2e875 100644 --- a/drivers/tty/vt/consolemap.c +++ b/drivers/tty/vt/consolemap.c @@ -361,10 +361,10 @@ int con_set_trans_old(unsigned char __user * arg) inbuf[i] = UNI_DIRECT_BASE | ch; } - console_lock(); + guard(console_lock)(); memcpy(translations[USER_MAP], inbuf, sizeof(inbuf)); update_user_maps(); - console_unlock(); + return 0; } @@ -374,13 +374,11 @@ int con_get_trans_old(unsigned char __user * arg) unsigned short *p = translations[USER_MAP]; unsigned char outbuf[E_TABSZ]; - console_lock(); - for (i = 0; i < ARRAY_SIZE(outbuf); i++) - { - ch = conv_uni_to_pc(vc_cons[fg_console].d, p[i]); - outbuf[i] = (ch & ~0xff) ? 0 : ch; - } - console_unlock(); + scoped_guard(console_lock) + for (i = 0; i < ARRAY_SIZE(outbuf); i++) { + ch = conv_uni_to_pc(vc_cons[fg_console].d, p[i]); + outbuf[i] = (ch & ~0xff) ? 0 : ch; + } return copy_to_user(arg, outbuf, sizeof(outbuf)) ? -EFAULT : 0; } @@ -392,10 +390,10 @@ int con_set_trans_new(ushort __user * arg) if (copy_from_user(inbuf, arg, sizeof(inbuf))) return -EFAULT; - console_lock(); + guard(console_lock)(); memcpy(translations[USER_MAP], inbuf, sizeof(inbuf)); update_user_maps(); - console_unlock(); + return 0; } @@ -403,9 +401,8 @@ int con_get_trans_new(ushort __user * arg) { unsigned short outbuf[E_TABSZ]; - console_lock(); - memcpy(outbuf, translations[USER_MAP], sizeof(outbuf)); - console_unlock(); + scoped_guard(console_lock) + memcpy(outbuf, translations[USER_MAP], sizeof(outbuf)); return copy_to_user(arg, outbuf, sizeof(outbuf)) ? -EFAULT : 0; } @@ -571,11 +568,8 @@ static int con_do_clear_unimap(struct vc_data *vc) int con_clear_unimap(struct vc_data *vc) { - int ret; - console_lock(); - ret = con_do_clear_unimap(vc); - console_unlock(); - return ret; + guard(console_lock)(); + return con_do_clear_unimap(vc); } static struct uni_pagedict *con_unshare_unimap(struct vc_data *vc, @@ -637,32 +631,28 @@ static struct uni_pagedict *con_unshare_unimap(struct vc_data *vc, int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list) { - int err = 0, err1; struct uni_pagedict *dict; - struct unipair *unilist, *plist; + struct unipair *plist; + int err = 0; if (!ct) return 0; - unilist = vmemdup_array_user(list, ct, sizeof(*unilist)); + struct unipair *unilist __free(kvfree) = vmemdup_array_user(list, ct, sizeof(*unilist)); if (IS_ERR(unilist)) return PTR_ERR(unilist); - console_lock(); + guard(console_lock)(); /* Save original vc_unipagdir_loc in case we allocate a new one */ dict = *vc->uni_pagedict_loc; - if (!dict) { - err = -EINVAL; - goto out_unlock; - } + if (!dict) + return -EINVAL; if (dict->refcount > 1) { dict = con_unshare_unimap(vc, dict); - if (IS_ERR(dict)) { - err = PTR_ERR(dict); - goto out_unlock; - } + if (IS_ERR(dict)) + return PTR_ERR(dict); } else if (dict == dflt) { dflt = NULL; } @@ -671,7 +661,7 @@ int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list) * Insert user specified unicode pairs into new table. */ for (plist = unilist; ct; ct--, plist++) { - err1 = con_insert_unipair(dict, plist->unicode, plist->fontpos); + int err1 = con_insert_unipair(dict, plist->unicode, plist->fontpos); if (err1) err = err1; } @@ -680,15 +670,12 @@ int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list) * Merge with fontmaps of any other virtual consoles. */ if (con_unify_unimap(vc, dict)) - goto out_unlock; + return err; for (enum translation_map m = FIRST_MAP; m <= LAST_MAP; m++) set_inverse_transl(vc, dict, m); set_inverse_trans_unicode(dict); -out_unlock: - console_unlock(); - kvfree(unilist); return err; } @@ -787,50 +774,49 @@ int con_get_unimap(struct vc_data *vc, ushort ct, ushort __user *uct, { ushort ect; struct uni_pagedict *dict; - struct unipair *unilist; unsigned int d, r, g; - int ret = 0; - unilist = kvmalloc_array(ct, sizeof(*unilist), GFP_KERNEL); + struct unipair *unilist __free(kvfree) = kvmalloc_array(ct, sizeof(*unilist), GFP_KERNEL); if (!unilist) return -ENOMEM; - console_lock(); - - ect = 0; - dict = *vc->uni_pagedict_loc; - if (!dict) - goto unlock; - - for (d = 0; d < UNI_DIRS; d++) { - u16 **dir = dict->uni_pgdir[d]; - if (!dir) - continue; + scoped_guard(console_lock) { + ect = 0; + dict = *vc->uni_pagedict_loc; + if (!dict) + break; - for (r = 0; r < UNI_DIR_ROWS; r++) { - u16 *row = dir[r]; - if (!row) + for (d = 0; d < UNI_DIRS; d++) { + u16 **dir = dict->uni_pgdir[d]; + if (!dir) continue; - for (g = 0; g < UNI_ROW_GLYPHS; g++, row++) { - if (*row >= MAX_GLYPH) + for (r = 0; r < UNI_DIR_ROWS; r++) { + u16 *row = dir[r]; + if (!row) continue; - if (ect < ct) { - unilist[ect].unicode = UNI(d, r, g); - unilist[ect].fontpos = *row; + + for (g = 0; g < UNI_ROW_GLYPHS; g++, row++) { + if (*row >= MAX_GLYPH) + continue; + if (ect < ct) { + unilist[ect].unicode = UNI(d, r, g); + unilist[ect].fontpos = *row; + } + ect++; } - ect++; } } } -unlock: - console_unlock(); + if (copy_to_user(list, unilist, min(ect, ct) * sizeof(*unilist))) - ret = -EFAULT; + return -EFAULT; if (put_user(ect, uct)) - ret = -EFAULT; - kvfree(unilist); - return ret ? ret : (ect <= ct) ? 0 : -ENOMEM; + return -EFAULT; + if (ect > ct) + return -ENOMEM; + + return 0; } /* diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c index 24b0a53e5a79..07d3b93975d3 100644 --- a/drivers/tty/vt/selection.c +++ b/drivers/tty/vt/selection.c @@ -127,9 +127,8 @@ int sel_loadlut(u32 __user *lut) if (copy_from_user(tmplut, lut, sizeof(inwordLut))) return -EFAULT; - console_lock(); + guard(console_lock)(); memcpy(inwordLut, tmplut, sizeof(inwordLut)); - console_unlock(); return 0; } @@ -375,15 +374,9 @@ static int vc_selection(struct vc_data *vc, struct tiocl_selection *v, int set_selection_kernel(struct tiocl_selection *v, struct tty_struct *tty) { - int ret; - - mutex_lock(&vc_sel.lock); - console_lock(); - ret = vc_selection(vc_cons[fg_console].d, v, tty); - console_unlock(); - mutex_unlock(&vc_sel.lock); - - return ret; + guard(mutex)(&vc_sel.lock); + guard(console_lock)(); + return vc_selection(vc_cons[fg_console].d, v, tty); } EXPORT_SYMBOL_GPL(set_selection_kernel); @@ -409,9 +402,8 @@ int paste_selection(struct tty_struct *tty) const char *bps = bp ? bracketed_paste_start : NULL; const char *bpe = bp ? bracketed_paste_end : NULL; - console_lock(); - poke_blanked_console(); - console_unlock(); + scoped_guard(console_lock) + poke_blanked_console(); ld = tty_ldisc_ref_wait(tty); if (!ld) diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c index 79b33d998d43..c814644ef4ee 100644 --- a/drivers/tty/vt/vc_screen.c +++ b/drivers/tty/vt/vc_screen.c @@ -53,6 +53,8 @@ #define HEADER_SIZE 4u #define CON_BUF_SIZE (IS_ENABLED(CONFIG_BASE_SMALL) ? 256 : PAGE_SIZE) +DEFINE_FREE(free_page_ptr, void *, if (_T) free_page((unsigned long)_T)); + /* * Our minor space: * @@ -72,7 +74,6 @@ #define use_unicode(inode) (iminor(inode) & 64) #define use_attributes(inode) (iminor(inode) & 128) - struct vcs_poll_data { struct notifier_block notifier; unsigned int cons_num; @@ -231,15 +232,13 @@ static loff_t vcs_lseek(struct file *file, loff_t offset, int orig) struct vc_data *vc; int size; - console_lock(); - vc = vcs_vc(inode, NULL); - if (!vc) { - console_unlock(); - return -ENXIO; - } + scoped_guard(console_lock) { + vc = vcs_vc(inode, NULL); + if (!vc) + return -ENXIO; - size = vcs_size(vc, use_attributes(inode), use_unicode(inode)); - console_unlock(); + size = vcs_size(vc, use_attributes(inode), use_unicode(inode)); + } if (size < 0) return size; return fixed_size_llseek(file, offset, orig, size); @@ -369,11 +368,10 @@ vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) struct vcs_poll_data *poll; unsigned int read; ssize_t ret; - char *con_buf; loff_t pos; bool viewed, attr, uni_mode; - con_buf = (char *) __get_free_page(GFP_KERNEL); + char *con_buf __free(free_page_ptr) = (char *)__get_free_page(GFP_KERNEL); if (!con_buf) return -ENOMEM; @@ -382,17 +380,16 @@ vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) /* Select the proper current console and verify * sanity of the situation under the console lock. */ - console_lock(); + guard(console_lock)(); uni_mode = use_unicode(inode); attr = use_attributes(inode); - ret = -EINVAL; if (pos < 0) - goto unlock_out; + return -EINVAL; /* we enforce 32-bit alignment for pos and count in unicode mode */ if (uni_mode && (pos | count) & 3) - goto unlock_out; + return -EINVAL; poll = file->private_data; if (count && poll) @@ -468,10 +465,8 @@ vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) } *ppos += read; if (read) - ret = read; -unlock_out: - console_unlock(); - free_page((unsigned long) con_buf); + return read; + return ret; } @@ -591,7 +586,6 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct inode *inode = file_inode(file); struct vc_data *vc; - char *con_buf; u16 *org0, *org; unsigned int written; int size; @@ -602,7 +596,7 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) if (use_unicode(inode)) return -EOPNOTSUPP; - con_buf = (char *) __get_free_page(GFP_KERNEL); + char *con_buf __free(free_page_ptr) = (char *)__get_free_page(GFP_KERNEL); if (!con_buf) return -ENOMEM; @@ -611,22 +605,18 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) /* Select the proper current console and verify * sanity of the situation under the console lock. */ - console_lock(); + guard(console_lock)(); attr = use_attributes(inode); - ret = -ENXIO; vc = vcs_vc(inode, &viewed); if (!vc) - goto unlock_out; + return -ENXIO; size = vcs_size(vc, attr, false); - if (size < 0) { - ret = size; - goto unlock_out; - } - ret = -EINVAL; + if (size < 0) + return size; if (pos < 0 || pos > size) - goto unlock_out; + return -EINVAL; if (count > size - pos) count = size - pos; written = 0; @@ -651,8 +641,7 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) */ if (written) break; - ret = -EFAULT; - goto unlock_out; + return -EFAULT; } } @@ -664,15 +653,13 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) if (!vc) { if (written) break; - ret = -ENXIO; - goto unlock_out; + return -ENXIO; } size = vcs_size(vc, attr, false); if (size < 0) { if (written) break; - ret = size; - goto unlock_out; + return size; } if (pos >= size) break; @@ -702,9 +689,6 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) if (written) vcs_scr_updated(vc); -unlock_out: - console_unlock(); - free_page((unsigned long) con_buf); return ret; } @@ -754,17 +738,17 @@ vcs_open(struct inode *inode, struct file *filp) unsigned int currcons = console(inode); bool attr = use_attributes(inode); bool uni_mode = use_unicode(inode); - int ret = 0; /* we currently don't support attributes in unicode mode */ if (attr && uni_mode) return -EOPNOTSUPP; - console_lock(); - if(currcons && !vc_cons_allocated(currcons-1)) - ret = -ENXIO; - console_unlock(); - return ret; + guard(console_lock)(); + + if (currcons && !vc_cons_allocated(currcons - 1)) + return -ENXIO; + + return 0; } static int vcs_release(struct inode *inode, struct file *file) diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index 62049ceb34de..6e0089b85c27 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -141,6 +141,7 @@ static const struct consw *con_driver_map[MAX_NR_CONSOLES]; static int con_open(struct tty_struct *, struct file *); static void vc_init(struct vc_data *vc, int do_clear); static void gotoxy(struct vc_data *vc, int new_x, int new_y); +static void restore_cur(struct vc_data *vc); static void save_cur(struct vc_data *vc); static void reset_terminal(struct vc_data *vc, int do_clear); static void con_flush_chars(struct tty_struct *tty); @@ -1317,12 +1318,9 @@ EXPORT_SYMBOL(__vc_resize); static int vt_resize(struct tty_struct *tty, struct winsize *ws) { struct vc_data *vc = tty->driver_data; - int ret; - console_lock(); - ret = vc_do_resize(tty, vc, ws->ws_col, ws->ws_row, false); - console_unlock(); - return ret; + guard(console_lock)(); + return vc_do_resize(tty, vc, ws->ws_col, ws->ws_row, false); } struct vc_data *vc_deallocate(unsigned int currcons) @@ -1343,6 +1341,10 @@ struct vc_data *vc_deallocate(unsigned int currcons) vc_uniscr_set(vc, NULL); kfree(vc->vc_screenbuf); vc_cons[currcons].d = NULL; + if (vc->vc_saved_screen != NULL) { + kfree(vc->vc_saved_screen); + vc->vc_saved_screen = NULL; + } } return vc; } @@ -1878,6 +1880,45 @@ static int get_bracketed_paste(struct tty_struct *tty) return vc->vc_bracketed_paste; } +/* console_lock is held */ +static void enter_alt_screen(struct vc_data *vc) +{ + unsigned int size = vc->vc_rows * vc->vc_cols * 2; + + if (vc->vc_saved_screen != NULL) + return; /* Already inside an alt-screen */ + vc->vc_saved_screen = kmemdup((u16 *)vc->vc_origin, size, GFP_KERNEL); + if (vc->vc_saved_screen == NULL) + return; + vc->vc_saved_rows = vc->vc_rows; + vc->vc_saved_cols = vc->vc_cols; + save_cur(vc); + /* clear entire screen */ + csi_J(vc, CSI_J_FULL); +} + +/* console_lock is held */ +static void leave_alt_screen(struct vc_data *vc) +{ + unsigned int rows = min(vc->vc_saved_rows, vc->vc_rows); + unsigned int cols = min(vc->vc_saved_cols, vc->vc_cols); + u16 *src, *dest; + + if (vc->vc_saved_screen == NULL) + return; /* Not inside an alt-screen */ + for (unsigned int r = 0; r < rows; r++) { + src = vc->vc_saved_screen + r * vc->vc_saved_cols; + dest = ((u16 *)vc->vc_origin) + r * vc->vc_cols; + memcpy(dest, src, 2 * cols); + } + restore_cur(vc); + /* Update the entire screen */ + if (con_should_update(vc)) + do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2); + kfree(vc->vc_saved_screen); + vc->vc_saved_screen = NULL; +} + enum { CSI_DEC_hl_CURSOR_KEYS = 1, /* CKM: cursor keys send ^[Ox/^[[x */ CSI_DEC_hl_132_COLUMNS = 3, /* COLM: 80/132 mode switch */ @@ -1888,6 +1929,7 @@ enum { CSI_DEC_hl_MOUSE_X10 = 9, CSI_DEC_hl_SHOW_CURSOR = 25, /* TCEM */ CSI_DEC_hl_MOUSE_VT200 = 1000, + CSI_DEC_hl_ALT_SCREEN = 1049, CSI_DEC_hl_BRACKETED_PASTE = 2004, }; @@ -1944,6 +1986,12 @@ static void csi_DEC_hl(struct vc_data *vc, bool on_off) case CSI_DEC_hl_BRACKETED_PASTE: vc->vc_bracketed_paste = on_off; break; + case CSI_DEC_hl_ALT_SCREEN: + if (on_off) + enter_alt_screen(vc); + else + leave_alt_screen(vc); + break; } } @@ -2182,6 +2230,13 @@ static void reset_terminal(struct vc_data *vc, int do_clear) vc->vc_deccm = global_cursor_default; vc->vc_decim = 0; + if (vc->vc_saved_screen != NULL) { + kfree(vc->vc_saved_screen); + vc->vc_saved_screen = NULL; + vc->vc_saved_rows = 0; + vc->vc_saved_cols = 0; + } + vt_reset_keyboard(vc->vc_num); vc->vc_cursor_type = cur_default; @@ -3135,12 +3190,11 @@ static int do_con_write(struct tty_struct *tty, const u8 *buf, int count) if (in_interrupt()) return count; - console_lock(); + guard(console_lock)(); currcons = vc->vc_num; if (!vc_cons_allocated(currcons)) { /* could this happen? */ pr_warn_once("con_write: tty %d not allocated\n", currcons+1); - console_unlock(); return 0; } @@ -3184,7 +3238,7 @@ rescan_last_byte: con_flush(vc, &draw); console_conditional_schedule(); notify_update(vc); - console_unlock(); + return n; } @@ -3199,7 +3253,7 @@ rescan_last_byte: */ static void console_callback(struct work_struct *ignored) { - console_lock(); + guard(console_lock)(); if (want_console >= 0) { if (want_console != fg_console && @@ -3228,8 +3282,6 @@ static void console_callback(struct work_struct *ignored) blank_timer_expired = 0; } notify_update(vc_cons[fg_console].d); - - console_unlock(); } int set_console(int nr) @@ -3433,9 +3485,8 @@ int tioclinux(struct tty_struct *tty, unsigned long arg) return -EPERM; return paste_selection(tty); case TIOCL_UNBLANKSCREEN: - console_lock(); - unblank_screen(); - console_unlock(); + scoped_guard(console_lock) + unblank_screen(); break; case TIOCL_SELLOADLUT: if (!capable(CAP_SYS_ADMIN)) @@ -3451,9 +3502,8 @@ int tioclinux(struct tty_struct *tty, unsigned long arg) data = vt_get_shift_state(); return put_user(data, p); case TIOCL_GETMOUSEREPORTING: - console_lock(); /* May be overkill */ - data = mouse_reporting(); - console_unlock(); + scoped_guard(console_lock) /* May be overkill */ + data = mouse_reporting(); return put_user(data, p); case TIOCL_SETVESABLANK: return set_vesa_blanking(param); @@ -3484,15 +3534,14 @@ int tioclinux(struct tty_struct *tty, unsigned long arg) * Needs the console lock here. Note that lots of other calls * need fixing before the lock is actually useful! */ - console_lock(); - scrollfront(vc_cons[fg_console].d, lines); - console_unlock(); + scoped_guard(console_lock) + scrollfront(vc_cons[fg_console].d, lines); break; case TIOCL_BLANKSCREEN: /* until explicitly unblanked, not only poked */ - console_lock(); - ignore_poke = 1; - do_blank_screen(0); - console_unlock(); + scoped_guard(console_lock) { + ignore_poke = 1; + do_blank_screen(0); + } break; case TIOCL_BLANKEDSCREEN: return console_blanked; @@ -3582,9 +3631,8 @@ static void con_flush_chars(struct tty_struct *tty) if (in_interrupt()) /* from flush_to_ldisc */ return; - console_lock(); + guard(console_lock)(); set_cursor(vc); - console_unlock(); } /* @@ -3596,22 +3644,20 @@ static int con_install(struct tty_driver *driver, struct tty_struct *tty) struct vc_data *vc; int ret; - console_lock(); + guard(console_lock)(); ret = vc_allocate(currcons); if (ret) - goto unlock; + return ret; vc = vc_cons[currcons].d; /* Still being freed */ - if (vc->port.tty) { - ret = -ERESTARTSYS; - goto unlock; - } + if (vc->port.tty) + return -ERESTARTSYS; ret = tty_port_install(&vc->port, driver, tty); if (ret) - goto unlock; + return ret; tty->driver_data = vc; vc->port.tty = tty; @@ -3625,9 +3671,8 @@ static int con_install(struct tty_driver *driver, struct tty_struct *tty) tty->termios.c_iflag |= IUTF8; else tty->termios.c_iflag &= ~IUTF8; -unlock: - console_unlock(); - return ret; + + return 0; } static int con_open(struct tty_struct *tty, struct file *filp) @@ -3646,9 +3691,9 @@ static void con_shutdown(struct tty_struct *tty) { struct vc_data *vc = tty->driver_data; BUG_ON(vc == NULL); - console_lock(); + + guard(console_lock)(); vc->port.tty = NULL; - console_unlock(); } static void con_cleanup(struct tty_struct *tty) @@ -4137,15 +4182,13 @@ static ssize_t store_bind(struct device *dev, struct device_attribute *attr, struct con_driver *con = dev_get_drvdata(dev); int bind = simple_strtoul(buf, NULL, 0); - console_lock(); + guard(console_lock)(); if (bind) vt_bind(con); else vt_unbind(con); - console_unlock(); - return count; } @@ -4155,9 +4198,8 @@ static ssize_t show_bind(struct device *dev, struct device_attribute *attr, struct con_driver *con = dev_get_drvdata(dev); int bind; - console_lock(); - bind = con_is_bound(con->con); - console_unlock(); + scoped_guard(console_lock) + bind = con_is_bound(con->con); return sysfs_emit(buf, "%i\n", bind); } @@ -4429,7 +4471,7 @@ static void con_driver_unregister_callback(struct work_struct *ignored) { int i; - console_lock(); + guard(console_lock)(); for (i = 0; i < MAX_NR_CON_DRIVER; i++) { struct con_driver *con_driver = ®istered_con_driver[i]; @@ -4454,8 +4496,6 @@ static void con_driver_unregister_callback(struct work_struct *ignored) con_driver->first = 0; con_driver->last = 0; } - - console_unlock(); } /* @@ -4491,9 +4531,8 @@ EXPORT_SYMBOL_GPL(do_take_over_console); */ void give_up_console(const struct consw *csw) { - console_lock(); + guard(console_lock)(); do_unregister_con_driver(csw); - console_unlock(); } EXPORT_SYMBOL(give_up_console); @@ -4541,9 +4580,8 @@ static int set_vesa_blanking(u8 __user *mode_user) if (get_user(mode, mode_user)) return -EFAULT; - console_lock(); + guard(console_lock)(); vesa_blank_mode = (mode <= VESA_BLANK_MAX) ? mode : VESA_NO_BLANKING; - console_unlock(); return 0; } @@ -4729,7 +4767,7 @@ int con_set_cmap(unsigned char __user *arg) if (copy_from_user(colormap, arg, sizeof(colormap))) return -EFAULT; - console_lock(); + guard(console_lock)(); for (i = k = 0; i < 16; i++) { default_red[i] = colormap[k++]; default_grn[i] = colormap[k++]; @@ -4745,7 +4783,6 @@ int con_set_cmap(unsigned char __user *arg) } set_palette(vc_cons[i].d); } - console_unlock(); return 0; } @@ -4755,13 +4792,12 @@ int con_get_cmap(unsigned char __user *arg) int i, k; unsigned char colormap[3*16]; - console_lock(); - for (i = k = 0; i < 16; i++) { - colormap[k++] = default_red[i]; - colormap[k++] = default_grn[i]; - colormap[k++] = default_blu[i]; - } - console_unlock(); + scoped_guard(console_lock) + for (i = k = 0; i < 16; i++) { + colormap[k++] = default_red[i]; + colormap[k++] = default_grn[i]; + colormap[k++] = default_blu[i]; + } if (copy_to_user(arg, colormap, sizeof(colormap))) return -EFAULT; @@ -4801,62 +4837,54 @@ void reset_palette(struct vc_data *vc) static int con_font_get(struct vc_data *vc, struct console_font_op *op) { struct console_font font; - int rc = -EINVAL; int c; unsigned int vpitch = op->op == KD_FONT_OP_GET_TALL ? op->height : 32; if (vpitch > max_font_height) return -EINVAL; + void *font_data __free(kvfree) = NULL; if (op->data) { - font.data = kvzalloc(max_font_size, GFP_KERNEL); + font.data = font_data = kvzalloc(max_font_size, GFP_KERNEL); if (!font.data) return -ENOMEM; } else font.data = NULL; - console_lock(); - if (vc->vc_mode != KD_TEXT) - rc = -EINVAL; - else if (vc->vc_sw->con_font_get) - rc = vc->vc_sw->con_font_get(vc, &font, vpitch); - else - rc = -ENOSYS; - console_unlock(); + scoped_guard(console_lock) { + if (vc->vc_mode != KD_TEXT) + return -EINVAL; + if (!vc->vc_sw->con_font_get) + return -ENOSYS; - if (rc) - goto out; + int ret = vc->vc_sw->con_font_get(vc, &font, vpitch); + if (ret) + return ret; + } c = (font.width+7)/8 * vpitch * font.charcount; if (op->data && font.charcount > op->charcount) - rc = -ENOSPC; + return -ENOSPC; if (font.width > op->width || font.height > op->height) - rc = -ENOSPC; - if (rc) - goto out; + return -ENOSPC; op->height = font.height; op->width = font.width; op->charcount = font.charcount; if (op->data && copy_to_user(op->data, font.data, c)) - rc = -EFAULT; + return -EFAULT; -out: - kvfree(font.data); - return rc; + return 0; } static int con_font_set(struct vc_data *vc, const struct console_font_op *op) { struct console_font font; - int rc = -EINVAL; int size; unsigned int vpitch = op->op == KD_FONT_OP_SET_TALL ? op->height : 32; - if (vc->vc_mode != KD_TEXT) - return -EINVAL; if (!op->data) return -EINVAL; if (op->charcount > max_font_glyphs) @@ -4870,7 +4898,7 @@ static int con_font_set(struct vc_data *vc, const struct console_font_op *op) if (size > max_font_size) return -ENOSPC; - font.data = memdup_user(op->data, size); + void *font_data __free(kfree) = font.data = memdup_user(op->data, size); if (IS_ERR(font.data)) return PTR_ERR(font.data); @@ -4878,18 +4906,17 @@ static int con_font_set(struct vc_data *vc, const struct console_font_op *op) font.width = op->width; font.height = op->height; - console_lock(); + guard(console_lock)(); + if (vc->vc_mode != KD_TEXT) - rc = -EINVAL; - else if (vc->vc_sw->con_font_set) { - if (vc_is_sel(vc)) - clear_selection(); - rc = vc->vc_sw->con_font_set(vc, &font, vpitch, op->flags); - } else - rc = -ENOSYS; - console_unlock(); - kfree(font.data); - return rc; + return -EINVAL; + if (!vc->vc_sw->con_font_set) + return -ENOSYS; + + if (vc_is_sel(vc)) + clear_selection(); + + return vc->vc_sw->con_font_set(vc, &font, vpitch, op->flags); } static int con_font_default(struct vc_data *vc, struct console_font_op *op) @@ -4897,8 +4924,6 @@ static int con_font_default(struct vc_data *vc, struct console_font_op *op) struct console_font font = {.width = op->width, .height = op->height}; char name[MAX_FONT_NAME]; char *s = name; - int rc; - if (!op->data) s = NULL; @@ -4907,23 +4932,23 @@ static int con_font_default(struct vc_data *vc, struct console_font_op *op) else name[MAX_FONT_NAME - 1] = 0; - console_lock(); - if (vc->vc_mode != KD_TEXT) { - console_unlock(); - return -EINVAL; - } - if (vc->vc_sw->con_font_default) { + scoped_guard(console_lock) { + if (vc->vc_mode != KD_TEXT) + return -EINVAL; + if (!vc->vc_sw->con_font_default) + return -ENOSYS; + if (vc_is_sel(vc)) clear_selection(); - rc = vc->vc_sw->con_font_default(vc, &font, s); - } else - rc = -ENOSYS; - console_unlock(); - if (!rc) { - op->width = font.width; - op->height = font.height; + int ret = vc->vc_sw->con_font_default(vc, &font, s); + if (ret) + return ret; } - return rc; + + op->width = font.width; + op->height = font.height; + + return 0; } int con_font_op(struct vc_data *vc, struct console_font_op *op) diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c index 61342e06970a..28993a3d0acb 100644 --- a/drivers/tty/vt/vt_ioctl.c +++ b/drivers/tty/vt/vt_ioctl.c @@ -373,15 +373,13 @@ static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd, break; } - case KDSETMODE: + case KDSETMODE: { if (!perm) return -EPERM; - console_lock(); - ret = vt_kdsetmode(vc, arg); - console_unlock(); - return ret; - + guard(console_lock)(); + return vt_kdsetmode(vc, arg); + } case KDGETMODE: return put_user(vc->vc_mode, (int __user *)arg); @@ -601,23 +599,21 @@ static int vt_setactivate(struct vt_setactivate __user *sa) vsa.console--; vsa.console = array_index_nospec(vsa.console, MAX_NR_CONSOLES); - console_lock(); - ret = vc_allocate(vsa.console); - if (ret) { - console_unlock(); - return ret; - } + scoped_guard(console_lock) { + ret = vc_allocate(vsa.console); + if (ret) + return ret; - /* - * This is safe providing we don't drop the console sem between - * vc_allocate and finishing referencing nvc. - */ - nvc = vc_cons[vsa.console].d; - nvc->vt_mode = vsa.mode; - nvc->vt_mode.frsig = 0; - put_pid(nvc->vt_pid); - nvc->vt_pid = get_pid(task_pid(current)); - console_unlock(); + /* + * This is safe providing we don't drop the console sem between + * vc_allocate and finishing referencing nvc. + */ + nvc = vc_cons[vsa.console].d; + nvc->vt_mode = vsa.mode; + nvc->vt_mode.frsig = 0; + put_pid(nvc->vt_pid); + nvc->vt_pid = get_pid(task_pid(current)); + } /* Commence switch and lock */ /* Review set_console locks */ @@ -630,19 +626,18 @@ static int vt_setactivate(struct vt_setactivate __user *sa) static int vt_disallocate(unsigned int vc_num) { struct vc_data *vc = NULL; - int ret = 0; - console_lock(); - if (vt_busy(vc_num)) - ret = -EBUSY; - else if (vc_num) - vc = vc_deallocate(vc_num); - console_unlock(); + scoped_guard(console_lock) { + if (vt_busy(vc_num)) + return -EBUSY; + if (vc_num) + vc = vc_deallocate(vc_num); + } if (vc && vc_num >= MIN_NR_CONSOLES) tty_port_put(&vc->port); - return ret; + return 0; } /* deallocate all unused consoles, but leave 0 */ @@ -651,13 +646,12 @@ static void vt_disallocate_all(void) struct vc_data *vc[MAX_NR_CONSOLES]; int i; - console_lock(); - for (i = 1; i < MAX_NR_CONSOLES; i++) - if (!vt_busy(i)) - vc[i] = vc_deallocate(i); - else - vc[i] = NULL; - console_unlock(); + scoped_guard(console_lock) + for (i = 1; i < MAX_NR_CONSOLES; i++) + if (!vt_busy(i)) + vc[i] = vc_deallocate(i); + else + vc[i] = NULL; for (i = 1; i < MAX_NR_CONSOLES; i++) { if (vc[i] && i >= MIN_NR_CONSOLES) @@ -703,7 +697,7 @@ static int vt_resizex(struct vc_data *vc, struct vt_consize __user *cs) if (!vc_cons[i].d) continue; - console_lock(); + guard(console_lock)(); vcp = vc_cons[i].d; if (vcp) { int ret; @@ -718,11 +712,9 @@ static int vt_resizex(struct vc_data *vc, struct vt_consize __user *cs) if (ret) { vcp->vc_scan_lines = save_scan_lines; vcp->vc_cell_height = save_cell_height; - console_unlock(); return ret; } } - console_unlock(); } return 0; @@ -770,7 +762,7 @@ int vt_ioctl(struct tty_struct *tty, if (tmp.mode != VT_AUTO && tmp.mode != VT_PROCESS) return -EINVAL; - console_lock(); + guard(console_lock)(); vc->vt_mode = tmp; /* the frsig is ignored, so we set it to 0 */ vc->vt_mode.frsig = 0; @@ -778,7 +770,6 @@ int vt_ioctl(struct tty_struct *tty, vc->vt_pid = get_pid(task_pid(current)); /* no switch is required -- saw@shade.msu.ru */ vc->vt_newvt = -1; - console_unlock(); break; } @@ -787,9 +778,8 @@ int vt_ioctl(struct tty_struct *tty, struct vt_mode tmp; int rc; - console_lock(); - memcpy(&tmp, &vc->vt_mode, sizeof(struct vt_mode)); - console_unlock(); + scoped_guard(console_lock) + memcpy(&tmp, &vc->vt_mode, sizeof(struct vt_mode)); rc = copy_to_user(up, &tmp, sizeof(struct vt_mode)); if (rc) @@ -811,12 +801,10 @@ int vt_ioctl(struct tty_struct *tty, return -EFAULT; state = 1; /* /dev/tty0 is always open */ - console_lock(); /* required by vt_in_use() */ - for (i = 0, mask = 2; i < MAX_NR_CONSOLES && mask; - ++i, mask <<= 1) - if (vt_in_use(i)) - state |= mask; - console_unlock(); + scoped_guard(console_lock) /* required by vt_in_use() */ + for (i = 0, mask = 2; i < MAX_NR_CONSOLES && mask; ++i, mask <<= 1) + if (vt_in_use(i)) + state |= mask; return put_user(state, &vtstat->v_state); } @@ -824,11 +812,10 @@ int vt_ioctl(struct tty_struct *tty, * Returns the first available (non-opened) console. */ case VT_OPENQRY: - console_lock(); /* required by vt_in_use() */ - for (i = 0; i < MAX_NR_CONSOLES; ++i) - if (!vt_in_use(i)) - break; - console_unlock(); + scoped_guard(console_lock) /* required by vt_in_use() */ + for (i = 0; i < MAX_NR_CONSOLES; ++i) + if (!vt_in_use(i)) + break; i = i < MAX_NR_CONSOLES ? (i+1) : -1; return put_user(i, (int __user *)arg); @@ -845,11 +832,11 @@ int vt_ioctl(struct tty_struct *tty, arg--; arg = array_index_nospec(arg, MAX_NR_CONSOLES); - console_lock(); - ret = vc_allocate(arg); - console_unlock(); - if (ret) - return ret; + scoped_guard(console_lock) { + ret = vc_allocate(arg); + if (ret) + return ret; + } set_console(arg); break; @@ -880,15 +867,13 @@ int vt_ioctl(struct tty_struct *tty, * 2: completed switch-to OK */ case VT_RELDISP: + { if (!perm) return -EPERM; - console_lock(); - ret = vt_reldisp(vc, arg); - console_unlock(); - - return ret; - + guard(console_lock)(); + return vt_reldisp(vc, arg); + } /* * Disallocate memory associated to VT (but leave VT1) @@ -917,16 +902,17 @@ int vt_ioctl(struct tty_struct *tty, get_user(cc, &vtsizes->v_cols)) return -EFAULT; - console_lock(); + guard(console_lock)(); for (i = 0; i < MAX_NR_CONSOLES; i++) { vc = vc_cons[i].d; if (vc) { /* FIXME: review v tty lock */ - __vc_resize(vc_cons[i].d, cc, ll, true); + ret = __vc_resize(vc_cons[i].d, cc, ll, true); + if (ret) + return ret; } } - console_unlock(); break; } @@ -996,20 +982,17 @@ void vc_SAK(struct work_struct *work) struct vc_data *vc; struct tty_struct *tty; - console_lock(); + guard(console_lock)(); vc = vc_con->d; - if (vc) { - /* FIXME: review tty ref counting */ - tty = vc->port.tty; - /* - * SAK should also work in all raw modes and reset - * them properly. - */ - if (tty) - __do_SAK(tty); - reset_vc(vc); - } - console_unlock(); + if (!vc) + return; + + /* FIXME: review tty ref counting */ + tty = vc->port.tty; + /* SAK should also work in all raw modes and reset them properly. */ + if (tty) + __do_SAK(tty); + reset_vc(vc); } #ifdef CONFIG_COMPAT @@ -1287,31 +1270,29 @@ int vt_move_to_console(unsigned int vt, int alloc) { int prev; - console_lock(); - /* Graphics mode - up to X */ - if (disable_vt_switch) { - console_unlock(); - return 0; - } - prev = fg_console; + scoped_guard(console_lock) { + /* Graphics mode - up to X */ + if (disable_vt_switch) + return 0; - if (alloc && vc_allocate(vt)) { - /* we can't have a free VC for now. Too bad, - * we don't want to mess the screen for now. */ - console_unlock(); - return -ENOSPC; - } + prev = fg_console; - if (set_console(vt)) { - /* - * We're unable to switch to the SUSPEND_CONSOLE. - * Let the calling function know so it can decide - * what to do. - */ - console_unlock(); - return -EIO; + if (alloc && vc_allocate(vt)) { + /* + * We can't have a free VC for now. Too bad, we don't want to mess the + * screen for now. + */ + return -ENOSPC; + } + + if (set_console(vt)) { + /* + * We're unable to switch to the SUSPEND_CONSOLE. Let the calling function + * know so it can decide what to do. + */ + return -EIO; + } } - console_unlock(); if (vt_waitactive(vt + 1)) { pr_debug("Suspend: Can't switch VCs."); return -EINTR; @@ -1328,8 +1309,7 @@ int vt_move_to_console(unsigned int vt, int alloc) */ void pm_set_vt_switch(int do_switch) { - console_lock(); + guard(console_lock)(); disable_vt_switch = !do_switch; - console_unlock(); } EXPORT_SYMBOL(pm_set_vt_switch); diff --git a/drivers/uio/uio_aec.c b/drivers/uio/uio_aec.c index 8c164e51ff9e..dafcc5f44f24 100644 --- a/drivers/uio/uio_aec.c +++ b/drivers/uio/uio_aec.c @@ -33,7 +33,7 @@ #define MAILBOX 0x0F -static struct pci_device_id ids[] = { +static const struct pci_device_id ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AEC, PCI_DEVICE_ID_AEC_VITCLTC), }, { 0, } }; diff --git a/drivers/uio/uio_cif.c b/drivers/uio/uio_cif.c index 1cc3b8b5a345..4e4b589ddef1 100644 --- a/drivers/uio/uio_cif.c +++ b/drivers/uio/uio_cif.c @@ -105,7 +105,7 @@ static void hilscher_pci_remove(struct pci_dev *dev) iounmap(info->mem[0].internal_addr); } -static struct pci_device_id hilscher_pci_ids[] = { +static const struct pci_device_id hilscher_pci_ids[] = { { .vendor = PCI_VENDOR_ID_PLX, .device = PCI_DEVICE_ID_PLX_9030, diff --git a/drivers/uio/uio_dmem_genirq.c b/drivers/uio/uio_dmem_genirq.c index 31aa75110ba5..41c18ec62a45 100644 --- a/drivers/uio/uio_dmem_genirq.c +++ b/drivers/uio/uio_dmem_genirq.c @@ -297,28 +297,6 @@ static int uio_dmem_genirq_probe(struct platform_device *pdev) return devm_uio_register_device(&pdev->dev, priv->uioinfo); } -static int uio_dmem_genirq_runtime_nop(struct device *dev) -{ - /* Runtime PM callback shared between ->runtime_suspend() - * and ->runtime_resume(). Simply returns success. - * - * In this driver pm_runtime_get_sync() and pm_runtime_put_sync() - * are used at open() and release() time. This allows the - * Runtime PM code to turn off power to the device while the - * device is unused, ie before open() and after release(). - * - * This Runtime PM callback does not need to save or restore - * any registers since user space is responsbile for hardware - * register reinitialization after open(). - */ - return 0; -} - -static const struct dev_pm_ops uio_dmem_genirq_dev_pm_ops = { - .runtime_suspend = uio_dmem_genirq_runtime_nop, - .runtime_resume = uio_dmem_genirq_runtime_nop, -}; - #ifdef CONFIG_OF static const struct of_device_id uio_of_genirq_match[] = { { /* empty for now */ }, @@ -330,7 +308,6 @@ static struct platform_driver uio_dmem_genirq = { .probe = uio_dmem_genirq_probe, .driver = { .name = DRIVER_NAME, - .pm = &uio_dmem_genirq_dev_pm_ops, .of_match_table = of_match_ptr(uio_of_genirq_match), }, }; diff --git a/drivers/uio/uio_hv_generic.c b/drivers/uio/uio_hv_generic.c index f19efad4d6f8..3f8e2e27697f 100644 --- a/drivers/uio/uio_hv_generic.c +++ b/drivers/uio/uio_hv_generic.c @@ -111,7 +111,6 @@ static void hv_uio_channel_cb(void *context) struct hv_device *hv_dev; struct hv_uio_private_data *pdata; - chan->inbound.ring_buffer->interrupt_mask = 1; virt_mb(); /* @@ -183,8 +182,6 @@ hv_uio_new_channel(struct vmbus_channel *new_sc) return; } - /* Disable interrupts on sub channel */ - new_sc->inbound.ring_buffer->interrupt_mask = 1; set_channel_read_mode(new_sc, HV_CALL_ISR); ret = hv_create_ring_sysfs(new_sc, hv_uio_ring_mmap); if (ret) { @@ -227,9 +224,7 @@ hv_uio_open(struct uio_info *info, struct inode *inode) ret = vmbus_connect_ring(dev->channel, hv_uio_channel_cb, dev->channel); - if (ret == 0) - dev->channel->inbound.ring_buffer->interrupt_mask = 1; - else + if (ret) atomic_dec(&pdata->refcnt); return ret; diff --git a/drivers/uio/uio_netx.c b/drivers/uio/uio_netx.c index a1a58802c793..18917b2ac04c 100644 --- a/drivers/uio/uio_netx.c +++ b/drivers/uio/uio_netx.c @@ -127,7 +127,7 @@ static void netx_pci_remove(struct pci_dev *dev) iounmap(info->mem[0].internal_addr); } -static struct pci_device_id netx_pci_ids[] = { +static const struct pci_device_id netx_pci_ids[] = { { .vendor = PCI_VENDOR_ID_HILSCHER, .device = PCI_DEVICE_ID_HILSCHER_NETX, diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c index 2ec7d25e8264..0a1885d1b2e3 100644 --- a/drivers/uio/uio_pdrv_genirq.c +++ b/drivers/uio/uio_pdrv_genirq.c @@ -249,34 +249,11 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev) return ret; } -static int uio_pdrv_genirq_runtime_nop(struct device *dev) -{ - /* Runtime PM callback shared between ->runtime_suspend() - * and ->runtime_resume(). Simply returns success. - * - * In this driver pm_runtime_get_sync() and pm_runtime_put_sync() - * are used at open() and release() time. This allows the - * Runtime PM code to turn off power to the device while the - * device is unused, ie before open() and after release(). - * - * This Runtime PM callback does not need to save or restore - * any registers since user space is responsbile for hardware - * register reinitialization after open(). - */ - return 0; -} - -static const struct dev_pm_ops uio_pdrv_genirq_dev_pm_ops = { - .runtime_suspend = uio_pdrv_genirq_runtime_nop, - .runtime_resume = uio_pdrv_genirq_runtime_nop, -}; - #ifdef CONFIG_OF static struct of_device_id uio_of_genirq_match[] = { { /* This is filled with module_parm */ }, { /* Sentinel */ }, }; -MODULE_DEVICE_TABLE(of, uio_of_genirq_match); module_param_string(of_id, uio_of_genirq_match[0].compatible, 128, 0); MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio"); #endif @@ -285,7 +262,6 @@ static struct platform_driver uio_pdrv_genirq = { .probe = uio_pdrv_genirq_probe, .driver = { .name = DRIVER_NAME, - .pm = &uio_pdrv_genirq_dev_pm_ops, .of_match_table = of_match_ptr(uio_of_genirq_match), }, }; diff --git a/drivers/uio/uio_sercos3.c b/drivers/uio/uio_sercos3.c index b93a5f8f4cba..12afc2fa1a0b 100644 --- a/drivers/uio/uio_sercos3.c +++ b/drivers/uio/uio_sercos3.c @@ -191,7 +191,7 @@ static void sercos3_pci_remove(struct pci_dev *dev) } } -static struct pci_device_id sercos3_pci_ids[] = { +static const struct pci_device_id sercos3_pci_ids[] = { { .vendor = PCI_VENDOR_ID_PLX, .device = PCI_DEVICE_ID_PLX_9030, diff --git a/drivers/usb/cdns3/cdns3-trace.h b/drivers/usb/cdns3/cdns3-trace.h index c4e542f1b9b7..21a6a2ce7a3d 100644 --- a/drivers/usb/cdns3/cdns3-trace.h +++ b/drivers/usb/cdns3/cdns3-trace.h @@ -283,39 +283,6 @@ TRACE_EVENT(cdns3_ep0_queue, __entry->length) ); -DECLARE_EVENT_CLASS(cdns3_stream_split_transfer_len, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req), - TP_STRUCT__entry( - __string(name, req->priv_ep->name) - __field(struct cdns3_request *, req) - __field(unsigned int, length) - __field(unsigned int, actual) - __field(unsigned int, stream_id) - ), - TP_fast_assign( - __assign_str(name); - __entry->req = req; - __entry->actual = req->request.length; - __entry->length = req->request.actual; - __entry->stream_id = req->request.stream_id; - ), - TP_printk("%s: req: %p,request length: %u actual length: %u SID: %u", - __get_str(name), __entry->req, __entry->length, - __entry->actual, __entry->stream_id) -); - -DEFINE_EVENT(cdns3_stream_split_transfer_len, cdns3_stream_transfer_split, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - -DEFINE_EVENT(cdns3_stream_split_transfer_len, - cdns3_stream_transfer_split_next_part, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - DECLARE_EVENT_CLASS(cdns3_log_aligned_request, TP_PROTO(struct cdns3_request *priv_req), TP_ARGS(priv_req), @@ -354,34 +321,6 @@ DEFINE_EVENT(cdns3_log_aligned_request, cdns3_prepare_aligned_request, TP_ARGS(req) ); -DECLARE_EVENT_CLASS(cdns3_log_map_request, - TP_PROTO(struct cdns3_request *priv_req), - TP_ARGS(priv_req), - TP_STRUCT__entry( - __string(name, priv_req->priv_ep->name) - __field(struct usb_request *, req) - __field(void *, buf) - __field(dma_addr_t, dma) - ), - TP_fast_assign( - __assign_str(name); - __entry->req = &priv_req->request; - __entry->buf = priv_req->request.buf; - __entry->dma = priv_req->request.dma; - ), - TP_printk("%s: req: %p, req buf %p, dma %p", - __get_str(name), __entry->req, __entry->buf, &__entry->dma - ) -); -DEFINE_EVENT(cdns3_log_map_request, cdns3_map_request, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); -DEFINE_EVENT(cdns3_log_map_request, cdns3_mapped_request, - TP_PROTO(struct cdns3_request *req), - TP_ARGS(req) -); - DECLARE_EVENT_CLASS(cdns3_log_trb, TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb), TP_ARGS(priv_ep, trb), diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index 55f95f41b3b4..0252560cbc80 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -1976,7 +1976,10 @@ static int __cdnsp_gadget_init(struct cdns *cdns) return 0; del_gadget: - usb_del_gadget_udc(&pdev->gadget); + usb_del_gadget(&pdev->gadget); + cdnsp_gadget_free_endpoints(pdev); + usb_put_gadget(&pdev->gadget); + goto halt_pdev; free_endpoints: cdnsp_gadget_free_endpoints(pdev); halt_pdev: @@ -1998,8 +2001,9 @@ static void cdnsp_gadget_exit(struct cdns *cdns) devm_free_irq(pdev->dev, cdns->dev_irq, pdev); pm_runtime_mark_last_busy(cdns->dev); pm_runtime_put_autosuspend(cdns->dev); - usb_del_gadget_udc(&pdev->gadget); + usb_del_gadget(&pdev->gadget); cdnsp_gadget_free_endpoints(pdev); + usb_put_gadget(&pdev->gadget); cdnsp_mem_cleanup(pdev); kfree(pdev); cdns->gadget_dev = NULL; diff --git a/drivers/usb/cdns3/cdnsp-pci.c b/drivers/usb/cdns3/cdnsp-pci.c index 8c361b8394e9..5e7b88ca8b96 100644 --- a/drivers/usb/cdns3/cdnsp-pci.c +++ b/drivers/usb/cdns3/cdnsp-pci.c @@ -85,7 +85,7 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, cdnsp = kzalloc(sizeof(*cdnsp), GFP_KERNEL); if (!cdnsp) { ret = -ENOMEM; - goto disable_pci; + goto put_pci; } } @@ -168,9 +168,6 @@ free_cdnsp: if (!pci_is_enabled(func)) kfree(cdnsp); -disable_pci: - pci_disable_device(pdev); - put_pci: pci_dev_put(func); diff --git a/drivers/usb/cdns3/cdnsp-trace.h b/drivers/usb/cdns3/cdnsp-trace.h index f2bcf77a5d0a..9b33a736c3de 100644 --- a/drivers/usb/cdns3/cdnsp-trace.h +++ b/drivers/usb/cdns3/cdnsp-trace.h @@ -178,11 +178,6 @@ DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_set_config, TP_ARGS(msg) ); -DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_halted, - TP_PROTO(char *msg), - TP_ARGS(msg) -); - DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep_halt, TP_PROTO(char *msg), TP_ARGS(msg) @@ -399,11 +394,6 @@ DEFINE_EVENT(cdnsp_log_trb, cdnsp_cmd_timeout, TP_ARGS(ring, trb) ); -DEFINE_EVENT(cdnsp_log_trb, cdnsp_defered_event, - TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb), - TP_ARGS(ring, trb) -); - DECLARE_EVENT_CLASS(cdnsp_log_pdev, TP_PROTO(struct cdnsp_device *pdev), TP_ARGS(pdev), @@ -433,16 +423,6 @@ DEFINE_EVENT(cdnsp_log_pdev, cdnsp_alloc_priv_device, TP_ARGS(vdev) ); -DEFINE_EVENT(cdnsp_log_pdev, cdnsp_free_priv_device, - TP_PROTO(struct cdnsp_device *vdev), - TP_ARGS(vdev) -); - -DEFINE_EVENT(cdnsp_log_pdev, cdnsp_setup_device, - TP_PROTO(struct cdnsp_device *vdev), - TP_ARGS(vdev) -); - DEFINE_EVENT(cdnsp_log_pdev, cdnsp_setup_addressable_priv_device, TP_PROTO(struct cdnsp_device *vdev), TP_ARGS(vdev) @@ -575,11 +555,6 @@ DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_stop_ep, TP_ARGS(ctx) ); -DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_flush_ep, - TP_PROTO(struct cdnsp_ep_ctx *ctx), - TP_ARGS(ctx) -); - DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_set_deq_ep, TP_PROTO(struct cdnsp_ep_ctx *ctx), TP_ARGS(ctx) diff --git a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c index acbefccbdb2a..a7a1d38b6bef 100644 --- a/drivers/usb/class/usblp.c +++ b/drivers/usb/class/usblp.c @@ -34,6 +34,7 @@ #include <linux/module.h> #include <linux/kernel.h> +#include <linux/minmax.h> #include <linux/sched/signal.h> #include <linux/signal.h> #include <linux/poll.h> @@ -871,7 +872,7 @@ static ssize_t usblp_read(struct file *file, char __user *buffer, size_t len, lo goto done; } - count = len < avail - usblp->readcount ? len : avail - usblp->readcount; + count = min_t(ssize_t, len, avail - usblp->readcount); if (count != 0 && copy_to_user(buffer, usblp->readbuf + usblp->readcount, count)) { count = -EFAULT; diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index ac006abd13b3..766000b4939e 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -9,6 +9,7 @@ usbcore-y += devio.o notify.o generic.o quirks.o devices.o usbcore-y += phy.o port.o usbcore-$(CONFIG_OF) += of.o +usbcore-$(CONFIG_USB_XHCI_SIDEBAND) += offload.o usbcore-$(CONFIG_USB_PCI) += hcd-pci.o usbcore-$(CONFIG_ACPI) += usb-acpi.o diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 42468bbeffd2..baf5bc844b6f 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -507,8 +507,8 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, } /* Parse a possible eUSB2 periodic endpoint companion descriptor */ - if (bcdUSB == 0x0220 && d->wMaxPacketSize == 0 && - (usb_endpoint_xfer_isoc(d) || usb_endpoint_xfer_int(d))) + if (udev->speed == USB_SPEED_HIGH && bcdUSB == 0x0220 && + !le16_to_cpu(d->wMaxPacketSize) && usb_endpoint_is_isoc_in(d)) usb_parse_eusb2_isoc_endpoint_companion(ddev, cfgno, inum, asnum, endpoint, buffer, size); diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index f441958b0ef4..d29edc7c616a 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -332,10 +332,10 @@ static int usb_probe_interface(struct device *dev) return error; if (udev->authorized == 0) { - dev_err(&intf->dev, "Device is not authorized for usage\n"); + dev_info(&intf->dev, "Device is not authorized for usage\n"); return error; } else if (intf->authorized == 0) { - dev_err(&intf->dev, "Interface %d is not authorized for usage\n", + dev_info(&intf->dev, "Interface %d is not authorized for usage\n", intf->altsetting->desc.bInterfaceNumber); return error; } @@ -1420,11 +1420,28 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) udev->state == USB_STATE_SUSPENDED) goto done; + if (msg.event == PM_EVENT_SUSPEND && usb_offload_check(udev)) { + dev_dbg(&udev->dev, "device offloaded, skip suspend.\n"); + udev->offload_at_suspend = 1; + } + /* Suspend all the interfaces and then udev itself */ if (udev->actconfig) { n = udev->actconfig->desc.bNumInterfaces; for (i = n - 1; i >= 0; --i) { intf = udev->actconfig->interface[i]; + /* + * Don't suspend interfaces with remote wakeup while + * the controller is active. This preserves pending + * interrupt urbs, allowing interrupt events to be + * handled during system suspend. + */ + if (udev->offload_at_suspend && + intf->needs_remote_wakeup) { + dev_dbg(&intf->dev, + "device offloaded, skip suspend.\n"); + continue; + } status = usb_suspend_interface(udev, intf, msg); /* Ignore errors during system sleep transitions */ @@ -1435,7 +1452,8 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) } } if (status == 0) { - status = usb_suspend_device(udev, msg); + if (!udev->offload_at_suspend) + status = usb_suspend_device(udev, msg); /* * Ignore errors from non-root-hub devices during @@ -1480,9 +1498,11 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) */ } else { udev->can_submit = 0; - for (i = 0; i < 16; ++i) { - usb_hcd_flush_endpoint(udev, udev->ep_out[i]); - usb_hcd_flush_endpoint(udev, udev->ep_in[i]); + if (!udev->offload_at_suspend) { + for (i = 0; i < 16; ++i) { + usb_hcd_flush_endpoint(udev, udev->ep_out[i]); + usb_hcd_flush_endpoint(udev, udev->ep_in[i]); + } } } @@ -1524,17 +1544,35 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg) udev->can_submit = 1; /* Resume the device */ - if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) - status = usb_resume_device(udev, msg); + if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) { + if (!udev->offload_at_suspend) + status = usb_resume_device(udev, msg); + else + dev_dbg(&udev->dev, + "device offloaded, skip resume.\n"); + } /* Resume the interfaces */ if (status == 0 && udev->actconfig) { for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { intf = udev->actconfig->interface[i]; + /* + * Interfaces with remote wakeup aren't suspended + * while the controller is active. This preserves + * pending interrupt urbs, allowing interrupt events + * to be handled during system suspend. + */ + if (udev->offload_at_suspend && + intf->needs_remote_wakeup) { + dev_dbg(&intf->dev, + "device offloaded, skip resume.\n"); + continue; + } usb_resume_interface(udev, intf, msg, udev->reset_resume); } } + udev->offload_at_suspend = 0; usb_mark_last_busy(udev); done: @@ -1723,8 +1761,6 @@ int usb_autoresume_device(struct usb_device *udev) dev_vdbg(&udev->dev, "%s: cnt %d -> %d\n", __func__, atomic_read(&udev->dev.power.usage_count), status); - if (status > 0) - status = 0; return status; } @@ -1829,8 +1865,6 @@ int usb_autopm_get_interface(struct usb_interface *intf) dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", __func__, atomic_read(&intf->dev.power.usage_count), status); - if (status > 0) - status = 0; return status; } EXPORT_SYMBOL_GPL(usb_autopm_get_interface); diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index 9c6ae5e1198b..a48994e11ef3 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -243,7 +243,7 @@ int usb_generic_driver_probe(struct usb_device *udev) * with the driver core and lets interface drivers bind to them. */ if (udev->authorized == 0) - dev_err(&udev->dev, "Device is not authorized for usage\n"); + dev_info(&udev->dev, "Device is not authorized for usage\n"); else { c = usb_choose_configuration(udev); if (c >= 0) { diff --git a/drivers/usb/core/offload.c b/drivers/usb/core/offload.c new file mode 100644 index 000000000000..7c699f1b8d2b --- /dev/null +++ b/drivers/usb/core/offload.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * offload.c - USB offload related functions + * + * Copyright (c) 2025, Google LLC. + * + * Author: Guan-Yu Lin + */ + +#include <linux/usb.h> + +#include "usb.h" + +/** + * usb_offload_get - increment the offload_usage of a USB device + * @udev: the USB device to increment its offload_usage + * + * Incrementing the offload_usage of a usb_device indicates that offload is + * enabled on this usb_device; that is, another entity is actively handling USB + * transfers. This information allows the USB driver to adjust its power + * management policy based on offload activity. + * + * Return: 0 on success. A negative error code otherwise. + */ +int usb_offload_get(struct usb_device *udev) +{ + int ret; + + usb_lock_device(udev); + if (udev->state == USB_STATE_NOTATTACHED) { + usb_unlock_device(udev); + return -ENODEV; + } + + if (udev->state == USB_STATE_SUSPENDED || + udev->offload_at_suspend) { + usb_unlock_device(udev); + return -EBUSY; + } + + /* + * offload_usage could only be modified when the device is active, since + * it will alter the suspend flow of the device. + */ + ret = usb_autoresume_device(udev); + if (ret < 0) { + usb_unlock_device(udev); + return ret; + } + + udev->offload_usage++; + usb_autosuspend_device(udev); + usb_unlock_device(udev); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_offload_get); + +/** + * usb_offload_put - drop the offload_usage of a USB device + * @udev: the USB device to drop its offload_usage + * + * The inverse operation of usb_offload_get, which drops the offload_usage of + * a USB device. This information allows the USB driver to adjust its power + * management policy based on offload activity. + * + * Return: 0 on success. A negative error code otherwise. + */ +int usb_offload_put(struct usb_device *udev) +{ + int ret; + + usb_lock_device(udev); + if (udev->state == USB_STATE_NOTATTACHED) { + usb_unlock_device(udev); + return -ENODEV; + } + + if (udev->state == USB_STATE_SUSPENDED || + udev->offload_at_suspend) { + usb_unlock_device(udev); + return -EBUSY; + } + + /* + * offload_usage could only be modified when the device is active, since + * it will alter the suspend flow of the device. + */ + ret = usb_autoresume_device(udev); + if (ret < 0) { + usb_unlock_device(udev); + return ret; + } + + /* Drop the count when it wasn't 0, ignore the operation otherwise. */ + if (udev->offload_usage) + udev->offload_usage--; + usb_autosuspend_device(udev); + usb_unlock_device(udev); + + return ret; +} +EXPORT_SYMBOL_GPL(usb_offload_put); + +/** + * usb_offload_check - check offload activities on a USB device + * @udev: the USB device to check its offload activity. + * + * Check if there are any offload activity on the USB device right now. This + * information could be used for power management or other forms of resource + * management. + * + * The caller must hold @udev's device lock. In addition, the caller should + * ensure downstream usb devices are all either suspended or marked as + * "offload_at_suspend" to ensure the correctness of the return value. + * + * Returns true on any offload activity, false otherwise. + */ +bool usb_offload_check(struct usb_device *udev) __must_hold(&udev->dev->mutex) +{ + struct usb_device *child; + bool active; + int port1; + + usb_hub_for_each_child(udev, port1, child) { + usb_lock_device(child); + active = usb_offload_check(child); + usb_unlock_device(child); + if (active) + return true; + } + + return !!udev->offload_usage; +} +EXPORT_SYMBOL_GPL(usb_offload_check); diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 7a76d5a62db1..ff8df16cca35 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -372,6 +372,7 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags) struct usb_host_endpoint *ep; int is_out; unsigned int allowed; + bool is_eusb2_isoch_double; if (!urb || !urb->complete) return -EINVAL; @@ -434,7 +435,8 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags) return -ENODEV; max = usb_endpoint_maxp(&ep->desc); - if (max <= 0) { + is_eusb2_isoch_double = usb_endpoint_is_hs_isoc_double(dev, ep); + if (!max && !is_eusb2_isoch_double) { dev_dbg(&dev->dev, "bogus endpoint ep%d%s in %s (bad maxpacket %d)\n", usb_endpoint_num(&ep->desc), is_out ? "out" : "in", @@ -467,9 +469,13 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags) max = le32_to_cpu(isoc_ep_comp->dwBytesPerInterval); } - /* "high bandwidth" mode, 1-3 packets/uframe? */ - if (dev->speed == USB_SPEED_HIGH) - max *= usb_endpoint_maxp_mult(&ep->desc); + /* High speed, 1-3 packets/uframe, max 6 for eUSB2 double bw */ + if (dev->speed == USB_SPEED_HIGH) { + if (is_eusb2_isoch_double) + max = le32_to_cpu(ep->eusb2_isoc_ep_comp.dwBytesPerInterval); + else + max *= usb_endpoint_maxp_mult(&ep->desc); + } if (urb->number_of_packets <= 0) return -EINVAL; diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index fca7735fc660..b6b0b8489523 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -670,6 +670,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, set_dev_node(&dev->dev, dev_to_node(bus->sysdev)); dev->state = USB_STATE_ATTACHED; dev->lpm_disable_count = 1; + dev->offload_usage = 0; atomic_set(&dev->urbnum, 0); INIT_LIST_HEAD(&dev->ep0.urb_list); @@ -1110,6 +1111,56 @@ void usb_free_noncoherent(struct usb_device *dev, size_t size, } EXPORT_SYMBOL_GPL(usb_free_noncoherent); +/** + * usb_endpoint_max_periodic_payload - Get maximum payload bytes per service + * interval + * @udev: The USB device + * @ep: The endpoint + * + * Returns: the maximum number of bytes isochronous or interrupt endpoint @ep + * can transfer during a service interval, or 0 for other endpoints. + */ +u32 usb_endpoint_max_periodic_payload(struct usb_device *udev, + const struct usb_host_endpoint *ep) +{ + if (!usb_endpoint_xfer_isoc(&ep->desc) && + !usb_endpoint_xfer_int(&ep->desc)) + return 0; + + switch (udev->speed) { + case USB_SPEED_SUPER_PLUS: + if (USB_SS_SSP_ISOC_COMP(ep->ss_ep_comp.bmAttributes)) + return le32_to_cpu(ep->ssp_isoc_ep_comp.dwBytesPerInterval); + fallthrough; + case USB_SPEED_SUPER: + return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval); + default: + if (usb_endpoint_is_hs_isoc_double(udev, ep)) + return le32_to_cpu(ep->eusb2_isoc_ep_comp.dwBytesPerInterval); + return usb_endpoint_maxp(&ep->desc) * usb_endpoint_maxp_mult(&ep->desc); + } +} +EXPORT_SYMBOL_GPL(usb_endpoint_max_periodic_payload); + +/** + * usb_endpoint_is_hs_isoc_double - Tell whether an endpoint uses USB 2 + * Isochronous Double IN Bandwidth + * @udev: The USB device + * @ep: The endpoint + * + * Returns: true if an endpoint @ep conforms to USB 2 Isochronous Double IN + * Bandwidth ECN, false otherwise. + */ +bool usb_endpoint_is_hs_isoc_double(struct usb_device *udev, + const struct usb_host_endpoint *ep) +{ + return ep->eusb2_isoc_ep_comp.bDescriptorType && + le16_to_cpu(udev->descriptor.bcdUSB) == 0x220 && + usb_endpoint_is_isoc_in(&ep->desc) && + !le16_to_cpu(ep->desc.wMaxPacketSize); +} +EXPORT_SYMBOL_GPL(usb_endpoint_is_hs_isoc_double); + /* * Notifications of device and interface registration */ diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index ea6bd537e337..091bfcfef753 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -1029,11 +1029,33 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) return 0; } +static int dwc2_limit_speed(struct dwc2_hsotg *hsotg) +{ + enum usb_device_speed usb_speed; + + usb_speed = usb_get_maximum_speed(hsotg->dev); + switch (usb_speed) { + case USB_SPEED_LOW: + dev_err(hsotg->dev, "Maximum speed cannot be forced to low-speed\n"); + return -EINVAL; + case USB_SPEED_FULL: + if (hsotg->params.speed == DWC2_SPEED_PARAM_LOW) + break; + hsotg->params.speed = DWC2_SPEED_PARAM_FULL; + break; + default: + break; + } + + return 0; +} + typedef void (*set_params_cb)(struct dwc2_hsotg *data); int dwc2_init_params(struct dwc2_hsotg *hsotg) { set_params_cb set_params; + int ret; dwc2_set_default_params(hsotg); dwc2_get_device_properties(hsotg); @@ -1051,6 +1073,10 @@ int dwc2_init_params(struct dwc2_hsotg *hsotg) } } + ret = dwc2_limit_speed(hsotg); + if (ret) + return ret; + dwc2_check_params(hsotg); return 0; diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 310d182e10b5..4925d15084f8 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -189,4 +189,15 @@ config USB_DWC3_RTK or dual-role mode. Say 'Y' or 'M' if you have such device. +config USB_DWC3_GENERIC_PLAT + tristate "DWC3 Generic Platform Driver" + depends on OF && COMMON_CLK + default USB_DWC3 + help + Support USB3 functionality in simple SoC integrations. + Currently supports SpacemiT DWC USB3. Platforms using + dwc3-of-simple can easily switch to dwc3-generic by flattening + the dwc3 child node in the device tree. + Say 'Y' or 'M' here if your platform integrates DWC3 in a similar way. + endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 830e6c9e5fe0..96469e48ff9d 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -57,3 +57,4 @@ obj-$(CONFIG_USB_DWC3_IMX8MP) += dwc3-imx8mp.o obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o obj-$(CONFIG_USB_DWC3_RTK) += dwc3-rtk.o +obj-$(CONFIG_USB_DWC3_GENERIC_PLAT) += dwc3-generic-plat.o diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 8002c23a5a02..ae140c356295 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -156,6 +156,7 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy) dwc3_writel(dwc->regs, DWC3_GCTL, reg); dwc->current_dr_role = mode; + trace_dwc3_set_prtcap(mode); } static void __dwc3_set_mode(struct work_struct *work) @@ -2351,6 +2352,7 @@ static int dwc3_probe(struct platform_device *pdev) return -ENOMEM; dwc->dev = &pdev->dev; + dwc->glue_ops = NULL; probe_data.dwc = dwc; probe_data.res = res; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index d5b985fa12f4..a5fc92c4ffa3 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -993,6 +993,17 @@ struct dwc3_scratchpad_array { }; /** + * struct dwc3_glue_ops - The ops indicate the notifications that + * need to be passed on to glue layer + * @pre_set_role: Notify glue of role switch notifications + * @pre_run_stop: Notify run stop enable/disable information to glue + */ +struct dwc3_glue_ops { + void (*pre_set_role)(struct dwc3 *dwc, enum usb_role role); + void (*pre_run_stop)(struct dwc3 *dwc, bool is_on); +}; + +/** * struct dwc3 - representation of our controller * @drd_work: workqueue used for role swapping * @ep0_trb: trb which is used for the ctrl_req @@ -1012,6 +1023,7 @@ struct dwc3_scratchpad_array { * @eps: endpoint array * @gadget: device side representation of the peripheral controller * @gadget_driver: pointer to the gadget driver + * @glue_ops: Vendor callbacks for flattened device implementations. * @bus_clk: clock for accessing the registers * @ref_clk: reference clock * @susp_clk: clock used when the SS phy is in low power (S3) state @@ -1197,6 +1209,8 @@ struct dwc3 { struct usb_gadget *gadget; struct usb_gadget_driver *gadget_driver; + const struct dwc3_glue_ops *glue_ops; + struct clk *bus_clk; struct clk *ref_clk; struct clk *susp_clk; @@ -1614,6 +1628,18 @@ void dwc3_event_buffers_cleanup(struct dwc3 *dwc); int dwc3_core_soft_reset(struct dwc3 *dwc); void dwc3_enable_susphy(struct dwc3 *dwc, bool enable); +static inline void dwc3_pre_set_role(struct dwc3 *dwc, enum usb_role role) +{ + if (dwc->glue_ops && dwc->glue_ops->pre_set_role) + dwc->glue_ops->pre_set_role(dwc, role); +} + +static inline void dwc3_pre_run_stop(struct dwc3 *dwc, bool is_on) +{ + if (dwc->glue_ops && dwc->glue_ops->pre_run_stop) + dwc->glue_ops->pre_run_stop(dwc, is_on); +} + #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) int dwc3_host_init(struct dwc3 *dwc); void dwc3_host_exit(struct dwc3 *dwc); diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h index 09d703852a92..6e1cdcdce7cc 100644 --- a/drivers/usb/dwc3/debug.h +++ b/drivers/usb/dwc3/debug.h @@ -14,6 +14,24 @@ #include "core.h" /** + * dwc3_mode_string - returns mode name + * @mode: GCTL.PrtCapDir value + */ +static inline const char *dwc3_mode_string(u32 mode) +{ + switch (mode) { + case DWC3_GCTL_PRTCAP_HOST: + return "host"; + case DWC3_GCTL_PRTCAP_DEVICE: + return "device"; + case DWC3_GCTL_PRTCAP_OTG: + return "otg"; + default: + return "UNKNOWN"; + } +} + +/** * dwc3_gadget_ep_cmd_string - returns endpoint command string * @cmd: command code */ diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index ebf03468fac4..d18bf5e32cc8 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -402,6 +402,7 @@ static int dwc3_mode_show(struct seq_file *s, void *unused) struct dwc3 *dwc = s->private; unsigned long flags; u32 reg; + u32 mode; int ret; ret = pm_runtime_resume_and_get(dwc->dev); @@ -412,18 +413,15 @@ static int dwc3_mode_show(struct seq_file *s, void *unused) reg = dwc3_readl(dwc->regs, DWC3_GCTL); spin_unlock_irqrestore(&dwc->lock, flags); - switch (DWC3_GCTL_PRTCAP(reg)) { + mode = DWC3_GCTL_PRTCAP(reg); + switch (mode) { case DWC3_GCTL_PRTCAP_HOST: - seq_puts(s, "host\n"); - break; case DWC3_GCTL_PRTCAP_DEVICE: - seq_puts(s, "device\n"); - break; case DWC3_GCTL_PRTCAP_OTG: - seq_puts(s, "otg\n"); + seq_printf(s, "%s\n", dwc3_mode_string(mode)); break; default: - seq_printf(s, "UNKNOWN %08x\n", DWC3_GCTL_PRTCAP(reg)); + seq_printf(s, "UNKNOWN %08x\n", mode); } pm_runtime_put_sync(dwc->dev); diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index 7977860932b1..4c91240eb429 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -464,6 +464,7 @@ static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, break; } + dwc3_pre_set_role(dwc, role); dwc3_set_mode(dwc, mode); return 0; } diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c new file mode 100644 index 000000000000..d96b20570002 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dwc3-generic-plat.c - DesignWare USB3 generic platform driver + * + * Copyright (C) 2025 Ze Huang <huang.ze@linux.dev> + * + * Inspired by dwc3-qcom.c and dwc3-of-simple.c + */ + +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include "glue.h" + +struct dwc3_generic { + struct device *dev; + struct dwc3 dwc; + struct clk_bulk_data *clks; + int num_clocks; + struct reset_control *resets; +}; + +#define to_dwc3_generic(d) container_of((d), struct dwc3_generic, dwc) + +static void dwc3_generic_reset_control_assert(void *data) +{ + reset_control_assert(data); +} + +static int dwc3_generic_probe(struct platform_device *pdev) +{ + struct dwc3_probe_data probe_data = {}; + struct device *dev = &pdev->dev; + struct dwc3_generic *dwc3g; + struct resource *res; + int ret; + + dwc3g = devm_kzalloc(dev, sizeof(*dwc3g), GFP_KERNEL); + if (!dwc3g) + return -ENOMEM; + + dwc3g->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + dwc3g->resets = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(dwc3g->resets)) + return dev_err_probe(dev, PTR_ERR(dwc3g->resets), "failed to get resets\n"); + + ret = reset_control_assert(dwc3g->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to assert resets\n"); + + /* Not strict timing, just for safety */ + udelay(2); + + ret = reset_control_deassert(dwc3g->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to deassert resets\n"); + + ret = devm_add_action_or_reset(dev, dwc3_generic_reset_control_assert, dwc3g->resets); + if (ret) + return ret; + + ret = devm_clk_bulk_get_all_enabled(dwc3g->dev, &dwc3g->clks); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get clocks\n"); + + dwc3g->num_clocks = ret; + dwc3g->dwc.dev = dev; + probe_data.dwc = &dwc3g->dwc; + probe_data.res = res; + probe_data.ignore_clocks_and_resets = true; + ret = dwc3_core_probe(&probe_data); + if (ret) + return dev_err_probe(dev, ret, "failed to register DWC3 Core\n"); + + return 0; +} + +static void dwc3_generic_remove(struct platform_device *pdev) +{ + struct dwc3 *dwc = platform_get_drvdata(pdev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + + dwc3_core_remove(dwc); + + clk_bulk_disable_unprepare(dwc3g->num_clocks, dwc3g->clks); +} + +static int dwc3_generic_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + int ret; + + ret = dwc3_pm_suspend(dwc); + if (ret) + return ret; + + clk_bulk_disable_unprepare(dwc3g->num_clocks, dwc3g->clks); + + return 0; +} + +static int dwc3_generic_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + int ret; + + ret = clk_bulk_prepare_enable(dwc3g->num_clocks, dwc3g->clks); + if (ret) + return ret; + + ret = dwc3_pm_resume(dwc); + if (ret) + return ret; + + return 0; +} + +static int dwc3_generic_runtime_suspend(struct device *dev) +{ + return dwc3_runtime_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_generic_runtime_resume(struct device *dev) +{ + return dwc3_runtime_resume(dev_get_drvdata(dev)); +} + +static int dwc3_generic_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} + +static const struct dev_pm_ops dwc3_generic_dev_pm_ops = { + SYSTEM_SLEEP_PM_OPS(dwc3_generic_suspend, dwc3_generic_resume) + RUNTIME_PM_OPS(dwc3_generic_runtime_suspend, dwc3_generic_runtime_resume, + dwc3_generic_runtime_idle) +}; + +static const struct of_device_id dwc3_generic_of_match[] = { + { .compatible = "spacemit,k1-dwc3", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dwc3_generic_of_match); + +static struct platform_driver dwc3_generic_driver = { + .probe = dwc3_generic_probe, + .remove = dwc3_generic_remove, + .driver = { + .name = "dwc3-generic-plat", + .of_match_table = dwc3_generic_of_match, + .pm = pm_ptr(&dwc3_generic_dev_pm_ops), + }, +}; +module_platform_driver(dwc3_generic_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DesignWare USB3 generic platform driver"); diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index ca7e1c02773a..ded2ca86670c 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -11,7 +11,6 @@ #include <linux/of_clk.h> #include <linux/module.h> #include <linux/kernel.h> -#include <linux/extcon.h> #include <linux/interconnect.h> #include <linux/platform_device.h> #include <linux/phy/phy.h> @@ -79,16 +78,13 @@ struct dwc3_qcom { struct dwc3_qcom_port ports[DWC3_QCOM_MAX_PORTS]; u8 num_ports; - struct extcon_dev *edev; - struct extcon_dev *host_edev; - struct notifier_block vbus_nb; - struct notifier_block host_nb; - enum usb_dr_mode mode; bool is_suspended; bool pm_suspended; struct icc_path *icc_path_ddr; struct icc_path *icc_path_apps; + + enum usb_role current_role; }; #define to_dwc3_qcom(d) container_of((d), struct dwc3_qcom, dwc) @@ -117,11 +113,6 @@ static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val) readl(base + offset); } -/* - * TODO: Make the in-core role switching code invoke dwc3_qcom_vbus_override_enable(), - * validate that the in-core extcon support is functional, and drop extcon - * handling from the glue - */ static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable) { if (enable) { @@ -137,80 +128,6 @@ static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable) } } -static int dwc3_qcom_vbus_notifier(struct notifier_block *nb, - unsigned long event, void *ptr) -{ - struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, vbus_nb); - - /* enable vbus override for device mode */ - dwc3_qcom_vbus_override_enable(qcom, event); - qcom->mode = event ? USB_DR_MODE_PERIPHERAL : USB_DR_MODE_HOST; - - return NOTIFY_DONE; -} - -static int dwc3_qcom_host_notifier(struct notifier_block *nb, - unsigned long event, void *ptr) -{ - struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, host_nb); - - /* disable vbus override in host mode */ - dwc3_qcom_vbus_override_enable(qcom, !event); - qcom->mode = event ? USB_DR_MODE_HOST : USB_DR_MODE_PERIPHERAL; - - return NOTIFY_DONE; -} - -static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom) -{ - struct device *dev = qcom->dev; - struct extcon_dev *host_edev; - int ret; - - if (!of_property_present(dev->of_node, "extcon")) - return 0; - - qcom->edev = extcon_get_edev_by_phandle(dev, 0); - if (IS_ERR(qcom->edev)) - return dev_err_probe(dev, PTR_ERR(qcom->edev), - "Failed to get extcon\n"); - - qcom->vbus_nb.notifier_call = dwc3_qcom_vbus_notifier; - - qcom->host_edev = extcon_get_edev_by_phandle(dev, 1); - if (IS_ERR(qcom->host_edev)) - qcom->host_edev = NULL; - - ret = devm_extcon_register_notifier(dev, qcom->edev, EXTCON_USB, - &qcom->vbus_nb); - if (ret < 0) { - dev_err(dev, "VBUS notifier register failed\n"); - return ret; - } - - if (qcom->host_edev) - host_edev = qcom->host_edev; - else - host_edev = qcom->edev; - - qcom->host_nb.notifier_call = dwc3_qcom_host_notifier; - ret = devm_extcon_register_notifier(dev, host_edev, EXTCON_USB_HOST, - &qcom->host_nb); - if (ret < 0) { - dev_err(dev, "Host notifier register failed\n"); - return ret; - } - - /* Update initial VBUS override based on extcon state */ - if (extcon_get_state(qcom->edev, EXTCON_USB) || - !extcon_get_state(host_edev, EXTCON_USB_HOST)) - dwc3_qcom_vbus_notifier(&qcom->vbus_nb, true, qcom->edev); - else - dwc3_qcom_vbus_notifier(&qcom->vbus_nb, false, qcom->edev); - - return 0; -} - static int dwc3_qcom_interconnect_enable(struct dwc3_qcom *qcom) { int ret; @@ -641,6 +558,55 @@ static int dwc3_qcom_setup_irq(struct dwc3_qcom *qcom, struct platform_device *p return 0; } +static void dwc3_qcom_set_role_notifier(struct dwc3 *dwc, enum usb_role next_role) +{ + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + + if (qcom->current_role == next_role) + return; + + if (pm_runtime_resume_and_get(qcom->dev)) { + dev_dbg(qcom->dev, "Failed to resume device\n"); + return; + } + + if (qcom->current_role == USB_ROLE_DEVICE) + dwc3_qcom_vbus_override_enable(qcom, false); + else if (qcom->current_role != USB_ROLE_DEVICE) + dwc3_qcom_vbus_override_enable(qcom, true); + + pm_runtime_mark_last_busy(qcom->dev); + pm_runtime_put_sync(qcom->dev); + + /* + * Current role changes via usb_role_switch_set_role callback protected + * internally by mutex lock. + */ + qcom->current_role = next_role; +} + +static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on) +{ + struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); + + /* + * When autosuspend is enabled and controller goes to suspend + * after removing UDC from userspace, the next UDC write needs + * setting of QSCRATCH VBUS_VALID to "1" to generate a connect + * done event. + */ + if (!is_on) + return; + + dwc3_qcom_vbus_override_enable(qcom, true); + pm_runtime_mark_last_busy(qcom->dev); +} + +struct dwc3_glue_ops dwc3_qcom_glue_ops = { + .pre_set_role = dwc3_qcom_set_role_notifier, + .pre_run_stop = dwc3_qcom_run_stop_notifier, +}; + static int dwc3_qcom_probe(struct platform_device *pdev) { struct dwc3_probe_data probe_data = {}; @@ -717,6 +683,23 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (ignore_pipe_clk) dwc3_qcom_select_utmi_clk(qcom); + qcom->mode = usb_get_dr_mode(dev); + + if (qcom->mode == USB_DR_MODE_HOST) { + qcom->current_role = USB_ROLE_HOST; + } else if (qcom->mode == USB_DR_MODE_PERIPHERAL) { + qcom->current_role = USB_ROLE_DEVICE; + dwc3_qcom_vbus_override_enable(qcom, true); + } else { + if ((device_property_read_bool(dev, "usb-role-switch")) && + (usb_get_role_switch_default_mode(dev) == USB_DR_MODE_HOST)) + qcom->current_role = USB_ROLE_HOST; + else + qcom->current_role = USB_ROLE_DEVICE; + } + + qcom->dwc.glue_ops = &dwc3_qcom_glue_ops; + qcom->dwc.dev = dev; probe_data.dwc = &qcom->dwc; probe_data.res = &res; @@ -731,17 +714,6 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (ret) goto remove_core; - qcom->mode = usb_get_dr_mode(dev); - - /* enable vbus override for device mode */ - if (qcom->mode != USB_DR_MODE_HOST) - dwc3_qcom_vbus_override_enable(qcom, true); - - /* register extcon to override sw_vbus on Vbus change later */ - ret = dwc3_qcom_register_extcon(qcom); - if (ret) - goto interconnect_exit; - wakeup_source = of_property_read_bool(dev->of_node, "wakeup-source"); device_init_wakeup(&pdev->dev, wakeup_source); @@ -749,8 +721,6 @@ static int dwc3_qcom_probe(struct platform_device *pdev) return 0; -interconnect_exit: - dwc3_qcom_interconnect_exit(qcom); remove_core: dwc3_core_remove(&qcom->dwc); clk_disable: @@ -764,11 +734,14 @@ static void dwc3_qcom_remove(struct platform_device *pdev) struct dwc3 *dwc = platform_get_drvdata(pdev); struct dwc3_qcom *qcom = to_dwc3_qcom(dwc); - dwc3_core_remove(&qcom->dwc); + if (pm_runtime_resume_and_get(qcom->dev) < 0) + return; + dwc3_core_remove(&qcom->dwc); clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks); - dwc3_qcom_interconnect_exit(qcom); + + pm_runtime_put_noidle(qcom->dev); } static int dwc3_qcom_pm_suspend(struct device *dev) @@ -873,6 +846,7 @@ MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match); static struct platform_driver dwc3_qcom_driver = { .probe = dwc3_qcom_probe, .remove = dwc3_qcom_remove, + .shutdown = dwc3_qcom_remove, .driver = { .name = "dwc3-qcom", .pm = pm_ptr(&dwc3_qcom_dev_pm_ops), diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 554f997eb8c4..6f18b4840a25 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2662,6 +2662,7 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on) dwc->pullups_connected = false; } + dwc3_pre_run_stop(dwc, is_on); dwc3_gadget_dctl_write_safe(dwc, reg); do { diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index bdeb1aaf65d8..b6ba984bafcd 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -19,6 +19,23 @@ #include "core.h" #include "debug.h" +DECLARE_EVENT_CLASS(dwc3_log_set_prtcap, + TP_PROTO(u32 mode), + TP_ARGS(mode), + TP_STRUCT__entry( + __field(u32, mode) + ), + TP_fast_assign( + __entry->mode = mode; + ), + TP_printk("mode %s", dwc3_mode_string(__entry->mode)) +); + +DEFINE_EVENT(dwc3_log_set_prtcap, dwc3_set_prtcap, + TP_PROTO(u32 mode), + TP_ARGS(mode) +); + DECLARE_EVENT_CLASS(dwc3_log_io, TP_PROTO(void *base, u32 offset, u32 value), TP_ARGS(base, offset, value), diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index f94ea196ce54..6bcac85c5550 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -1750,6 +1750,8 @@ static int configfs_composite_bind(struct usb_gadget *gadget, cdev->use_os_string = true; cdev->b_vendor_code = gi->b_vendor_code; memcpy(cdev->qw_sign, gi->qw_sign, OS_STRING_QW_SIGN_LEN); + } else { + cdev->use_os_string = false; } if (gadget_is_otg(gadget) && !otg_desc[0]) { diff --git a/drivers/usb/gadget/function/f_acm.c b/drivers/usb/gadget/function/f_acm.c index 7061720b9732..106046e17c4e 100644 --- a/drivers/usb/gadget/function/f_acm.c +++ b/drivers/usb/gadget/function/f_acm.c @@ -11,12 +11,15 @@ /* #define VERBOSE_DEBUG */ +#include <linux/cleanup.h> #include <linux/slab.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/device.h> #include <linux/err.h> +#include <linux/usb/gadget.h> + #include "u_serial.h" @@ -613,6 +616,7 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) struct usb_string *us; int status; struct usb_ep *ep; + struct usb_request *request __free(free_usb_request) = NULL; /* REVISIT might want instance-specific strings to help * distinguish instances ... @@ -630,7 +634,7 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs, and patch descriptors */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; acm->ctrl_id = status; acm_iad_descriptor.bFirstInterface = status; @@ -639,43 +643,41 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; acm->data_id = status; acm_data_interface_desc.bInterfaceNumber = status; acm_union_desc.bSlaveInterface0 = status; acm_call_mgmt_descriptor.bDataInterface = status; - status = -ENODEV; - /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc); if (!ep) - goto fail; + return -ENODEV; acm->port.in = ep; ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc); if (!ep) - goto fail; + return -ENODEV; acm->port.out = ep; ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc); if (!ep) - goto fail; + return -ENODEV; acm->notify = ep; acm_iad_descriptor.bFunctionProtocol = acm->bInterfaceProtocol; acm_control_interface_desc.bInterfaceProtocol = acm->bInterfaceProtocol; /* allocate notification */ - acm->notify_req = gs_alloc_req(ep, - sizeof(struct usb_cdc_notification) + 2, - GFP_KERNEL); - if (!acm->notify_req) - goto fail; + request = gs_alloc_req(ep, + sizeof(struct usb_cdc_notification) + 2, + GFP_KERNEL); + if (!request) + return -ENODEV; - acm->notify_req->complete = acm_cdc_notify_complete; - acm->notify_req->context = acm; + request->complete = acm_cdc_notify_complete; + request->context = acm; /* support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at @@ -692,7 +694,9 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, acm_fs_function, acm_hs_function, acm_ss_function, acm_ss_function); if (status) - goto fail; + return status; + + acm->notify_req = no_free_ptr(request); dev_dbg(&cdev->gadget->dev, "acm ttyGS%d: IN/%s OUT/%s NOTIFY/%s\n", @@ -700,14 +704,6 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) acm->port.in->name, acm->port.out->name, acm->notify->name); return 0; - -fail: - if (acm->notify_req) - gs_free_req(acm->notify, acm->notify_req); - - ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status); - - return status; } static void acm_unbind(struct usb_configuration *c, struct usb_function *f) diff --git a/drivers/usb/gadget/function/f_ecm.c b/drivers/usb/gadget/function/f_ecm.c index 027226325039..675d2bc538a4 100644 --- a/drivers/usb/gadget/function/f_ecm.c +++ b/drivers/usb/gadget/function/f_ecm.c @@ -8,6 +8,7 @@ /* #define VERBOSE_DEBUG */ +#include <linux/cleanup.h> #include <linux/slab.h> #include <linux/kernel.h> #include <linux/module.h> @@ -15,6 +16,8 @@ #include <linux/etherdevice.h> #include <linux/string_choices.h> +#include <linux/usb/gadget.h> + #include "u_ether.h" #include "u_ether_configfs.h" #include "u_ecm.h" @@ -678,6 +681,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) struct usb_ep *ep; struct f_ecm_opts *ecm_opts; + struct usb_request *request __free(free_usb_request) = NULL; if (!can_support_ecm(cdev->gadget)) return -EINVAL; @@ -711,7 +715,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; ecm->ctrl_id = status; ecm_iad_descriptor.bFirstInterface = status; @@ -720,24 +724,22 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; ecm->data_id = status; ecm_data_nop_intf.bInterfaceNumber = status; ecm_data_intf.bInterfaceNumber = status; ecm_union_desc.bSlaveInterface0 = status; - status = -ENODEV; - /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_in_desc); if (!ep) - goto fail; + return -ENODEV; ecm->port.in_ep = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_out_desc); if (!ep) - goto fail; + return -ENODEV; ecm->port.out_ep = ep; /* NOTE: a status/notification endpoint is *OPTIONAL* but we @@ -746,20 +748,18 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) */ ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_notify_desc); if (!ep) - goto fail; + return -ENODEV; ecm->notify = ep; - status = -ENOMEM; - /* allocate notification request and buffer */ - ecm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); - if (!ecm->notify_req) - goto fail; - ecm->notify_req->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL); - if (!ecm->notify_req->buf) - goto fail; - ecm->notify_req->context = ecm; - ecm->notify_req->complete = ecm_notify_complete; + request = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL); + if (!request->buf) + return -ENOMEM; + request->context = ecm; + request->complete = ecm_notify_complete; /* support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at @@ -778,7 +778,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, ecm_fs_function, ecm_hs_function, ecm_ss_function, ecm_ss_function); if (status) - goto fail; + return status; /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code @@ -788,20 +788,12 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) ecm->port.open = ecm_open; ecm->port.close = ecm_close; + ecm->notify_req = no_free_ptr(request); + DBG(cdev, "CDC Ethernet: IN/%s OUT/%s NOTIFY/%s\n", ecm->port.in_ep->name, ecm->port.out_ep->name, ecm->notify->name); return 0; - -fail: - if (ecm->notify_req) { - kfree(ecm->notify_req->buf); - usb_ep_free_request(ecm->notify, ecm->notify_req); - } - - ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); - - return status; } static inline struct f_ecm_opts *to_f_ecm_opts(struct config_item *item) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 5246fa6af3d6..47cfbe41fdff 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -2407,7 +2407,12 @@ static int ffs_func_eps_enable(struct ffs_function *func) ep = func->eps; epfile = ffs->epfiles; count = ffs->eps_count; - while(count--) { + if (!epfile) { + ret = -ENOMEM; + goto done; + } + + while (count--) { ep->ep->driver_data = ep; ret = config_ep_by_speed(func->gadget, &func->function, ep->ep); @@ -2431,6 +2436,7 @@ static int ffs_func_eps_enable(struct ffs_function *func) } wake_up_interruptible(&ffs->wait); +done: spin_unlock_irqrestore(&func->ffs->eps_lock, flags); return ret; diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index 8e1d1e884050..307ea563af95 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -511,7 +511,7 @@ try_again: } req->status = 0; - req->zero = 0; + req->zero = 1; req->length = count; req->complete = f_hidg_req_complete; req->context = hidg; @@ -967,7 +967,7 @@ stall: return -EOPNOTSUPP; respond: - req->zero = 0; + req->zero = 1; req->length = length; status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); if (status < 0) diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index 58b0dd575af3..0e38330271d5 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -11,6 +11,7 @@ * Copyright (C) 2008 Nokia Corporation */ +#include <linux/cleanup.h> #include <linux/kernel.h> #include <linux/interrupt.h> #include <linux/module.h> @@ -20,6 +21,7 @@ #include <linux/string_choices.h> #include <linux/usb/cdc.h> +#include <linux/usb/gadget.h> #include "u_ether.h" #include "u_ether_configfs.h" @@ -1436,18 +1438,18 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) struct usb_ep *ep; struct f_ncm_opts *ncm_opts; + struct usb_os_desc_table *os_desc_table __free(kfree) = NULL; + struct usb_request *request __free(free_usb_request) = NULL; + if (!can_support_ecm(cdev->gadget)) return -EINVAL; ncm_opts = container_of(f->fi, struct f_ncm_opts, func_inst); if (cdev->use_os_string) { - f->os_desc_table = kzalloc(sizeof(*f->os_desc_table), - GFP_KERNEL); - if (!f->os_desc_table) + os_desc_table = kzalloc(sizeof(*os_desc_table), GFP_KERNEL); + if (!os_desc_table) return -ENOMEM; - f->os_desc_n = 1; - f->os_desc_table[0].os_desc = &ncm_opts->ncm_os_desc; } mutex_lock(&ncm_opts->lock); @@ -1459,16 +1461,17 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) mutex_unlock(&ncm_opts->lock); if (status) - goto fail; + return status; ncm_opts->bound = true; + ncm_string_defs[1].s = ncm->ethaddr; + us = usb_gstrings_attach(cdev, ncm_strings, ARRAY_SIZE(ncm_string_defs)); - if (IS_ERR(us)) { - status = PTR_ERR(us); - goto fail; - } + if (IS_ERR(us)) + return PTR_ERR(us); + ncm_control_intf.iInterface = us[STRING_CTRL_IDX].id; ncm_data_nop_intf.iInterface = us[STRING_DATA_IDX].id; ncm_data_intf.iInterface = us[STRING_DATA_IDX].id; @@ -1478,20 +1481,16 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; ncm->ctrl_id = status; ncm_iad_desc.bFirstInterface = status; ncm_control_intf.bInterfaceNumber = status; ncm_union_desc.bMasterInterface0 = status; - if (cdev->use_os_string) - f->os_desc_table[0].if_id = - ncm_iad_desc.bFirstInterface; - status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; ncm->data_id = status; ncm_data_nop_intf.bInterfaceNumber = status; @@ -1500,35 +1499,31 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) ecm_desc.wMaxSegmentSize = cpu_to_le16(ncm_opts->max_segment_size); - status = -ENODEV; - /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_in_desc); if (!ep) - goto fail; + return -ENODEV; ncm->port.in_ep = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_out_desc); if (!ep) - goto fail; + return -ENODEV; ncm->port.out_ep = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_notify_desc); if (!ep) - goto fail; + return -ENODEV; ncm->notify = ep; - status = -ENOMEM; - /* allocate notification request and buffer */ - ncm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); - if (!ncm->notify_req) - goto fail; - ncm->notify_req->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL); - if (!ncm->notify_req->buf) - goto fail; - ncm->notify_req->context = ncm; - ncm->notify_req->complete = ncm_notify_complete; + request = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL); + if (!request->buf) + return -ENOMEM; + request->context = ncm; + request->complete = ncm_notify_complete; /* * support all relevant hardware speeds... we expect that when @@ -1548,7 +1543,7 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, ncm_fs_function, ncm_hs_function, ncm_ss_function, ncm_ss_function); if (status) - goto fail; + return status; /* * NOTE: all that is done without knowing or caring about @@ -1561,23 +1556,18 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) hrtimer_setup(&ncm->task_timer, ncm_tx_timeout, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + if (cdev->use_os_string) { + os_desc_table[0].os_desc = &ncm_opts->ncm_os_desc; + os_desc_table[0].if_id = ncm_iad_desc.bFirstInterface; + f->os_desc_table = no_free_ptr(os_desc_table); + f->os_desc_n = 1; + } + ncm->notify_req = no_free_ptr(request); + DBG(cdev, "CDC Network: IN/%s OUT/%s NOTIFY/%s\n", ncm->port.in_ep->name, ncm->port.out_ep->name, ncm->notify->name); return 0; - -fail: - kfree(f->os_desc_table); - f->os_desc_n = 0; - - if (ncm->notify_req) { - kfree(ncm->notify_req->buf); - usb_ep_free_request(ncm->notify, ncm->notify_req); - } - - ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); - - return status; } static inline struct f_ncm_opts *to_f_ncm_opts(struct config_item *item) @@ -1771,7 +1761,6 @@ static struct usb_function *ncm_alloc(struct usb_function_instance *fi) mutex_unlock(&opts->lock); return ERR_PTR(-EINVAL); } - ncm_string_defs[STRING_MAC_IDX].s = ncm->ethaddr; spin_lock_init(&ncm->lock); ncm_reset_values(ncm); diff --git a/drivers/usb/gadget/function/f_rndis.c b/drivers/usb/gadget/function/f_rndis.c index 7cec19d65fb5..7451e7cb7a85 100644 --- a/drivers/usb/gadget/function/f_rndis.c +++ b/drivers/usb/gadget/function/f_rndis.c @@ -19,6 +19,8 @@ #include <linux/atomic.h> +#include <linux/usb/gadget.h> + #include "u_ether.h" #include "u_ether_configfs.h" #include "u_rndis.h" @@ -662,6 +664,8 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) struct usb_ep *ep; struct f_rndis_opts *rndis_opts; + struct usb_os_desc_table *os_desc_table __free(kfree) = NULL; + struct usb_request *request __free(free_usb_request) = NULL; if (!can_support_rndis(c)) return -EINVAL; @@ -669,12 +673,9 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) rndis_opts = container_of(f->fi, struct f_rndis_opts, func_inst); if (cdev->use_os_string) { - f->os_desc_table = kzalloc(sizeof(*f->os_desc_table), - GFP_KERNEL); - if (!f->os_desc_table) + os_desc_table = kzalloc(sizeof(*os_desc_table), GFP_KERNEL); + if (!os_desc_table) return -ENOMEM; - f->os_desc_n = 1; - f->os_desc_table[0].os_desc = &rndis_opts->rndis_os_desc; } rndis_iad_descriptor.bFunctionClass = rndis_opts->class; @@ -692,16 +693,14 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) gether_set_gadget(rndis_opts->net, cdev->gadget); status = gether_register_netdev(rndis_opts->net); if (status) - goto fail; + return status; rndis_opts->bound = true; } us = usb_gstrings_attach(cdev, rndis_strings, ARRAY_SIZE(rndis_string_defs)); - if (IS_ERR(us)) { - status = PTR_ERR(us); - goto fail; - } + if (IS_ERR(us)) + return PTR_ERR(us); rndis_control_intf.iInterface = us[0].id; rndis_data_intf.iInterface = us[1].id; rndis_iad_descriptor.iFunction = us[2].id; @@ -709,36 +708,30 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; rndis->ctrl_id = status; rndis_iad_descriptor.bFirstInterface = status; rndis_control_intf.bInterfaceNumber = status; rndis_union_desc.bMasterInterface0 = status; - if (cdev->use_os_string) - f->os_desc_table[0].if_id = - rndis_iad_descriptor.bFirstInterface; - status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; rndis->data_id = status; rndis_data_intf.bInterfaceNumber = status; rndis_union_desc.bSlaveInterface0 = status; - status = -ENODEV; - /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc); if (!ep) - goto fail; + return -ENODEV; rndis->port.in_ep = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc); if (!ep) - goto fail; + return -ENODEV; rndis->port.out_ep = ep; /* NOTE: a status/notification endpoint is, strictly speaking, @@ -747,21 +740,19 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) */ ep = usb_ep_autoconfig(cdev->gadget, &fs_notify_desc); if (!ep) - goto fail; + return -ENODEV; rndis->notify = ep; - status = -ENOMEM; - /* allocate notification request and buffer */ - rndis->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); - if (!rndis->notify_req) - goto fail; - rndis->notify_req->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL); - if (!rndis->notify_req->buf) - goto fail; - rndis->notify_req->length = STATUS_BYTECOUNT; - rndis->notify_req->context = rndis; - rndis->notify_req->complete = rndis_response_complete; + request = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL); + if (!request->buf) + return -ENOMEM; + request->length = STATUS_BYTECOUNT; + request->context = rndis; + request->complete = rndis_response_complete; /* support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at @@ -778,7 +769,7 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, eth_fs_function, eth_hs_function, eth_ss_function, eth_ss_function); if (status) - goto fail; + return status; rndis->port.open = rndis_open; rndis->port.close = rndis_close; @@ -789,9 +780,18 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) if (rndis->manufacturer && rndis->vendorID && rndis_set_param_vendor(rndis->params, rndis->vendorID, rndis->manufacturer)) { - status = -EINVAL; - goto fail_free_descs; + usb_free_all_descriptors(f); + return -EINVAL; + } + + if (cdev->use_os_string) { + os_desc_table[0].os_desc = &rndis_opts->rndis_os_desc; + os_desc_table[0].if_id = rndis_iad_descriptor.bFirstInterface; + f->os_desc_table = no_free_ptr(os_desc_table); + f->os_desc_n = 1; + } + rndis->notify_req = no_free_ptr(request); /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code @@ -802,21 +802,6 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) rndis->port.in_ep->name, rndis->port.out_ep->name, rndis->notify->name); return 0; - -fail_free_descs: - usb_free_all_descriptors(f); -fail: - kfree(f->os_desc_table); - f->os_desc_n = 0; - - if (rndis->notify_req) { - kfree(rndis->notify_req->buf); - usb_ep_free_request(rndis->notify, rndis->notify_req); - } - - ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); - - return status; } void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net) diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-trace.h b/drivers/usb/gadget/udc/cdns2/cdns2-trace.h index ade1752956b1..f4df0e2ff853 100644 --- a/drivers/usb/gadget/udc/cdns2/cdns2-trace.h +++ b/drivers/usb/gadget/udc/cdns2/cdns2-trace.h @@ -47,16 +47,6 @@ DEFINE_EVENT(cdns2_log_enable_disable, cdns2_pullup, TP_ARGS(set) ); -DEFINE_EVENT(cdns2_log_enable_disable, cdns2_lpm, - TP_PROTO(int set), - TP_ARGS(set) -); - -DEFINE_EVENT(cdns2_log_enable_disable, cdns2_may_wakeup, - TP_PROTO(int set), - TP_ARGS(set) -); - DECLARE_EVENT_CLASS(cdns2_log_simple, TP_PROTO(char *msg), TP_ARGS(msg), @@ -79,11 +69,6 @@ DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_status_stage, TP_ARGS(msg) ); -DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_set_config, - TP_PROTO(char *msg), - TP_ARGS(msg) -); - DEFINE_EVENT(cdns2_log_simple, cdns2_ep0_setup, TP_PROTO(char *msg), TP_ARGS(msg) @@ -340,11 +325,6 @@ DEFINE_EVENT(cdns2_log_request, cdns2_free_request, TP_ARGS(preq) ); -DEFINE_EVENT(cdns2_log_request, cdns2_ep_queue, - TP_PROTO(struct cdns2_request *preq), - TP_ARGS(preq) -); - DEFINE_EVENT(cdns2_log_request, cdns2_request_dequeue, TP_PROTO(struct cdns2_request *preq), TP_ARGS(preq) @@ -355,50 +335,6 @@ DEFINE_EVENT(cdns2_log_request, cdns2_request_giveback, TP_ARGS(preq) ); -TRACE_EVENT(cdns2_ep0_enqueue, - TP_PROTO(struct cdns2_device *dev_priv, struct usb_request *request), - TP_ARGS(dev_priv, request), - TP_STRUCT__entry( - __field(int, dir) - __field(int, length) - ), - TP_fast_assign( - __entry->dir = dev_priv->eps[0].dir; - __entry->length = request->length; - ), - TP_printk("Queue to ep0%s length: %u", __entry->dir ? "in" : "out", - __entry->length) -); - -DECLARE_EVENT_CLASS(cdns2_log_map_request, - TP_PROTO(struct cdns2_request *priv_req), - TP_ARGS(priv_req), - TP_STRUCT__entry( - __string(name, priv_req->pep->name) - __field(struct usb_request *, req) - __field(void *, buf) - __field(dma_addr_t, dma) - ), - TP_fast_assign( - __assign_str(name); - __entry->req = &priv_req->request; - __entry->buf = priv_req->request.buf; - __entry->dma = priv_req->request.dma; - ), - TP_printk("%s: req: %p, req buf %p, dma %p", - __get_str(name), __entry->req, __entry->buf, &__entry->dma - ) -); - -DEFINE_EVENT(cdns2_log_map_request, cdns2_map_request, - TP_PROTO(struct cdns2_request *req), - TP_ARGS(req) -); -DEFINE_EVENT(cdns2_log_map_request, cdns2_mapped_request, - TP_PROTO(struct cdns2_request *req), - TP_ARGS(req) -); - DECLARE_EVENT_CLASS(cdns2_log_trb, TP_PROTO(struct cdns2_endpoint *pep, struct cdns2_trb *trb), TP_ARGS(pep, trb), @@ -507,11 +443,6 @@ DEFINE_EVENT(cdns2_log_ep, cdns2_gadget_ep_disable, TP_ARGS(pep) ); -DEFINE_EVENT(cdns2_log_ep, cdns2_iso_out_ep_disable, - TP_PROTO(struct cdns2_endpoint *pep), - TP_ARGS(pep) -); - DEFINE_EVENT(cdns2_log_ep, cdns2_ep_busy_try_halt_again, TP_PROTO(struct cdns2_endpoint *pep), TP_ARGS(pep) diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index d709e24c1fd4..694653761c44 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -194,6 +194,9 @@ struct usb_request *usb_ep_alloc_request(struct usb_ep *ep, req = ep->ops->alloc_request(ep, gfp_flags); + if (req) + req->ep = ep; + trace_usb_ep_alloc_request(ep, req, req ? 0 : -ENOMEM); return req; @@ -1125,6 +1128,7 @@ void usb_gadget_set_state(struct usb_gadget *gadget, { gadget->state = state; schedule_work(&gadget->work); + trace_usb_gadget_set_state(gadget, 0); } EXPORT_SYMBOL_GPL(usb_gadget_set_state); diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 1d3085cc9d22..0c38fc37b6e6 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -812,8 +812,7 @@ static void tegra_xudc_update_data_role(struct tegra_xudc *xudc, return; } - xudc->device_mode = (usbphy->last_event == USB_EVENT_VBUS) ? true : - false; + xudc->device_mode = usbphy->last_event == USB_EVENT_VBUS; phy_index = tegra_xudc_get_phy_index(xudc, usbphy); dev_dbg(xudc->dev, "%s(): current phy index is %d\n", __func__, diff --git a/drivers/usb/gadget/udc/trace.h b/drivers/usb/gadget/udc/trace.h index 4e334298b0e8..fa3e6ddf0a12 100644 --- a/drivers/usb/gadget/udc/trace.h +++ b/drivers/usb/gadget/udc/trace.h @@ -81,6 +81,11 @@ DECLARE_EVENT_CLASS(udc_log_gadget, __entry->ret) ); +DEFINE_EVENT(udc_log_gadget, usb_gadget_set_state, + TP_PROTO(struct usb_gadget *g, int ret), + TP_ARGS(g, ret) +); + DEFINE_EVENT(udc_log_gadget, usb_gadget_frame_number, TP_PROTO(struct usb_gadget *g, int ret), TP_ARGS(g, ret) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 109100cc77a3..c4f17ce5c77b 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -93,7 +93,7 @@ config USB_XHCI_RCAR default ARCH_RENESAS help Say 'Y' to enable the support for the xHCI host controller - found in Renesas R-Car ARM SoCs. + found in Renesas R-Car and RZ/G3E alike ARM SoCs. config USB_XHCI_RZV2M bool "xHCI support for Renesas RZ/V2M SoC" diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c index dcf31a592f5d..4b5f03f683f7 100644 --- a/drivers/usb/host/max3421-hcd.c +++ b/drivers/usb/host/max3421-hcd.c @@ -1916,7 +1916,7 @@ error: if (hcd) { kfree(max3421_hcd->tx); kfree(max3421_hcd->rx); - if (max3421_hcd->spi_thread) + if (!IS_ERR_OR_NULL(max3421_hcd->spi_thread)) kthread_stop(max3421_hcd->spi_thread); usb_put_hcd(hcd); } diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index 66d970854357..e623e24d3f8e 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -448,13 +448,6 @@ static const struct dev_pm_ops ohci_hcd_s3c2410_pm_ops = { .resume = ohci_hcd_s3c2410_drv_resume, }; -static const struct of_device_id ohci_hcd_s3c2410_dt_ids[] = { - { .compatible = "samsung,s3c2410-ohci" }, - { /* sentinel */ } -}; - -MODULE_DEVICE_TABLE(of, ohci_hcd_s3c2410_dt_ids); - static struct platform_driver ohci_hcd_s3c2410_driver = { .probe = ohci_hcd_s3c2410_probe, .remove = ohci_hcd_s3c2410_remove, @@ -462,7 +455,6 @@ static struct platform_driver ohci_hcd_s3c2410_driver = { .driver = { .name = "s3c2410-ohci", .pm = &ohci_hcd_s3c2410_pm_ops, - .of_match_table = ohci_hcd_s3c2410_dt_ids, }, }; diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h index 4b8ff4815644..89bc83e4f1eb 100644 --- a/drivers/usb/host/xhci-caps.h +++ b/drivers/usb/host/xhci-caps.h @@ -89,3 +89,5 @@ #define HCC2_GSC(p) ((p) & (1 << 8)) /* true: HC support Virtualization Based Trusted I/O Capability */ #define HCC2_VTC(p) ((p) & (1 << 9)) +/* true: HC support Double BW on a eUSB2 HS ISOC EP */ +#define HCC2_EUSB2_DIC(p) ((p) & (1 << 11)) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index c4a6544aa107..6e5b6057de79 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1330,18 +1330,33 @@ static unsigned int xhci_get_endpoint_interval(struct usb_device *udev, return interval; } -/* The "Mult" field in the endpoint context is only set for SuperSpeed isoc eps. +/* + * xHCs without LEC use the "Mult" field in the endpoint context for SuperSpeed + * isoc eps, and High speed isoc eps that support bandwidth doubling. Standard * High speed endpoint descriptors can define "the number of additional * transaction opportunities per microframe", but that goes in the Max Burst * endpoint context field. */ -static u32 xhci_get_endpoint_mult(struct usb_device *udev, - struct usb_host_endpoint *ep) +static u32 xhci_get_endpoint_mult(struct xhci_hcd *xhci, + struct usb_device *udev, + struct usb_host_endpoint *ep) { - if (udev->speed < USB_SPEED_SUPER || - !usb_endpoint_xfer_isoc(&ep->desc)) - return 0; - return ep->ss_ep_comp.bmAttributes; + bool lec; + + /* xHCI 1.1 with LEC set does not use mult field, except intel eUSB2 */ + lec = xhci->hci_version > 0x100 && HCC2_LEC(xhci->hcc_params2); + + /* eUSB2 double isoc bw devices are the only USB2 devices using mult */ + if (usb_endpoint_is_hs_isoc_double(udev, ep) && + (!lec || xhci->quirks & XHCI_INTEL_HOST)) + return 1; + + /* SuperSpeed isoc transfers on hosts without LEC uses mult field */ + if (udev->speed >= USB_SPEED_SUPER && + usb_endpoint_xfer_isoc(&ep->desc) && !lec) + return ep->ss_ep_comp.bmAttributes; + + return 0; } static u32 xhci_get_endpoint_max_burst(struct usb_device *udev, @@ -1353,8 +1368,16 @@ static u32 xhci_get_endpoint_max_burst(struct usb_device *udev, if (udev->speed == USB_SPEED_HIGH && (usb_endpoint_xfer_isoc(&ep->desc) || - usb_endpoint_xfer_int(&ep->desc))) + usb_endpoint_xfer_int(&ep->desc))) { + /* + * USB 2 Isochronous Double IN Bandwidth ECN uses fixed burst + * size and max packets bits 12:11 are invalid. + */ + if (usb_endpoint_is_hs_isoc_double(udev, ep)) + return 2; + return usb_endpoint_maxp_mult(&ep->desc) - 1; + } return 0; } @@ -1378,36 +1401,6 @@ static u32 xhci_get_endpoint_type(struct usb_host_endpoint *ep) return 0; } -/* Return the maximum endpoint service interval time (ESIT) payload. - * Basically, this is the maxpacket size, multiplied by the burst size - * and mult size. - */ -static u32 xhci_get_max_esit_payload(struct usb_device *udev, - struct usb_host_endpoint *ep) -{ - int max_burst; - int max_packet; - - /* Only applies for interrupt or isochronous endpoints */ - if (usb_endpoint_xfer_control(&ep->desc) || - usb_endpoint_xfer_bulk(&ep->desc)) - return 0; - - /* SuperSpeedPlus Isoc ep sending over 48k per esit */ - if ((udev->speed >= USB_SPEED_SUPER_PLUS) && - USB_SS_SSP_ISOC_COMP(ep->ss_ep_comp.bmAttributes)) - return le32_to_cpu(ep->ssp_isoc_ep_comp.dwBytesPerInterval); - - /* SuperSpeed or SuperSpeedPlus Isoc ep with less than 48k per esit */ - if (udev->speed >= USB_SPEED_SUPER) - return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval); - - max_packet = usb_endpoint_maxp(&ep->desc); - max_burst = usb_endpoint_maxp_mult(&ep->desc); - /* A 0 in max burst means 1 transfer per ESIT */ - return max_packet * max_burst; -} - /* Set up an endpoint with one ring segment. Do not allocate stream rings. * Drivers will have to call usb_alloc_streams() to do that. */ @@ -1439,13 +1432,20 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, ring_type = usb_endpoint_type(&ep->desc); + /* Ensure host supports double isoc bandwidth for eUSB2 devices */ + if (usb_endpoint_is_hs_isoc_double(udev, ep) && + !HCC2_EUSB2_DIC(xhci->hcc_params2)) { + dev_dbg(&udev->dev, "Double Isoc Bandwidth not supported by xhci\n"); + return -EINVAL; + } + /* * Get values to fill the endpoint context, mostly from ep descriptor. * The average TRB buffer lengt for bulk endpoints is unclear as we * have no clue on scatter gather list entry size. For Isoc and Int, * set it to max available. See xHCI 1.1 spec 4.14.1.1 for details. */ - max_esit_payload = xhci_get_max_esit_payload(udev, ep); + max_esit_payload = usb_endpoint_max_periodic_payload(udev, ep); interval = xhci_get_endpoint_interval(udev, ep); /* Periodic endpoint bInterval limit quirk */ @@ -1462,8 +1462,8 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, } } - mult = xhci_get_endpoint_mult(udev, ep); - max_packet = usb_endpoint_maxp(&ep->desc); + mult = xhci_get_endpoint_mult(xhci, udev, ep); + max_packet = xhci_usb_endpoint_maxp(udev, ep); max_burst = xhci_get_endpoint_max_burst(udev, ep); avg_trb_len = max_esit_payload; @@ -1484,9 +1484,6 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, /* xHCI 1.0 and 1.1 indicates that ctrl ep avg TRB Length should be 8 */ if (usb_endpoint_xfer_control(&ep->desc) && xhci->hci_version >= 0x100) avg_trb_len = 8; - /* xhci 1.1 with LEC support doesn't use mult field, use RsvdZ */ - if ((xhci->hci_version > 0x100) && HCC2_LEC(xhci->hcc_params2)) - mult = 0; /* Set up the endpoint ring */ virt_dev->eps[ep_index].new_ring = diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 00fac8b233d2..5c8ab519f497 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -610,7 +610,7 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) { int retval; struct xhci_hcd *xhci; - struct usb_hcd *hcd; + struct usb_hcd *hcd, *usb3_hcd; struct reset_control *reset; reset = devm_reset_control_get_optional_exclusive(&dev->dev, NULL); @@ -636,26 +636,32 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) hcd = dev_get_drvdata(&dev->dev); xhci = hcd_to_xhci(hcd); xhci->reset = reset; - xhci->shared_hcd = usb_create_shared_hcd(&xhci_pci_hc_driver, &dev->dev, - pci_name(dev), hcd); - if (!xhci->shared_hcd) { - retval = -ENOMEM; - goto dealloc_usb2_hcd; - } - retval = xhci_ext_cap_init(xhci); - if (retval) - goto put_usb3_hcd; + xhci->allow_single_roothub = 1; + if (!xhci_has_one_roothub(xhci)) { + xhci->shared_hcd = usb_create_shared_hcd(&xhci_pci_hc_driver, &dev->dev, + pci_name(dev), hcd); + if (!xhci->shared_hcd) { + retval = -ENOMEM; + goto dealloc_usb2_hcd; + } - retval = usb_add_hcd(xhci->shared_hcd, dev->irq, - IRQF_SHARED); - if (retval) - goto put_usb3_hcd; - /* Roothub already marked as USB 3.0 speed */ + retval = xhci_ext_cap_init(xhci); + if (retval) + goto put_usb3_hcd; + + retval = usb_add_hcd(xhci->shared_hcd, dev->irq, IRQF_SHARED); + if (retval) + goto put_usb3_hcd; + } else { + retval = xhci_ext_cap_init(xhci); + if (retval) + goto dealloc_usb2_hcd; + } - if (!(xhci->quirks & XHCI_BROKEN_STREAMS) && - HCC_MAX_PSA(xhci->hcc_params) >= 4) - xhci->shared_hcd->can_do_streams = 1; + usb3_hcd = xhci_get_usb3_hcd(xhci); + if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && HCC_MAX_PSA(xhci->hcc_params) >= 4) + usb3_hcd->can_do_streams = 1; /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ pm_runtime_put_noidle(&dev->dev); diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 5eb51797de32..074d9c731639 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -20,6 +20,7 @@ #include <linux/acpi.h> #include <linux/usb/of.h> #include <linux/reset.h> +#include <linux/usb/xhci-sideband.h> #include "xhci.h" #include "xhci-plat.h" @@ -74,6 +75,16 @@ static int xhci_priv_resume_quirk(struct usb_hcd *hcd) return priv->resume_quirk(hcd); } +static int xhci_priv_post_resume_quirk(struct usb_hcd *hcd) +{ + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (!priv->post_resume_quirk) + return 0; + + return priv->post_resume_quirk(hcd); +} + static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci) { struct xhci_plat_priv *priv = xhci_to_priv(xhci); @@ -171,6 +182,7 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s return ret; pm_runtime_set_active(&pdev->dev); + pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_enable(&pdev->dev); pm_runtime_get_noresume(&pdev->dev); @@ -454,7 +466,7 @@ void xhci_plat_remove(struct platform_device *dev) } EXPORT_SYMBOL_GPL(xhci_plat_remove); -static int xhci_plat_suspend(struct device *dev) +static int xhci_plat_suspend_common(struct device *dev) { struct usb_hcd *hcd = dev_get_drvdata(dev); struct xhci_hcd *xhci = hcd_to_xhci(hcd); @@ -482,6 +494,25 @@ static int xhci_plat_suspend(struct device *dev) return 0; } +static int xhci_plat_suspend(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (xhci_sideband_check(hcd)) { + priv->sideband_at_suspend = 1; + dev_dbg(dev, "sideband instance active, skip suspend.\n"); + return 0; + } + + return xhci_plat_suspend_common(dev); +} + +static int xhci_plat_freeze(struct device *dev) +{ + return xhci_plat_suspend_common(dev); +} + static int xhci_plat_resume_common(struct device *dev, bool power_lost) { struct usb_hcd *hcd = dev_get_drvdata(dev); @@ -509,6 +540,10 @@ static int xhci_plat_resume_common(struct device *dev, bool power_lost) if (ret) goto disable_clks; + ret = xhci_priv_post_resume_quirk(hcd); + if (ret) + goto disable_clks; + pm_runtime_disable(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); @@ -526,6 +561,20 @@ disable_clks: static int xhci_plat_resume(struct device *dev) { + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); + + if (priv->sideband_at_suspend) { + priv->sideband_at_suspend = 0; + dev_dbg(dev, "sideband instance active, skip resume.\n"); + return 0; + } + + return xhci_plat_resume_common(dev, false); +} + +static int xhci_plat_thaw(struct device *dev) +{ return xhci_plat_resume_common(dev, false); } @@ -558,9 +607,9 @@ static int __maybe_unused xhci_plat_runtime_resume(struct device *dev) const struct dev_pm_ops xhci_plat_pm_ops = { .suspend = pm_sleep_ptr(xhci_plat_suspend), .resume = pm_sleep_ptr(xhci_plat_resume), - .freeze = pm_sleep_ptr(xhci_plat_suspend), - .thaw = pm_sleep_ptr(xhci_plat_resume), - .poweroff = pm_sleep_ptr(xhci_plat_suspend), + .freeze = pm_sleep_ptr(xhci_plat_freeze), + .thaw = pm_sleep_ptr(xhci_plat_thaw), + .poweroff = pm_sleep_ptr(xhci_plat_freeze), .restore = pm_sleep_ptr(xhci_plat_restore), SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend, diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h index fe4f95e690fa..00751d851831 100644 --- a/drivers/usb/host/xhci-plat.h +++ b/drivers/usb/host/xhci-plat.h @@ -16,10 +16,12 @@ struct xhci_plat_priv { const char *firmware_name; unsigned long long quirks; bool power_lost; + unsigned sideband_at_suspend:1; void (*plat_start)(struct usb_hcd *); int (*init_quirk)(struct usb_hcd *); int (*suspend_quirk)(struct usb_hcd *); int (*resume_quirk)(struct usb_hcd *); + int (*post_resume_quirk)(struct usb_hcd *); }; #define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv) diff --git a/drivers/usb/host/xhci-rcar-regs.h b/drivers/usb/host/xhci-rcar-regs.h new file mode 100644 index 000000000000..5ecbda858be0 --- /dev/null +++ b/drivers/usb/host/xhci-rcar-regs.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __XHCI_RCAR_H +#define __XHCI_RCAR_H + +/*** Register Offset ***/ +#define RCAR_USB3_AXH_STA 0x104 /* AXI Host Control Status */ +#define RCAR_USB3_INT_ENA 0x224 /* Interrupt Enable */ +#define RCAR_USB3_DL_CTRL 0x250 /* FW Download Control & Status */ +#define RCAR_USB3_FW_DATA0 0x258 /* FW Data0 */ + +#define RCAR_USB3_LCLK 0xa44 /* LCLK Select */ +#define RCAR_USB3_CONF1 0xa48 /* USB3.0 Configuration1 */ +#define RCAR_USB3_CONF2 0xa5c /* USB3.0 Configuration2 */ +#define RCAR_USB3_CONF3 0xaa8 /* USB3.0 Configuration3 */ +#define RCAR_USB3_RX_POL 0xab0 /* USB3.0 RX Polarity */ +#define RCAR_USB3_TX_POL 0xab8 /* USB3.0 TX Polarity */ + +/*** Register Settings ***/ +/* AXI Host Control Status */ +#define RCAR_USB3_AXH_STA_B3_PLL_ACTIVE 0x00010000 +#define RCAR_USB3_AXH_STA_B2_PLL_ACTIVE 0x00000001 +#define RCAR_USB3_AXH_STA_PLL_ACTIVE_MASK (RCAR_USB3_AXH_STA_B3_PLL_ACTIVE | \ + RCAR_USB3_AXH_STA_B2_PLL_ACTIVE) + +/* Interrupt Enable */ +#define RCAR_USB3_INT_XHC_ENA 0x00000001 +#define RCAR_USB3_INT_PME_ENA 0x00000002 +#define RCAR_USB3_INT_HSE_ENA 0x00000004 +#define RCAR_USB3_INT_ENA_VAL (RCAR_USB3_INT_XHC_ENA | \ + RCAR_USB3_INT_PME_ENA | RCAR_USB3_INT_HSE_ENA) + +/* FW Download Control & Status */ +#define RCAR_USB3_DL_CTRL_ENABLE 0x00000001 +#define RCAR_USB3_DL_CTRL_FW_SUCCESS 0x00000010 +#define RCAR_USB3_DL_CTRL_FW_SET_DATA0 0x00000100 + +/* LCLK Select */ +#define RCAR_USB3_LCLK_ENA_VAL 0x01030001 + +/* USB3.0 Configuration */ +#define RCAR_USB3_CONF1_VAL 0x00030204 +#define RCAR_USB3_CONF2_VAL 0x00030300 +#define RCAR_USB3_CONF3_VAL 0x13802007 + +/* USB3.0 Polarity */ +#define RCAR_USB3_RX_POL_VAL BIT(21) +#define RCAR_USB3_TX_POL_VAL BIT(4) + +#endif /* __XHCI_RCAR_H */ diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index 1cc082a3b793..8a993ee21c87 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -11,9 +11,12 @@ #include <linux/platform_device.h> #include <linux/of.h> #include <linux/usb/phy.h> +#include <linux/reset.h> #include "xhci.h" #include "xhci-plat.h" +#include "xhci-rcar-regs.h" +#include "xhci-rzg3e-regs.h" #include "xhci-rzv2m.h" #define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem" @@ -29,50 +32,6 @@ MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V1); MODULE_FIRMWARE(XHCI_RCAR_FIRMWARE_NAME_V3); -/*** Register Offset ***/ -#define RCAR_USB3_AXH_STA 0x104 /* AXI Host Control Status */ -#define RCAR_USB3_INT_ENA 0x224 /* Interrupt Enable */ -#define RCAR_USB3_DL_CTRL 0x250 /* FW Download Control & Status */ -#define RCAR_USB3_FW_DATA0 0x258 /* FW Data0 */ - -#define RCAR_USB3_LCLK 0xa44 /* LCLK Select */ -#define RCAR_USB3_CONF1 0xa48 /* USB3.0 Configuration1 */ -#define RCAR_USB3_CONF2 0xa5c /* USB3.0 Configuration2 */ -#define RCAR_USB3_CONF3 0xaa8 /* USB3.0 Configuration3 */ -#define RCAR_USB3_RX_POL 0xab0 /* USB3.0 RX Polarity */ -#define RCAR_USB3_TX_POL 0xab8 /* USB3.0 TX Polarity */ - -/*** Register Settings ***/ -/* AXI Host Control Status */ -#define RCAR_USB3_AXH_STA_B3_PLL_ACTIVE 0x00010000 -#define RCAR_USB3_AXH_STA_B2_PLL_ACTIVE 0x00000001 -#define RCAR_USB3_AXH_STA_PLL_ACTIVE_MASK (RCAR_USB3_AXH_STA_B3_PLL_ACTIVE | \ - RCAR_USB3_AXH_STA_B2_PLL_ACTIVE) - -/* Interrupt Enable */ -#define RCAR_USB3_INT_XHC_ENA 0x00000001 -#define RCAR_USB3_INT_PME_ENA 0x00000002 -#define RCAR_USB3_INT_HSE_ENA 0x00000004 -#define RCAR_USB3_INT_ENA_VAL (RCAR_USB3_INT_XHC_ENA | \ - RCAR_USB3_INT_PME_ENA | RCAR_USB3_INT_HSE_ENA) - -/* FW Download Control & Status */ -#define RCAR_USB3_DL_CTRL_ENABLE 0x00000001 -#define RCAR_USB3_DL_CTRL_FW_SUCCESS 0x00000010 -#define RCAR_USB3_DL_CTRL_FW_SET_DATA0 0x00000100 - -/* LCLK Select */ -#define RCAR_USB3_LCLK_ENA_VAL 0x01030001 - -/* USB3.0 Configuration */ -#define RCAR_USB3_CONF1_VAL 0x00030204 -#define RCAR_USB3_CONF2_VAL 0x00030300 -#define RCAR_USB3_CONF3_VAL 0x13802007 - -/* USB3.0 Polarity */ -#define RCAR_USB3_RX_POL_VAL BIT(21) -#define RCAR_USB3_TX_POL_VAL BIT(4) - static void xhci_rcar_start_gen2(struct usb_hcd *hcd) { /* LCLK Select */ @@ -110,6 +69,48 @@ static void xhci_rcar_start(struct usb_hcd *hcd) } } +static void xhci_rzg3e_start(struct usb_hcd *hcd) +{ + u32 int_en; + + if (hcd->regs) { + /* Update the controller initial setting */ + writel(0x03130200, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(0)); + writel(0x00160200, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(1)); + writel(0x03150000, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(2)); + writel(0x03130200, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(3)); + writel(0x00180000, hcd->regs + RZG3E_USB3_HOST_U3P0PIPESC(4)); + + /* Interrupt Enable */ + int_en = readl(hcd->regs + RZG3E_USB3_HOST_INTEN); + int_en |= RZG3E_USB3_HOST_INTEN_ENA; + writel(int_en, hcd->regs + RZG3E_USB3_HOST_INTEN); + } +} + +static int xhci_rzg3e_resume(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + return reset_control_deassert(xhci->reset); +} + +static int xhci_rzg3e_post_resume(struct usb_hcd *hcd) +{ + xhci_rzg3e_start(hcd); + + return 0; +} + +static int xhci_rzg3e_suspend(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + reset_control_assert(xhci->reset); + + return 0; +} + static int xhci_rcar_download_firmware(struct usb_hcd *hcd) { struct device *dev = hcd->self.controller; @@ -233,6 +234,14 @@ static const struct xhci_plat_priv xhci_plat_renesas_rzv2m = { .plat_start = xhci_rzv2m_start, }; +static const struct xhci_plat_priv xhci_plat_renesas_rzg3e = { + .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_RESET_ON_RESUME | XHCI_SUSPEND_RESUME_CLKS, + .plat_start = xhci_rzg3e_start, + .suspend_quirk = xhci_rzg3e_suspend, + .resume_quirk = xhci_rzg3e_resume, + .post_resume_quirk = xhci_rzg3e_post_resume, +}; + static const struct of_device_id usb_xhci_of_match[] = { { .compatible = "renesas,xhci-r8a7790", @@ -250,6 +259,9 @@ static const struct of_device_id usb_xhci_of_match[] = { .compatible = "renesas,xhci-r8a7796", .data = &xhci_plat_renesas_rcar_gen3, }, { + .compatible = "renesas,r9a09g047-xhci", + .data = &xhci_plat_renesas_rzg3e, + }, { .compatible = "renesas,rcar-gen2-xhci", .data = &xhci_plat_renesas_rcar_gen2, }, { diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 4f8f5aab109d..8e209aa33ea7 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -711,7 +711,7 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, return -ENODEV; } - hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id); + hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id) & TR_DEQ_PTR_MASK; new_seg = ep_ring->deq_seg; new_deq = ep_ring->dequeue; new_cycle = le32_to_cpu(td->end_trb->generic.field[3]) & TRB_CYCLE; @@ -723,7 +723,7 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, */ do { if (!hw_dequeue_found && xhci_trb_virt_to_dma(new_seg, new_deq) - == (dma_addr_t)(hw_dequeue & ~0xf)) { + == (dma_addr_t)hw_dequeue) { hw_dequeue_found = true; if (td_last_trb_found) break; @@ -1066,7 +1066,7 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) */ hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, td->urb->stream_id); - hw_deq &= ~0xf; + hw_deq &= TR_DEQ_PTR_MASK; if (td->cancel_status == TD_HALTED || trb_in_td(td, hw_deq)) { switch (td->cancel_status) { @@ -1156,7 +1156,7 @@ static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep) if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */ hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0); - hw_deq &= ~0xf; + hw_deq &= TR_DEQ_PTR_MASK; td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list); if (trb_in_td(td, hw_deq)) return td; @@ -1262,19 +1262,17 @@ reset_done: * Stopped state, but it will soon change to Running. * * Assume this bug on unexpected Stop Endpoint failures. - * Keep retrying until the EP starts and stops again. + * Keep retrying until the EP starts and stops again or + * up to a timeout (a defective HC may never start, or a + * driver bug may cause stopping an already stopped EP). */ + if (time_is_before_jiffies(ep->stop_time + msecs_to_jiffies(100))) + break; fallthrough; case EP_STATE_RUNNING: /* Race, HW handled stop ep cmd before ep was running */ xhci_dbg(xhci, "Stop ep completion ctx error, ctx_state %d\n", GET_EP_CTX_STATE(ep_ctx)); - /* - * Don't retry forever if we guessed wrong or a defective HC never starts - * the EP or says 'Running' but fails the command. We must give back TDs. - */ - if (time_is_before_jiffies(ep->stop_time + msecs_to_jiffies(100))) - break; command = xhci_alloc_command(xhci, false, GFP_ATOMIC); if (!command) { @@ -1481,7 +1479,7 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, u64 deq; /* 4.6.10 deq ptr is written to the stream ctx for streams */ if (ep->ep_state & EP_HAS_STREAMS) { - deq = le64_to_cpu(stream_ctx->stream_ring) & SCTX_DEQ_MASK; + deq = le64_to_cpu(stream_ctx->stream_ring) & TR_DEQ_PTR_MASK; /* * Cadence xHCI controllers store some endpoint state @@ -1497,7 +1495,7 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, stream_ctx->reserved[1] = 0; } } else { - deq = le64_to_cpu(ep_ctx->deq) & ~EP_CTX_CYCLE_MASK; + deq = le64_to_cpu(ep_ctx->deq) & TR_DEQ_PTR_MASK; } xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, "Successful Set TR Deq Ptr cmd, deq = @%08llx", deq); @@ -3550,7 +3548,7 @@ static u32 xhci_td_remainder(struct xhci_hcd *xhci, int transferred, if ((xhci->quirks & XHCI_MTK_HOST) && (xhci->hci_version < 0x100)) trb_buff_len = 0; - maxp = usb_endpoint_maxp(&urb->ep->desc); + maxp = xhci_usb_endpoint_maxp(urb->dev, urb->ep); total_packet_count = DIV_ROUND_UP(td_total_len, maxp); /* Queueing functions don't count the current TRB into transferred */ @@ -3567,7 +3565,7 @@ static int xhci_align_td(struct xhci_hcd *xhci, struct urb *urb, u32 enqd_len, u32 new_buff_len; size_t len; - max_pkt = usb_endpoint_maxp(&urb->ep->desc); + max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep); unalign = (enqd_len + *trb_buff_len) % max_pkt; /* we got lucky, last normal TRB data on segment is packet aligned */ @@ -4138,7 +4136,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, addr = start_addr + urb->iso_frame_desc[i].offset; td_len = urb->iso_frame_desc[i].length; td_remain_len = td_len; - max_pkt = usb_endpoint_maxp(&urb->ep->desc); + max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep); total_pkt_count = DIV_ROUND_UP(td_len, max_pkt); /* A zero-length transfer still involves at least one packet. */ diff --git a/drivers/usb/host/xhci-rzg3e-regs.h b/drivers/usb/host/xhci-rzg3e-regs.h new file mode 100644 index 000000000000..7a244a47b882 --- /dev/null +++ b/drivers/usb/host/xhci-rzg3e-regs.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __XHCI_RZG3E_H +#define __XHCI_RZG3E_H + +#define RZG3E_USB3_HOST_INTEN 0x1044 /* Interrupt Enable */ +#define RZG3E_USB3_HOST_U3P0PIPESC(x) (0x10c0 + (x) * 4) /* PIPE Status and Control Register */ + +#define RZG3E_USB3_HOST_INTEN_XHC BIT(0) +#define RZG3E_USB3_HOST_INTEN_HSE BIT(2) +#define RZG3E_USB3_HOST_INTEN_ENA (RZG3E_USB3_HOST_INTEN_XHC | RZG3E_USB3_HOST_INTEN_HSE) + +#endif /* __XHCI_RZG3E_H */ diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c index d49f9886dd84..e771a476fef2 100644 --- a/drivers/usb/host/xhci-sideband.c +++ b/drivers/usb/host/xhci-sideband.c @@ -267,6 +267,31 @@ xhci_sideband_get_event_buffer(struct xhci_sideband *sb) EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer); /** + * xhci_sideband_check - check the existence of active sidebands + * @hcd: the host controller driver associated with the target host controller + * + * Allow other drivers, such as usb controller driver, to check if there are + * any sideband activity on the host controller. This information could be used + * for power management or other forms of resource management. The caller should + * ensure downstream usb devices are all either suspended or marked as + * "offload_at_suspend" to ensure the correctness of the return value. + * + * Returns true on any active sideband existence, false otherwise. + */ +bool xhci_sideband_check(struct usb_hcd *hcd) +{ + struct usb_device *udev = hcd->self.root_hub; + bool active; + + usb_lock_device(udev); + active = usb_offload_check(udev); + usb_unlock_device(udev); + + return active; +} +EXPORT_SYMBOL_GPL(xhci_sideband_check); + +/** * xhci_sideband_create_interrupter - creates a new interrupter for this sideband * @sb: sideband instance for this usb device * @num_seg: number of event ring segments to allocate @@ -286,6 +311,7 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, bool ip_autoclear, u32 imod_interval, int intr_num) { int ret = 0; + struct usb_device *udev; if (!sb || !sb->xhci) return -ENODEV; @@ -304,6 +330,9 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, goto out; } + udev = sb->vdev->udev; + ret = usb_offload_get(udev); + sb->ir->ip_autoclear = ip_autoclear; out: @@ -323,6 +352,8 @@ EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter); void xhci_sideband_remove_interrupter(struct xhci_sideband *sb) { + struct usb_device *udev; + if (!sb || !sb->ir) return; @@ -330,6 +361,11 @@ xhci_sideband_remove_interrupter(struct xhci_sideband *sb) xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir); sb->ir = NULL; + udev = sb->vdev->udev; + + if (udev->state != USB_STATE_NOTATTACHED) + usb_offload_put(udev); + mutex_unlock(&sb->mutex); } EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter); diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 0c7af44d4dae..5255b1002893 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -155,6 +155,8 @@ #define FW_IOCTL_TYPE_SHIFT 24 #define FW_IOCTL_CFGTBL_READ 17 +#define WAKE_IRQ_START_INDEX 2 + struct tegra_xusb_fw_header { __le32 boot_loadaddr_in_imem; __le32 boot_codedfi_offset; @@ -228,6 +230,7 @@ struct tegra_xusb_soc { unsigned int num_supplies; const struct tegra_xusb_phy_type *phy_types; unsigned int num_types; + unsigned int max_num_wakes; const struct tegra_xusb_context_soc *context; struct { @@ -263,6 +266,7 @@ struct tegra_xusb { int xhci_irq; int mbox_irq; int padctl_irq; + int *wake_irqs; void __iomem *ipfs_base; void __iomem *fpci_base; @@ -313,6 +317,7 @@ struct tegra_xusb { bool suspended; struct tegra_xusb_context context; u8 lp0_utmi_pad_mask; + int num_wakes; }; static struct hc_driver __read_mostly tegra_xhci_hc_driver; @@ -1482,7 +1487,7 @@ static int tegra_xhci_id_notify(struct notifier_block *nb, tegra->otg_usb2_port = tegra_xusb_get_usb2_port(tegra, usbphy); - tegra->host_mode = (usbphy->last_event == USB_EVENT_ID) ? true : false; + tegra->host_mode = usbphy->last_event == USB_EVENT_ID; schedule_work(&tegra->id_work); @@ -1537,6 +1542,58 @@ static void tegra_xusb_deinit_usb_phy(struct tegra_xusb *tegra) otg_set_host(tegra->usbphy[i]->otg, NULL); } +static int tegra_xusb_setup_wakeup(struct platform_device *pdev, struct tegra_xusb *tegra) +{ + unsigned int i; + + if (tegra->soc->max_num_wakes == 0) + return 0; + + tegra->wake_irqs = devm_kcalloc(tegra->dev, + tegra->soc->max_num_wakes, + sizeof(*tegra->wake_irqs), GFP_KERNEL); + if (!tegra->wake_irqs) + return -ENOMEM; + + /* + * USB wake events are independent of each other, so it is not necessary for a platform + * to utilize all wake-up events supported for a given device. The USB host can operate + * even if wake-up events are not defined or fail to be configured. Therefore, we only + * return critical errors, such as -ENOMEM. + */ + for (i = 0; i < tegra->soc->max_num_wakes; i++) { + struct irq_data *data; + + tegra->wake_irqs[i] = platform_get_irq(pdev, i + WAKE_IRQ_START_INDEX); + if (tegra->wake_irqs[i] < 0) + break; + + data = irq_get_irq_data(tegra->wake_irqs[i]); + if (!data) { + dev_warn(tegra->dev, "get wake event %d irq data fail\n", i); + irq_dispose_mapping(tegra->wake_irqs[i]); + break; + } + + irq_set_irq_type(tegra->wake_irqs[i], irqd_get_trigger_type(data)); + } + + tegra->num_wakes = i; + dev_dbg(tegra->dev, "setup %d wake events\n", tegra->num_wakes); + + return 0; +} + +static void tegra_xusb_dispose_wake(struct tegra_xusb *tegra) +{ + unsigned int i; + + for (i = 0; i < tegra->num_wakes; i++) + irq_dispose_mapping(tegra->wake_irqs[i]); + + tegra->num_wakes = 0; +} + static int tegra_xusb_probe(struct platform_device *pdev) { struct tegra_xusb *tegra; @@ -1587,9 +1644,15 @@ static int tegra_xusb_probe(struct platform_device *pdev) if (tegra->mbox_irq < 0) return tegra->mbox_irq; + err = tegra_xusb_setup_wakeup(pdev, tegra); + if (err) + return err; + tegra->padctl = tegra_xusb_padctl_get(&pdev->dev); - if (IS_ERR(tegra->padctl)) - return PTR_ERR(tegra->padctl); + if (IS_ERR(tegra->padctl)) { + err = PTR_ERR(tegra->padctl); + goto dispose_wake; + } np = of_parse_phandle(pdev->dev.of_node, "nvidia,xusb-padctl", 0); if (!np) { @@ -1913,6 +1976,8 @@ put_powerdomains: put_padctl: of_node_put(np); tegra_xusb_padctl_put(tegra->padctl); +dispose_wake: + tegra_xusb_dispose_wake(tegra); return err; } @@ -1945,6 +2010,8 @@ static void tegra_xusb_remove(struct platform_device *pdev) if (tegra->padctl_irq) pm_runtime_disable(&pdev->dev); + tegra_xusb_dispose_wake(tegra); + pm_runtime_put(&pdev->dev); tegra_xusb_disable(tegra); @@ -2355,8 +2422,13 @@ out: pm_runtime_disable(dev); if (device_may_wakeup(dev)) { + unsigned int i; + if (enable_irq_wake(tegra->padctl_irq)) dev_err(dev, "failed to enable padctl wakes\n"); + + for (i = 0; i < tegra->num_wakes; i++) + enable_irq_wake(tegra->wake_irqs[i]); } } @@ -2384,8 +2456,13 @@ static __maybe_unused int tegra_xusb_resume(struct device *dev) } if (device_may_wakeup(dev)) { + unsigned int i; + if (disable_irq_wake(tegra->padctl_irq)) dev_err(dev, "failed to disable padctl wakes\n"); + + for (i = 0; i < tegra->num_wakes; i++) + disable_irq_wake(tegra->wake_irqs[i]); } tegra->suspended = false; mutex_unlock(&tegra->lock); @@ -2636,6 +2713,7 @@ static const struct tegra_xusb_soc tegra234_soc = { .num_supplies = ARRAY_SIZE(tegra194_supply_names), .phy_types = tegra194_phy_types, .num_types = ARRAY_SIZE(tegra194_phy_types), + .max_num_wakes = 7, .context = &tegra186_xusb_context, .ports = { .usb3 = { .offset = 0, .count = 4, }, diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h index bfb5c5c17012..9abc904f1749 100644 --- a/drivers/usb/host/xhci-trace.h +++ b/drivers/usb/host/xhci-trace.h @@ -541,23 +541,23 @@ DEFINE_EVENT(xhci_log_ring, xhci_inc_deq, ); DECLARE_EVENT_CLASS(xhci_log_portsc, - TP_PROTO(struct xhci_port *port, u32 portsc), - TP_ARGS(port, portsc), - TP_STRUCT__entry( - __field(u32, busnum) - __field(u32, portnum) - __field(u32, portsc) - ), - TP_fast_assign( - __entry->busnum = port->rhub->hcd->self.busnum; - __entry->portnum = port->hcd_portnum; - __entry->portsc = portsc; - ), - TP_printk("port %d-%d: %s", - __entry->busnum, - __entry->portnum, - xhci_decode_portsc(__get_buf(XHCI_MSG_MAX), __entry->portsc) - ) + TP_PROTO(struct xhci_port *port, u32 portsc), + TP_ARGS(port, portsc), + TP_STRUCT__entry( + __field(u32, busnum) + __field(u32, portnum) + __field(u32, portsc) + ), + TP_fast_assign( + __entry->busnum = port->rhub->hcd->self.busnum; + __entry->portnum = port->hcd_portnum + 1; + __entry->portsc = portsc; + ), + TP_printk("port %d-%d: %s", + __entry->busnum, + __entry->portnum, + xhci_decode_portsc(__get_buf(XHCI_MSG_MAX), __entry->portsc) + ) ); DEFINE_EVENT(xhci_log_portsc, xhci_handle_port_status, diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 742c23826e17..0cb45b95e4f5 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1336,7 +1336,7 @@ static bool xhci_urb_temp_buffer_required(struct usb_hcd *hcd, struct scatterlist *tail_sg; tail_sg = urb->sg; - max_pkt = usb_endpoint_maxp(&urb->ep->desc); + max_pkt = xhci_usb_endpoint_maxp(urb->dev, urb->ep); if (!urb->num_sgs) return ret; @@ -2924,6 +2924,20 @@ out: } EXPORT_SYMBOL_GPL(xhci_stop_endpoint_sync); +/* + * xhci_usb_endpoint_maxp - get endpoint max packet size + * @host_ep: USB host endpoint to be checked + * + * Returns max packet from the correct descriptor + */ +int xhci_usb_endpoint_maxp(struct usb_device *udev, + struct usb_host_endpoint *host_ep) +{ + if (usb_endpoint_is_hs_isoc_double(udev, host_ep)) + return le16_to_cpu(host_ep->eusb2_isoc_ep_comp.wMaxPacketSize); + return usb_endpoint_maxp(&host_ep->desc); +} + /* Issue a configure endpoint command or evaluate context command * and wait for it to finish. */ diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 85d5b964bf1e..58a51f09cceb 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -500,7 +500,8 @@ struct xhci_ep_ctx { /* deq bitmasks */ #define EP_CTX_CYCLE_MASK (1 << 0) -#define SCTX_DEQ_MASK (~0xfL) +/* bits 63:4 - TR Dequeue Pointer */ +#define TR_DEQ_PTR_MASK GENMASK_ULL(63, 4) /** @@ -1958,6 +1959,8 @@ void xhci_update_erst_dequeue(struct xhci_hcd *xhci, struct xhci_interrupter *ir, bool clear_ehb); void xhci_add_interrupter(struct xhci_hcd *xhci, unsigned int intr_num); +int xhci_usb_endpoint_maxp(struct usb_device *udev, + struct usb_host_endpoint *host_ep); /* xHCI roothub code */ void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port, diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 6497c4e81e95..09ac6f1c985f 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -147,6 +147,7 @@ config USB_APPLEDISPLAY config USB_QCOM_EUD tristate "QCOM Embedded USB Debugger(EUD) Driver" depends on ARCH_QCOM || COMPILE_TEST + select QCOM_SCM select USB_ROLE_SWITCH help This module enables support for Qualcomm Technologies, Inc. @@ -178,6 +179,20 @@ config USB_LJCA This driver can also be built as a module. If so, the module will be called usb-ljca. +config USB_USBIO + tristate "Intel USBIO Bridge support" + depends on USB && ACPI + select AUXILIARY_BUS + help + This adds support for Intel USBIO drivers. + This enables the USBIO bridge driver module in charge to talk + to the USB device. Additional drivers such as GPIO_USBIO and + I2C_USBIO must be enabled in order to use the device's full + functionality. + + This driver can also be built as a module. If so, the module + will be called usbio. + source "drivers/usb/misc/sisusbvga/Kconfig" config USB_LD @@ -231,8 +246,8 @@ config USB_EHSET_TEST_FIXTURE VID/PID pairs. This driver then initiates a corresponding test mode on the downstream port to which the test fixture is attached. - See <http://www.usb.org/developers/onthego/EHSET_v1.01.pdf> for more - information. + See <https://www.usb.org/sites/default/files/EHSET_v1.01%281%29.pdf> + for more information. config USB_ISIGHTFW tristate "iSight firmware loading support" diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 0cd5bc8f52fe..494ab0377f35 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_USB_EMI62) += emi62.o obj-$(CONFIG_USB_EZUSB_FX2) += ezusb.o obj-$(CONFIG_APPLE_MFI_FASTCHARGE) += apple-mfi-fastcharge.o obj-$(CONFIG_USB_LJCA) += usb-ljca.o +obj-$(CONFIG_USB_USBIO) += usbio.o obj-$(CONFIG_USB_IDMOUSE) += idmouse.o obj-$(CONFIG_USB_IOWARRIOR) += iowarrior.o obj-$(CONFIG_USB_ISIGHTFW) += isight_firmware.o diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c index 83079c414b4f..926419ca560f 100644 --- a/drivers/usb/misc/qcom_eud.c +++ b/drivers/usb/misc/qcom_eud.c @@ -15,6 +15,7 @@ #include <linux/slab.h> #include <linux/sysfs.h> #include <linux/usb/role.h> +#include <linux/firmware/qcom/qcom_scm.h> #define EUD_REG_INT1_EN_MASK 0x0024 #define EUD_REG_INT_STATUS_1 0x0044 @@ -34,7 +35,7 @@ struct eud_chip { struct device *dev; struct usb_role_switch *role_sw; void __iomem *base; - void __iomem *mode_mgr; + phys_addr_t mode_mgr; unsigned int int_status; int irq; bool enabled; @@ -43,18 +44,29 @@ struct eud_chip { static int enable_eud(struct eud_chip *priv) { + int ret; + + ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 1); + if (ret) + return ret; + writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN); writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE, priv->base + EUD_REG_INT1_EN_MASK); - writel(1, priv->mode_mgr + EUD_REG_EUD_EN2); return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE); } -static void disable_eud(struct eud_chip *priv) +static int disable_eud(struct eud_chip *priv) { + int ret; + + ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 0); + if (ret) + return ret; + writel(0, priv->base + EUD_REG_CSR_EUD_EN); - writel(0, priv->mode_mgr + EUD_REG_EUD_EN2); + return 0; } static ssize_t enable_show(struct device *dev, @@ -82,11 +94,12 @@ static ssize_t enable_store(struct device *dev, chip->enabled = enable; else disable_eud(chip); + } else { - disable_eud(chip); + ret = disable_eud(chip); } - return count; + return ret < 0 ? ret : count; } static DEVICE_ATTR_RW(enable); @@ -178,6 +191,7 @@ static void eud_role_switch_release(void *data) static int eud_probe(struct platform_device *pdev) { struct eud_chip *chip; + struct resource *res; int ret; chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); @@ -193,16 +207,16 @@ static int eud_probe(struct platform_device *pdev) ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip); if (ret) - return dev_err_probe(chip->dev, ret, - "failed to add role switch release action\n"); + return ret; chip->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(chip->base)) return PTR_ERR(chip->base); - chip->mode_mgr = devm_platform_ioremap_resource(pdev, 1); - if (IS_ERR(chip->mode_mgr)) - return PTR_ERR(chip->mode_mgr); + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return -ENODEV; + chip->mode_mgr = res->start; chip->irq = platform_get_irq(pdev, 0); if (chip->irq < 0) diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c index 4fb453ca5450..7c0778631bea 100644 --- a/drivers/usb/misc/usb251xb.c +++ b/drivers/usb/misc/usb251xb.c @@ -17,6 +17,7 @@ #include <linux/module.h> #include <linux/nls.h> #include <linux/of.h> +#include <linux/platform_device.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> @@ -242,15 +243,19 @@ static int usb251xb_check_dev_children(struct device *dev, void *child) static int usb251x_check_gpio_chip(struct usb251xb *hub) { struct gpio_chip *gc = gpiod_to_chip(hub->gpio_reset); - struct i2c_adapter *adap = hub->i2c->adapter; + struct i2c_adapter *adap; int ret; + if (!hub->i2c) + return 0; + if (!hub->gpio_reset) return 0; if (!gc) return -EINVAL; + adap = hub->i2c->adapter; ret = usb251xb_check_dev_children(&adap->dev, gc->parent); if (ret) { dev_err(hub->dev, "Reset GPIO chip is at the same i2c-bus\n"); @@ -271,7 +276,8 @@ static void usb251xb_reset(struct usb251xb *hub) if (!hub->gpio_reset) return; - i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); + if (hub->i2c) + i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); gpiod_set_value_cansleep(hub->gpio_reset, 1); usleep_range(1, 10); /* >=1us RESET_N asserted */ @@ -280,7 +286,8 @@ static void usb251xb_reset(struct usb251xb *hub) /* wait for hub recovery/stabilization */ usleep_range(500, 750); /* >=500us after RESET_N deasserted */ - i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); + if (hub->i2c) + i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); } static int usb251xb_connect(struct usb251xb *hub) @@ -289,6 +296,12 @@ static int usb251xb_connect(struct usb251xb *hub) int err, i; char i2c_wb[USB251XB_I2C_REG_SZ]; + if (!hub->i2c) { + usb251xb_reset(hub); + dev_info(dev, "hub is put in default configuration.\n"); + return 0; + } + memset(i2c_wb, 0, USB251XB_I2C_REG_SZ); if (hub->skip_config) { @@ -698,18 +711,13 @@ static int usb251xb_i2c_probe(struct i2c_client *i2c) return usb251xb_probe(hub); } -static int __maybe_unused usb251xb_suspend(struct device *dev) +static int usb251xb_suspend(struct usb251xb *hub) { - struct i2c_client *client = to_i2c_client(dev); - struct usb251xb *hub = i2c_get_clientdata(client); - return regulator_disable(hub->vdd); } -static int __maybe_unused usb251xb_resume(struct device *dev) +static int usb251xb_resume(struct usb251xb *hub) { - struct i2c_client *client = to_i2c_client(dev); - struct usb251xb *hub = i2c_get_clientdata(client); int err; err = regulator_enable(hub->vdd); @@ -719,7 +727,23 @@ static int __maybe_unused usb251xb_resume(struct device *dev) return usb251xb_connect(hub); } -static SIMPLE_DEV_PM_OPS(usb251xb_pm_ops, usb251xb_suspend, usb251xb_resume); +static int usb251xb_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb251xb *hub = i2c_get_clientdata(client); + + return usb251xb_suspend(hub); +} + +static int usb251xb_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb251xb *hub = i2c_get_clientdata(client); + + return usb251xb_resume(hub); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_i2c_pm_ops, usb251xb_i2c_suspend, usb251xb_i2c_resume); static const struct i2c_device_id usb251xb_id[] = { { "usb2422" }, @@ -739,13 +763,71 @@ static struct i2c_driver usb251xb_i2c_driver = { .driver = { .name = DRIVER_NAME, .of_match_table = usb251xb_of_match, - .pm = &usb251xb_pm_ops, + .pm = pm_sleep_ptr(&usb251xb_i2c_pm_ops), }, .probe = usb251xb_i2c_probe, .id_table = usb251xb_id, }; -module_i2c_driver(usb251xb_i2c_driver); +static int usb251xb_plat_probe(struct platform_device *pdev) +{ + struct usb251xb *hub; + + hub = devm_kzalloc(&pdev->dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + platform_set_drvdata(pdev, hub); + hub->dev = &pdev->dev; + + return usb251xb_probe(hub); +} + +static int usb251xb_plat_suspend(struct device *dev) +{ + return usb251xb_suspend(dev_get_drvdata(dev)); +} + +static int usb251xb_plat_resume(struct device *dev) +{ + return usb251xb_resume(dev_get_drvdata(dev)); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_plat_pm_ops, usb251xb_plat_suspend, usb251xb_plat_resume); + +static struct platform_driver usb251xb_plat_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = usb251xb_of_match, + .pm = pm_sleep_ptr(&usb251xb_plat_pm_ops), + }, + .probe = usb251xb_plat_probe, +}; + +static int __init usb251xb_init(void) +{ + int err; + + err = i2c_add_driver(&usb251xb_i2c_driver); + if (err) + return err; + + err = platform_driver_register(&usb251xb_plat_driver); + if (err) { + i2c_del_driver(&usb251xb_i2c_driver); + return err; + } + + return 0; +} +module_init(usb251xb_init); + +static void __exit usb251xb_exit(void) +{ + platform_driver_unregister(&usb251xb_plat_driver); + i2c_del_driver(&usb251xb_i2c_driver); +} +module_exit(usb251xb_exit); MODULE_AUTHOR("Richard Leitner <richard.leitner@skidata.com>"); MODULE_DESCRIPTION("USB251x/xBi USB 2.0 Hub Controller Driver"); diff --git a/drivers/usb/misc/usbio.c b/drivers/usb/misc/usbio.c new file mode 100644 index 000000000000..37644dddf157 --- /dev/null +++ b/drivers/usb/misc/usbio.c @@ -0,0 +1,749 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel USBIO Bridge driver + * + * Copyright (c) 2025 Intel Corporation. + * Copyright (c) 2025 Red Hat, Inc. + */ + +#include <linux/acpi.h> +#include <linux/auxiliary_bus.h> +#include <linux/byteorder/generic.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/lockdep.h> +#include <linux/mutex.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/usb.h> +#include <linux/usb/usbio.h> + +/************************************* + * USBIO Bridge Protocol Definitions * + *************************************/ + +/* USBIO Control Commands */ +#define USBIO_CTRLCMD_PROTVER 0 +#define USBIO_CTRLCMD_FWVER 1 +#define USBIO_CTRLCMD_HS 2 +#define USBIO_CTRLCMD_ENUMGPIO 16 +#define USBIO_CTRLCMD_ENUMI2C 17 + +/* USBIO Packet Flags */ +#define USBIO_PKTFLAG_ACK BIT(0) +#define USBIO_PKTFLAG_RSP BIT(1) +#define USBIO_PKTFLAG_CMP BIT(2) +#define USBIO_PKTFLAG_ERR BIT(3) + +#define USBIO_PKTFLAGS_REQRESP (USBIO_PKTFLAG_CMP | USBIO_PKTFLAG_ACK) + +#define USBIO_CTRLXFER_TIMEOUT 0 +#define USBIO_BULKXFER_TIMEOUT 100 + +struct usbio_protver { + u8 ver; +} __packed; + +struct usbio_fwver { + u8 major; + u8 minor; + __le16 patch; + __le16 build; +} __packed; + +/*********************************** + * USBIO Bridge Device Definitions * + ***********************************/ + +/** + * struct usbio_device - the usb device exposing IOs + * + * @dev: the device in the usb interface + * @udev: the detected usb device + * @intf: the usb interface + * @quirks: quirks + * @ctrl_mutex: protects ctrl_buf + * @ctrl_pipe: the control transfer pipe + * @ctrlbuf_len: the size of the control transfer pipe + * @ctrlbuf: the buffer used for control transfers + * @bulk_mutex: protects tx_buf, rx_buf and split bulk-transfers getting interrupted + * @tx_pipe: the bulk out pipe + * @txbuf_len: the size of the bulk out pipe + * @txbuf: the buffer used for bulk out transfers + * @rx_pipe: the bulk in pipe + * @rxbuf_len: the size of the bulk in pipe + * @rxdat_len: the data length at rx buffer + * @rxbuf: the buffer used for bulk in transfers + * @urb: the urb to read bulk pipe + * @done: completion object as request is done + * @cli_list: device's client list + * @nr_gpio_banks: Number of GPIO banks + * @gpios: GPIO bank descriptors + * @nr_gpio_banks: Number of I2C busses + * @gpios: I2C bank descriptors + */ +struct usbio_device { + struct device *dev; + struct usb_device *udev; + struct usb_interface *intf; + unsigned long quirks; + + struct mutex ctrl_mutex; + unsigned int ctrl_pipe; + u16 ctrlbuf_len; + void *ctrlbuf; + + struct mutex bulk_mutex; + unsigned int tx_pipe; + u16 txbuf_len; + void *txbuf; + + unsigned int rx_pipe; + u16 rxbuf_len; + u16 rxdat_len; + void *rxbuf; + struct urb *urb; + + struct completion done; + + struct list_head cli_list; + + unsigned int nr_gpio_banks; + struct usbio_gpio_bank_desc gpios[USBIO_MAX_GPIOBANKS]; + + unsigned int nr_i2c_buses; + struct usbio_i2c_bus_desc i2cs[USBIO_MAX_I2CBUSES]; +}; + +/** + * struct usbio_client - represents a usbio client + * + * @auxdev: auxiliary device object + * @mutex: protects @bridge + * @bridge: usbio bridge who service the client + * @link: usbio bridge clients list member + */ +struct usbio_client { + struct auxiliary_device auxdev; + struct mutex mutex; + struct usbio_device *bridge; + struct list_head link; +}; + +#define adev_to_client(adev) container_of_const(adev, struct usbio_client, auxdev) + +static int usbio_ctrl_msg(struct usbio_device *usbio, u8 type, u8 cmd, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len) +{ + u8 request = USB_TYPE_VENDOR | USB_RECIP_DEVICE; + struct usbio_ctrl_packet *cpkt; + unsigned int pipe; + u16 cpkt_len; + int ret; + + lockdep_assert_held(&usbio->ctrl_mutex); + + if ((obuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt))) || + (ibuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt)))) + return -EMSGSIZE; + + /* Prepare Control Packet Header */ + cpkt = usbio->ctrlbuf; + cpkt->header.type = type; + cpkt->header.cmd = cmd; + if (type == USBIO_PKTTYPE_CTRL || ibuf_len) + cpkt->header.flags = USBIO_PKTFLAGS_REQRESP; + else + cpkt->header.flags = USBIO_PKTFLAG_CMP; + cpkt->len = obuf_len; + + /* Copy the data */ + memcpy(cpkt->data, obuf, obuf_len); + + pipe = usb_sndctrlpipe(usbio->udev, usbio->ctrl_pipe); + cpkt_len = sizeof(*cpkt) + obuf_len; + ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_OUT, 0, 0, + cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT); + dev_dbg(usbio->dev, "control out %d hdr %*phN data %*phN\n", ret, + (int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data); + + if (ret != cpkt_len) { + dev_err(usbio->dev, "USB control out failed: %d\n", ret); + return (ret < 0) ? ret : -EPROTO; + } + + if (!(cpkt->header.flags & USBIO_PKTFLAG_ACK)) + return 0; + + pipe = usb_rcvctrlpipe(usbio->udev, usbio->ctrl_pipe); + cpkt_len = sizeof(*cpkt) + ibuf_len; + ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_IN, 0, 0, + cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT); + dev_dbg(usbio->dev, "control in %d hdr %*phN data %*phN\n", ret, + (int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data); + + if (ret < sizeof(*cpkt)) { + dev_err(usbio->dev, "USB control in failed: %d\n", ret); + return (ret < 0) ? ret : -EPROTO; + } + + if (cpkt->header.type != type || cpkt->header.cmd != cmd || + !(cpkt->header.flags & USBIO_PKTFLAG_RSP)) { + dev_err(usbio->dev, "Unexpected reply type: %u, cmd: %u, flags: %u\n", + cpkt->header.type, cpkt->header.cmd, cpkt->header.flags); + return -EPROTO; + } + + if (cpkt->header.flags & USBIO_PKTFLAG_ERR) + return -EREMOTEIO; + + if (ibuf_len < cpkt->len) + return -ENOSPC; + + memcpy(ibuf, cpkt->data, cpkt->len); + + return cpkt->len; +} + +int usbio_control_msg(struct auxiliary_device *adev, u8 type, u8 cmd, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + int ret; + + guard(mutex)(&client->mutex); + + usbio = client->bridge; + if (!usbio) + return -ENODEV; /* Disconnected */ + + ret = usb_autopm_get_interface(usbio->intf); + if (ret) + return ret; + + mutex_lock(&usbio->ctrl_mutex); + + ret = usbio_ctrl_msg(client->bridge, type, cmd, obuf, obuf_len, ibuf, ibuf_len); + + mutex_unlock(&usbio->ctrl_mutex); + usb_autopm_put_interface(usbio->intf); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(usbio_control_msg, "USBIO"); + +static void usbio_bulk_recv(struct urb *urb) +{ + struct usbio_bulk_packet *bpkt = urb->transfer_buffer; + struct usbio_device *usbio = urb->context; + + if (!urb->status) { + if (bpkt->header.flags & USBIO_PKTFLAG_RSP) { + usbio->rxdat_len = urb->actual_length; + complete(&usbio->done); + } + } else if (urb->status != -ENOENT) { + dev_err(usbio->dev, "Bulk in error %d\n", urb->status); + } + + usb_submit_urb(usbio->urb, GFP_ATOMIC); +} + +int usbio_bulk_msg(struct auxiliary_device *adev, u8 type, u8 cmd, bool last, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio = client->bridge; + struct usbio_bulk_packet *bpkt; + int ret, act = 0; + u16 bpkt_len; + + lockdep_assert_held(&client->mutex); + lockdep_assert_held(&usbio->bulk_mutex); + + if ((obuf_len > (usbio->txbuf_len - sizeof(*bpkt))) || + (ibuf_len > (usbio->txbuf_len - sizeof(*bpkt)))) + return -EMSGSIZE; + + if (ibuf_len) + reinit_completion(&usbio->done); + + /* If no data to send, skip to read */ + if (!obuf_len) + goto read; + + /* Prepare Bulk Packet Header */ + bpkt = usbio->txbuf; + bpkt->header.type = type; + bpkt->header.cmd = cmd; + if (!last) + bpkt->header.flags = 0; + else if (ibuf_len) + bpkt->header.flags = USBIO_PKTFLAGS_REQRESP; + else + bpkt->header.flags = USBIO_PKTFLAG_CMP; + bpkt->len = cpu_to_le16(obuf_len); + + /* Copy the data */ + memcpy(bpkt->data, obuf, obuf_len); + + bpkt_len = sizeof(*bpkt) + obuf_len; + ret = usb_bulk_msg(usbio->udev, usbio->tx_pipe, bpkt, bpkt_len, &act, + USBIO_BULKXFER_TIMEOUT); + dev_dbg(usbio->dev, "bulk out %d hdr %*phN data %*phN\n", act, + (int)sizeof(*bpkt), bpkt, obuf_len, bpkt->data); + + if (ret || act != bpkt_len) { + dev_err(usbio->dev, "Bulk out failed: %d\n", ret); + return ret ?: -EPROTO; + } + + if (!(bpkt->header.flags & USBIO_PKTFLAG_ACK)) + return obuf_len; + +read: + ret = wait_for_completion_timeout(&usbio->done, USBIO_BULKXFER_TIMEOUT); + if (ret <= 0) { + dev_err(usbio->dev, "Bulk in wait failed: %d\n", ret); + return ret ?: -ETIMEDOUT; + } + + act = usbio->rxdat_len; + bpkt = usbio->rxbuf; + bpkt_len = le16_to_cpu(bpkt->len); + dev_dbg(usbio->dev, "bulk in %d hdr %*phN data %*phN\n", act, + (int)sizeof(*bpkt), bpkt, bpkt_len, bpkt->data); + + /* + * Unsupported bulk commands get only an usbio_packet_header with + * the error flag set as reply. Return -EPIPE for this case. + */ + if (act == sizeof(struct usbio_packet_header) && + (bpkt->header.flags & USBIO_PKTFLAG_ERR)) + return -EPIPE; + + if (act < sizeof(*bpkt)) { + dev_err(usbio->dev, "Bulk in short read: %d\n", act); + return -EPROTO; + } + + if (bpkt->header.type != type || bpkt->header.cmd != cmd || + !(bpkt->header.flags & USBIO_PKTFLAG_RSP)) { + dev_err(usbio->dev, + "Unexpected bulk in type 0x%02x cmd 0x%02x flags 0x%02x\n", + bpkt->header.type, bpkt->header.cmd, bpkt->header.flags); + return -EPROTO; + } + + if (bpkt->header.flags & USBIO_PKTFLAG_ERR) + return -EREMOTEIO; + + if (ibuf_len < bpkt_len) + return -ENOSPC; + + memcpy(ibuf, bpkt->data, bpkt_len); + + return bpkt_len; +} +EXPORT_SYMBOL_NS_GPL(usbio_bulk_msg, "USBIO"); + +int usbio_acquire(struct auxiliary_device *adev) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + int ret; + + mutex_lock(&client->mutex); + + usbio = client->bridge; + if (!usbio) { + ret = -ENODEV; /* Disconnected */ + goto err_unlock; + } + + ret = usb_autopm_get_interface(usbio->intf); + if (ret) + goto err_unlock; + + mutex_lock(&usbio->bulk_mutex); + + /* Leave client locked until release to avoid abba deadlock issues */ + return 0; + +err_unlock: + mutex_unlock(&client->mutex); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(usbio_acquire, "USBIO"); + +void usbio_release(struct auxiliary_device *adev) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio = client->bridge; + + lockdep_assert_held(&client->mutex); + + mutex_unlock(&usbio->bulk_mutex); + usb_autopm_put_interface(usbio->intf); + mutex_unlock(&client->mutex); +} +EXPORT_SYMBOL_NS_GPL(usbio_release, "USBIO"); + +void usbio_get_txrxbuf_len(struct auxiliary_device *adev, u16 *txbuf_len, u16 *rxbuf_len) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + + guard(mutex)(&client->mutex); + + usbio = client->bridge; + if (!usbio) + return; /* Disconnected */ + + *txbuf_len = usbio->txbuf_len; + *rxbuf_len = usbio->rxbuf_len; +} +EXPORT_SYMBOL_NS_GPL(usbio_get_txrxbuf_len, "USBIO"); + +unsigned long usbio_get_quirks(struct auxiliary_device *adev) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + + guard(mutex)(&client->mutex); + + usbio = client->bridge; + if (!usbio) + return 0; /* Disconnected */ + + return usbio->quirks; +} +EXPORT_SYMBOL_NS_GPL(usbio_get_quirks, "USBIO"); + +static void usbio_auxdev_release(struct device *dev) +{ + struct auxiliary_device *adev = to_auxiliary_dev(dev); + struct usbio_client *client = adev_to_client(adev); + + mutex_destroy(&client->mutex); + kfree(client); +} + +static int usbio_add_client(struct usbio_device *usbio, char *name, u8 id, void *data) +{ + struct usbio_client *client; + struct auxiliary_device *adev; + int ret; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; + + mutex_init(&client->mutex); + client->bridge = usbio; + adev = &client->auxdev; + adev->name = name; + adev->id = id; + + adev->dev.parent = usbio->dev; + adev->dev.platform_data = data; + adev->dev.release = usbio_auxdev_release; + + ret = auxiliary_device_init(adev); + if (ret) { + usbio_auxdev_release(&adev->dev); + return ret; + } + + ret = auxiliary_device_add(adev); + if (ret) { + auxiliary_device_uninit(adev); + return ret; + } + + list_add_tail(&client->link, &usbio->cli_list); + + return 0; +} + +static int usbio_enum_gpios(struct usbio_device *usbio) +{ + struct usbio_gpio_bank_desc *gpio = usbio->gpios; + + dev_dbg(usbio->dev, "GPIO Banks: %d\n", usbio->nr_gpio_banks); + + for (unsigned int i = 0; i < usbio->nr_gpio_banks; i++) + dev_dbg(usbio->dev, "\tBank%d[%d] map: %#08x\n", + gpio[i].id, gpio[i].pins, gpio[i].bmap); + + usbio_add_client(usbio, USBIO_GPIO_CLIENT, 0, gpio); + + return 0; +} + +static int usbio_enum_i2cs(struct usbio_device *usbio) +{ + struct usbio_i2c_bus_desc *i2c = usbio->i2cs; + + dev_dbg(usbio->dev, "I2C Busses: %d\n", usbio->nr_i2c_buses); + + for (unsigned int i = 0; i < usbio->nr_i2c_buses; i++) { + dev_dbg(usbio->dev, "\tBus%d caps: %#02x\n", i2c[i].id, i2c[i].caps); + usbio_add_client(usbio, USBIO_I2C_CLIENT, i, &i2c[i]); + } + + return 0; +} + +static int usbio_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct usbio_device *usbio = usb_get_intfdata(intf); + + usb_kill_urb(usbio->urb); + + return 0; +} + +static int usbio_resume(struct usb_interface *intf) +{ + struct usbio_device *usbio = usb_get_intfdata(intf); + + return usb_submit_urb(usbio->urb, GFP_KERNEL); +} + +static void usbio_disconnect(struct usb_interface *intf) +{ + struct usbio_device *usbio = usb_get_intfdata(intf); + struct usbio_client *client; + + /* Wakeup any clients waiting for a reply */ + usbio->rxdat_len = 0; + complete(&usbio->done); + + /* Let clients know the bridge is gone */ + list_for_each_entry(client, &usbio->cli_list, link) { + mutex_lock(&client->mutex); + client->bridge = NULL; + mutex_unlock(&client->mutex); + } + + /* From here on clients will no longer touch struct usbio_device */ + usb_kill_urb(usbio->urb); + usb_free_urb(usbio->urb); + + list_for_each_entry_reverse(client, &usbio->cli_list, link) { + auxiliary_device_delete(&client->auxdev); + auxiliary_device_uninit(&client->auxdev); + } +} + +static int usbio_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *ep_in, *ep_out; + struct device *dev = &intf->dev; + struct usbio_protver protver; + struct usbio_device *usbio; + struct usbio_fwver fwver; + int ret; + + usbio = devm_kzalloc(dev, sizeof(*usbio), GFP_KERNEL); + if (!usbio) + return -ENOMEM; + + ret = devm_mutex_init(dev, &usbio->ctrl_mutex); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &usbio->bulk_mutex); + if (ret) + return ret; + + usbio->dev = dev; + usbio->udev = udev; + usbio->intf = intf; + usbio->quirks = id ? id->driver_info : 0; + init_completion(&usbio->done); + INIT_LIST_HEAD(&usbio->cli_list); + usb_set_intfdata(intf, usbio); + + usbio->ctrl_pipe = usb_endpoint_num(&udev->ep0.desc); + usbio->ctrlbuf_len = usb_maxpacket(udev, usbio->ctrl_pipe); + usbio->ctrlbuf = devm_kzalloc(dev, usbio->ctrlbuf_len, GFP_KERNEL); + if (!usbio->ctrlbuf) + return -ENOMEM; + + /* Find the first bulk-in and bulk-out endpoints */ + ret = usb_find_common_endpoints(intf->cur_altsetting, &ep_in, &ep_out, + NULL, NULL); + if (ret) { + dev_err(dev, "Cannot find bulk endpoints: %d\n", ret); + return ret; + } + + usbio->tx_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(ep_out)); + + if (usbio->quirks & USBIO_QUIRK_BULK_MAXP_63) + usbio->txbuf_len = 63; + else + usbio->txbuf_len = usb_endpoint_maxp(ep_out); + + usbio->txbuf = devm_kzalloc(dev, usbio->txbuf_len, GFP_KERNEL); + if (!usbio->txbuf) + return -ENOMEM; + + usbio->rx_pipe = usb_rcvbulkpipe(udev, usb_endpoint_num(ep_in)); + + if (usbio->quirks & USBIO_QUIRK_BULK_MAXP_63) + usbio->rxbuf_len = 63; + else + usbio->rxbuf_len = usb_endpoint_maxp(ep_in); + + usbio->rxbuf = devm_kzalloc(dev, usbio->rxbuf_len, GFP_KERNEL); + if (!usbio->rxbuf) + return -ENOMEM; + + usbio->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!usbio->urb) + return -ENOMEM; + + usb_fill_bulk_urb(usbio->urb, udev, usbio->rx_pipe, usbio->rxbuf, + usbio->rxbuf_len, usbio_bulk_recv, usbio); + ret = usb_submit_urb(usbio->urb, GFP_KERNEL); + if (ret) + return dev_err_probe(dev, ret, "Submitting usb urb\n"); + + mutex_lock(&usbio->ctrl_mutex); + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_HS, NULL, 0, NULL, 0); + if (ret < 0) + goto err_unlock; + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_PROTVER, NULL, 0, + &protver, sizeof(protver)); + if (ret < 0) + goto err_unlock; + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_FWVER, NULL, 0, + &fwver, sizeof(fwver)); + if (ret < 0) + goto err_unlock; + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_ENUMGPIO, NULL, 0, + usbio->gpios, sizeof(usbio->gpios)); + if (ret < 0 || ret % sizeof(struct usbio_gpio_bank_desc)) { + ret = (ret < 0) ? ret : -EPROTO; + goto err_unlock; + } + usbio->nr_gpio_banks = ret / sizeof(struct usbio_gpio_bank_desc); + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_ENUMI2C, NULL, 0, + usbio->i2cs, sizeof(usbio->i2cs)); + if (ret < 0 || ret % sizeof(struct usbio_i2c_bus_desc)) { + ret = (ret < 0) ? ret : -EPROTO; + goto err_unlock; + } + usbio->nr_i2c_buses = ret / sizeof(struct usbio_i2c_bus_desc); + + mutex_unlock(&usbio->ctrl_mutex); + + dev_dbg(dev, "ProtVer(BCD): %02x FwVer: %d.%d.%d.%d\n", + protver.ver, fwver.major, fwver.minor, + le16_to_cpu(fwver.patch), le16_to_cpu(fwver.build)); + + usbio_enum_gpios(usbio); + usbio_enum_i2cs(usbio); + + return 0; + +err_unlock: + mutex_unlock(&usbio->ctrl_mutex); + usb_kill_urb(usbio->urb); + usb_free_urb(usbio->urb); + + return ret; +} + +static const struct usb_device_id usbio_table[] = { + { USB_DEVICE(0x2ac1, 0x20c1), /* Lattice NX40 */ + .driver_info = USBIO_QUIRK_I2C_MAX_RW_LEN_52 }, + { USB_DEVICE(0x2ac1, 0x20c9), /* Lattice NX33 */ + .driver_info = USBIO_QUIRK_I2C_NO_INIT_ACK | USBIO_QUIRK_I2C_MAX_RW_LEN_52 | + USBIO_QUIRK_I2C_ALLOW_400KHZ }, + { USB_DEVICE(0x2ac1, 0x20cb) }, /* Lattice NX33U */ + { USB_DEVICE(0x06cb, 0x0701), /* Synaptics Sabre */ + .driver_info = USBIO_QUIRK_BULK_MAXP_63 | USBIO_QUIRK_I2C_USE_CHUNK_LEN }, + { } +}; +MODULE_DEVICE_TABLE(usb, usbio_table); + +static struct usb_driver usbio_driver = { + .name = "usbio-bridge", + .probe = usbio_probe, + .disconnect = usbio_disconnect, + .suspend = usbio_suspend, + .resume = usbio_resume, + .id_table = usbio_table, + .supports_autosuspend = 1, +}; +module_usb_driver(usbio_driver); + +struct usbio_match_ids_walk_data { + struct acpi_device *adev; + const struct acpi_device_id *hids; + unsigned int id; +}; + +static int usbio_match_device_ids(struct acpi_device *adev, void *data) +{ + struct usbio_match_ids_walk_data *wd = data; + unsigned int id = 0; + char *uid; + + if (acpi_match_device_ids(adev, wd->hids)) + return 0; + + uid = acpi_device_uid(adev); + if (uid) { + for (int i = 0; i < strlen(uid); i++) { + if (!kstrtouint(&uid[i], 10, &id)) + break; + } + } + + if (!uid || wd->id == id) { + wd->adev = adev; + return 1; + } + + return 0; +} + +void usbio_acpi_bind(struct auxiliary_device *adev, const struct acpi_device_id *hids) +{ + struct device *dev = &adev->dev; + struct acpi_device *parent; + struct usbio_match_ids_walk_data wd = { + .adev = NULL, + .hids = hids, + .id = adev->id, + }; + + parent = ACPI_COMPANION(dev->parent); + if (!parent) + return; + + acpi_dev_for_each_child(parent, usbio_match_device_ids, &wd); + if (wd.adev) + ACPI_COMPANION_SET(dev, wd.adev); +} +EXPORT_SYMBOL_NS_GPL(usbio_acpi_bind, "USBIO"); + +MODULE_DESCRIPTION("Intel USBIO Bridge driver"); +MODULE_AUTHOR("Israel Cepeda <israel.a.cepeda.lopez@intel.com>"); +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c index c93b43f5bc46..e713fc5964b1 100644 --- a/drivers/usb/mon/mon_bin.c +++ b/drivers/usb/mon/mon_bin.c @@ -68,18 +68,20 @@ * The magic limit was calculated so that it allows the monitoring * application to pick data once in two ticks. This way, another application, * which presumably drives the bus, gets to hog CPU, yet we collect our data. - * If HZ is 100, a 480 mbit/s bus drives 614 KB every jiffy. USB has an - * enormous overhead built into the bus protocol, so we need about 1000 KB. + * + * Originally, for a 480 Mbit/s bus this required a buffer of about 1 MB. For + * modern 20 Gbps buses, this value increases to over 50 MB. The maximum + * buffer size is set to 64 MiB to accommodate this. * * This is still too much for most cases, where we just snoop a few * descriptor fetches for enumeration. So, the default is a "reasonable" - * amount for systems with HZ=250 and incomplete bus saturation. + * amount for typical, low-throughput use cases. * * XXX What about multi-megabyte URBs which take minutes to transfer? */ -#define BUFF_MAX CHUNK_ALIGN(1200*1024) -#define BUFF_DFL CHUNK_ALIGN(300*1024) -#define BUFF_MIN CHUNK_ALIGN(8*1024) +#define BUFF_MAX CHUNK_ALIGN(64*1024*1024) +#define BUFF_DFL CHUNK_ALIGN(300*1024) +#define BUFF_MIN CHUNK_ALIGN(8*1024) /* * The per-event API header (2 per URB). diff --git a/drivers/usb/musb/musb_dsps.c b/drivers/usb/musb/musb_dsps.c index 12f587ab8511..a08ce96c08d3 100644 --- a/drivers/usb/musb/musb_dsps.c +++ b/drivers/usb/musb/musb_dsps.c @@ -839,7 +839,7 @@ static int dsps_setup_optional_vbus_irq(struct platform_device *pdev, { int error; - glue->vbus_irq = platform_get_irq_byname(pdev, "vbus"); + glue->vbus_irq = platform_get_irq_byname_optional(pdev, "vbus"); if (glue->vbus_irq == -EPROBE_DEFER) return -EPROBE_DEFER; diff --git a/drivers/usb/phy/phy-twl6030-usb.c b/drivers/usb/phy/phy-twl6030-usb.c index 49d79c1257f3..8c09db750bfd 100644 --- a/drivers/usb/phy/phy-twl6030-usb.c +++ b/drivers/usb/phy/phy-twl6030-usb.c @@ -328,9 +328,8 @@ static int twl6030_set_vbus(struct phy_companion *comparator, bool enabled) static int twl6030_usb_probe(struct platform_device *pdev) { - u32 ret; struct twl6030_usb *twl; - int status, err; + int status, err, ret; struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 18a6ef4dce51..8f536f2c500f 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -580,6 +580,10 @@ static const struct of_device_id usbhs_of_match[] = { .data = &usbhs_rzg2l_plat_info, }, { + .compatible = "renesas,usbhs-r9a09g077", + .data = &usbhs_rzg2l_plat_info, + }, + { .compatible = "renesas,rcar-gen2-usbhs", .data = &usbhs_rcar_gen2_plat_info, }, diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index fc869b7f803f..62e984d20e59 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2114,6 +2114,12 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */ .driver_info = RSVD(7) }, + { USB_DEVICE(0x1e0e, 0x9071), /* Simcom SIM8230 RMNET mode */ + .driver_info = RSVD(3) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9078, 0xff), /* Simcom SIM8230 ECM mode */ + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x907b, 0xff), /* Simcom SIM8230 RNDIS mode */ + .driver_info = RSVD(5) }, { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9205, 0xff) }, /* Simcom SIM7070/SIM7080/SIM7090 AT+ECM mode */ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9206, 0xff) }, /* Simcom SIM7070/SIM7080/SIM7090 AT-only mode */ { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_X060S_X200), diff --git a/drivers/usb/serial/oti6858.c b/drivers/usb/serial/oti6858.c index 24068368892c..bd206cb9cc08 100644 --- a/drivers/usb/serial/oti6858.c +++ b/drivers/usb/serial/oti6858.c @@ -106,7 +106,7 @@ struct oti6858_control_pkt { #define PIN_DTR 0x04 /* output pin */ #define PIN_RI 0x02 /* input pin, active low */ #define PIN_DCD 0x01 /* input pin, active low */ - u8 rx_bytes_avail; /* number of bytes in rx buffer */; + u8 rx_bytes_avail; /* number of bytes in rx buffer */ }; #define OTI6858_CTRL_PKT_SIZE sizeof(struct oti6858_control_pkt) diff --git a/drivers/usb/storage/realtek_cr.c b/drivers/usb/storage/realtek_cr.c index cb5bbb19060e..3cc243956fd4 100644 --- a/drivers/usb/storage/realtek_cr.c +++ b/drivers/usb/storage/realtek_cr.c @@ -260,8 +260,8 @@ static int rts51x_bulk_transport(struct us_data *us, u8 lun, * try to compute the actual residue, based on how much data * was really transferred and what the device tells us */ - if (residue) - residue = residue < buf_len ? residue : buf_len; + if (residue > buf_len) + residue = buf_len; if (act_len) *act_len = buf_len - residue; diff --git a/drivers/usb/typec/mux/tusb1046.c b/drivers/usb/typec/mux/tusb1046.c index b4f45c217b59..3c1a4551c2fb 100644 --- a/drivers/usb/typec/mux/tusb1046.c +++ b/drivers/usb/typec/mux/tusb1046.c @@ -129,7 +129,7 @@ static int tusb1046_i2c_probe(struct i2c_client *client) priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) - return dev_err_probe(dev, -ENOMEM, "failed to allocate driver data\n"); + return -ENOMEM; priv->client = client; diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c index 18303b34594b..c8b1463e6e8b 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c @@ -567,7 +567,7 @@ int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev, if (!res->nr_irqs || res->nr_irqs > PMIC_PDPHY_MAX_IRQS) return -EINVAL; - irq_data = devm_kzalloc(dev, sizeof(*irq_data) * res->nr_irqs, + irq_data = devm_kcalloc(dev, res->nr_irqs, sizeof(*irq_data), GFP_KERNEL); if (!irq_data) return -ENOMEM; diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c index 4fc83dcfae64..8051eaa46991 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c @@ -713,7 +713,7 @@ int qcom_pmic_typec_port_probe(struct platform_device *pdev, if (!res->nr_irqs || res->nr_irqs > PMIC_TYPEC_MAX_IRQS) return -EINVAL; - irq_data = devm_kzalloc(dev, sizeof(*irq_data) * res->nr_irqs, + irq_data = devm_kcalloc(dev, res->nr_irqs, sizeof(*irq_data), GFP_KERNEL); if (!irq_data) return -ENOMEM; diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index a56e31b20c21..2a951c585e92 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -949,6 +949,8 @@ static int tcpci_probe(struct i2c_client *client) if (err < 0) goto unregister_port; + device_set_wakeup_capable(chip->tcpci->dev, true); + return 0; unregister_port: @@ -969,6 +971,36 @@ static void tcpci_remove(struct i2c_client *client) tcpci_unregister_port(chip->tcpci); } +static int tcpci_suspend(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct tcpci_chip *chip = i2c_get_clientdata(i2c); + int ret; + + if (device_may_wakeup(dev)) + ret = enable_irq_wake(i2c->irq); + else + ret = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, 0); + + return ret; +} + +static int tcpci_resume(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct tcpci_chip *chip = i2c_get_clientdata(i2c); + int ret; + + if (device_may_wakeup(dev)) + ret = disable_irq_wake(i2c->irq); + else + ret = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, chip->tcpci->alert_mask); + + return ret; +} + +DEFINE_SIMPLE_DEV_PM_OPS(tcpci_pm_ops, tcpci_suspend, tcpci_resume); + static const struct i2c_device_id tcpci_id[] = { { "tcpci" }, { } @@ -987,6 +1019,7 @@ MODULE_DEVICE_TABLE(of, tcpci_of_match); static struct i2c_driver tcpci_i2c_driver = { .driver = { .name = "tcpci", + .pm = pm_sleep_ptr(&tcpci_pm_ops), .of_match_table = of_match_ptr(tcpci_of_match), }, .probe = tcpci_probe, diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index dcf141ada078..2b1049c9a6f3 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -16,6 +16,9 @@ #include <linux/interrupt.h> #include <linux/usb/typec.h> #include <linux/usb/typec_altmode.h> +#include <linux/usb/typec_dp.h> +#include <linux/usb/typec_mux.h> +#include <linux/usb/typec_tbt.h> #include <linux/usb/role.h> #include <linux/workqueue.h> #include <linux/firmware.h> @@ -35,14 +38,18 @@ #define TPS_REG_INT_MASK2 0x17 #define TPS_REG_INT_CLEAR1 0x18 #define TPS_REG_INT_CLEAR2 0x19 -#define TPS_REG_SYSTEM_POWER_STATE 0x20 #define TPS_REG_STATUS 0x1a +#define TPS_REG_SYSTEM_POWER_STATE 0x20 +#define TPS_REG_USB4_STATUS 0x24 #define TPS_REG_SYSTEM_CONF 0x28 #define TPS_REG_CTRL_CONF 0x29 #define TPS_REG_BOOT_STATUS 0x2D #define TPS_REG_POWER_STATUS 0x3f #define TPS_REG_PD_STATUS 0x40 #define TPS_REG_RX_IDENTITY_SOP 0x48 +#define TPS_REG_CF_VID_STATUS 0x5e +#define TPS_REG_DP_SID_STATUS 0x58 +#define TPS_REG_INTEL_VID_STATUS 0x59 #define TPS_REG_DATA_STATUS 0x5f #define TPS_REG_SLEEP_CONF 0x70 @@ -85,10 +92,38 @@ struct tps6598x_rx_identity_reg { struct usb_pd_identity identity; } __packed; +/* TPS_REG_USB4_STATUS */ +struct tps6598x_usb4_status_reg { + u8 mode_status; + __le32 eudo; + __le32 unknown; +} __packed; + +/* TPS_REG_DP_SID_STATUS */ +struct tps6598x_dp_sid_status_reg { + u8 mode_status; + __le32 status_tx; + __le32 status_rx; + __le32 configure; + __le32 mode_data; +} __packed; + +/* TPS_REG_INTEL_VID_STATUS */ +struct tps6598x_intel_vid_status_reg { + u8 mode_status; + __le32 attention_vdo; + __le16 enter_vdo; + __le16 device_mode; + __le16 cable_mode; +} __packed; + /* Standard Task return codes */ #define TPS_TASK_TIMEOUT 1 #define TPS_TASK_REJECTED 3 +/* Debounce delay for mode changes, in milliseconds */ +#define CD321X_DEBOUNCE_DELAY_MS 500 + enum { TPS_MODE_APP, TPS_MODE_BOOT, @@ -112,12 +147,20 @@ struct tps6598x; struct tipd_data { irq_handler_t irq_handler; + u64 irq_mask1; + size_t tps_struct_size; + void (*remove)(struct tps6598x *tps); int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node); + void (*unregister_port)(struct tps6598x *tps); + void (*trace_data_status)(u32 status); void (*trace_power_status)(u16 status); void (*trace_status)(u32 status); int (*apply_patch)(struct tps6598x *tps); int (*init)(struct tps6598x *tps); + int (*switch_power_state)(struct tps6598x *tps, u8 target_state); + bool (*read_data_status)(struct tps6598x *tps); int (*reset)(struct tps6598x *tps); + int (*connect)(struct tps6598x *tps, u32 status); }; struct tps6598x { @@ -139,12 +182,42 @@ struct tps6598x { int wakeup; u32 status; /* status reg */ + u32 data_status; u16 pwr_status; struct delayed_work wq_poll; const struct tipd_data *data; }; +struct cd321x_status { + u32 status; + u32 pwr_status; + u32 data_status; + u32 status_changed; + struct usb_pd_identity partner_identity; + struct tps6598x_dp_sid_status_reg dp_sid_status; + struct tps6598x_intel_vid_status_reg intel_vid_status; + struct tps6598x_usb4_status_reg usb4_status; +}; + +struct cd321x { + struct tps6598x tps; + + struct tps6598x_dp_sid_status_reg dp_sid_status; + struct tps6598x_intel_vid_status_reg intel_vid_status; + struct tps6598x_usb4_status_reg usb4_status; + + struct typec_altmode *port_altmode_dp; + struct typec_altmode *port_altmode_tbt; + + struct typec_mux *mux; + struct typec_mux_state state; + + struct cd321x_status update_status; + struct delayed_work update_work; + struct usb_pd_identity cur_partner_identity; +}; + static enum power_supply_property tps6598x_psy_props[] = { POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_ONLINE, @@ -490,7 +563,45 @@ static bool tps6598x_read_data_status(struct tps6598x *tps) dev_err(tps->dev, "failed to read data status: %d\n", ret); return false; } - trace_tps6598x_data_status(data_status); + tps->data_status = data_status; + + if (tps->data->trace_data_status) + tps->data->trace_data_status(data_status); + + return true; +} + +static bool cd321x_read_data_status(struct tps6598x *tps) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + int ret; + + ret = tps6598x_read_data_status(tps); + if (ret < 0) + return false; + + if (tps->data_status & TPS_DATA_STATUS_DP_CONNECTION) { + ret = tps6598x_block_read(tps, TPS_REG_DP_SID_STATUS, + &cd321x->dp_sid_status, sizeof(cd321x->dp_sid_status)); + if (ret) + dev_err(tps->dev, "Failed to read DP SID Status: %d\n", + ret); + } + + if (tps->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { + ret = tps6598x_block_read(tps, TPS_REG_INTEL_VID_STATUS, + &cd321x->intel_vid_status, sizeof(cd321x->intel_vid_status)); + if (ret) + dev_err(tps->dev, "Failed to read Intel VID Status: %d\n", ret); + } + + if (tps->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) { + ret = tps6598x_block_read(tps, TPS_REG_USB4_STATUS, + &cd321x->usb4_status, sizeof(cd321x->usb4_status)); + if (ret) + dev_err(tps->dev, + "Failed to read USB4 Status: %d\n", ret); + } return true; } @@ -526,6 +637,233 @@ static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) } } +static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status *st) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + + if (!(st->data_status & TPS_DATA_STATUS_DATA_CONNECTION)) { + if (cd321x->state.mode == TYPEC_STATE_SAFE) + return; + cd321x->state.alt = NULL; + cd321x->state.mode = TYPEC_STATE_SAFE; + cd321x->state.data = NULL; + typec_mux_set(cd321x->mux, &cd321x->state); + } else if (st->data_status & TPS_DATA_STATUS_DP_CONNECTION) { + struct typec_displayport_data dp_data; + unsigned long mode; + + switch (TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT(st->data_status)) { + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A: + mode = TYPEC_DP_STATE_A; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B: + mode = TYPEC_DP_STATE_B; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_C: + mode = TYPEC_DP_STATE_C; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_D: + mode = TYPEC_DP_STATE_D; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_E: + mode = TYPEC_DP_STATE_E; + break; + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_F: + mode = TYPEC_DP_STATE_F; + break; + default: + dev_err(tps->dev, "Invalid DP pin assignment\n"); + return; + } + + if (cd321x->state.alt == cd321x->port_altmode_dp && + cd321x->state.mode == mode) { + return; + } + + dp_data.status = le32_to_cpu(st->dp_sid_status.status_rx); + dp_data.conf = le32_to_cpu(st->dp_sid_status.configure); + cd321x->state.alt = cd321x->port_altmode_dp; + cd321x->state.data = &dp_data; + cd321x->state.mode = mode; + typec_mux_set(cd321x->mux, &cd321x->state); + } else if (st->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { + struct typec_thunderbolt_data tbt_data; + + if (cd321x->state.alt == cd321x->port_altmode_tbt && + cd321x->state.mode == TYPEC_TBT_MODE) + return; + + tbt_data.cable_mode = le16_to_cpu(st->intel_vid_status.cable_mode); + tbt_data.device_mode = le16_to_cpu(st->intel_vid_status.device_mode); + tbt_data.enter_vdo = le16_to_cpu(st->intel_vid_status.enter_vdo); + cd321x->state.alt = cd321x->port_altmode_tbt; + cd321x->state.mode = TYPEC_TBT_MODE; + cd321x->state.data = &tbt_data; + typec_mux_set(cd321x->mux, &cd321x->state); + } else if (st->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) { + struct enter_usb_data eusb_data; + + if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_MODE_USB4) + return; + + eusb_data.eudo = le32_to_cpu(st->usb4_status.eudo); + eusb_data.active_link_training = + !!(st->data_status & TPS_DATA_STATUS_ACTIVE_LINK_TRAIN); + + cd321x->state.alt = NULL; + cd321x->state.data = &eusb_data; + cd321x->state.mode = TYPEC_MODE_USB4; + typec_mux_set(cd321x->mux, &cd321x->state); + } else { + if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_STATE_USB) + return; + cd321x->state.alt = NULL; + cd321x->state.mode = TYPEC_STATE_USB; + cd321x->state.data = NULL; + typec_mux_set(cd321x->mux, &cd321x->state); + } + + /* Clear data since it's no longer used after typec_mux_set and points to the stack */ + cd321x->state.data = NULL; +} + +static void cd321x_update_work(struct work_struct *work) +{ + struct cd321x *cd321x = container_of(to_delayed_work(work), + struct cd321x, update_work); + struct tps6598x *tps = &cd321x->tps; + struct cd321x_status st; + + guard(mutex)(&tps->lock); + + st = cd321x->update_status; + cd321x->update_status.status_changed = 0; + + bool old_connected = !!tps->partner; + bool new_connected = st.status & TPS_STATUS_PLUG_PRESENT; + bool was_disconnected = st.status_changed & TPS_STATUS_PLUG_PRESENT; + + bool usb_connection = st.data_status & + (TPS_DATA_STATUS_USB2_CONNECTION | TPS_DATA_STATUS_USB3_CONNECTION); + + enum usb_role old_role = usb_role_switch_get_role(tps->role_sw); + enum usb_role new_role = USB_ROLE_NONE; + enum typec_pwr_opmode pwr_opmode = TYPEC_PWR_MODE_USB; + enum typec_orientation orientation = TYPEC_ORIENTATION_NONE; + + if (usb_connection) { + if (tps->data_status & TPS_DATA_STATUS_USB_DATA_ROLE) + new_role = USB_ROLE_DEVICE; + else + new_role = USB_ROLE_HOST; + } + + if (new_connected) { + pwr_opmode = TPS_POWER_STATUS_PWROPMODE(st.pwr_status); + orientation = TPS_STATUS_TO_UPSIDE_DOWN(st.status) ? + TYPEC_ORIENTATION_REVERSE : TYPEC_ORIENTATION_NORMAL; + } + + bool is_pd = pwr_opmode == TYPEC_PWR_MODE_PD; + bool partner_changed = old_connected && new_connected && + (was_disconnected || + (is_pd && memcmp(&st.partner_identity, + &cd321x->cur_partner_identity, sizeof(struct usb_pd_identity)))); + + /* If we are switching from an active role, transition to USB_ROLE_NONE first */ + if (old_role != USB_ROLE_NONE && (new_role != old_role || was_disconnected)) + usb_role_switch_set_role(tps->role_sw, USB_ROLE_NONE); + + /* Process partner disconnection or change */ + if (!new_connected || partner_changed) { + if (!IS_ERR(tps->partner)) + typec_unregister_partner(tps->partner); + tps->partner = NULL; + } + + /* If there was a disconnection, set PHY to off */ + if (!new_connected || was_disconnected) { + cd321x->state.alt = NULL; + cd321x->state.mode = TYPEC_STATE_SAFE; + cd321x->state.data = NULL; + typec_set_mode(tps->port, TYPEC_STATE_SAFE); + } + + /* Update Type-C properties */ + typec_set_pwr_opmode(tps->port, pwr_opmode); + typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(st.status)); + typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(st.status)); + typec_set_orientation(tps->port, orientation); + typec_set_data_role(tps->port, TPS_STATUS_TO_TYPEC_DATAROLE(st.status)); + power_supply_changed(tps->psy); + + /* If the plug is disconnected, we are done */ + if (!new_connected) + return; + + /* Set up partner if we were previously disconnected (or changed). */ + if (!tps->partner) { + struct typec_partner_desc desc; + + desc.usb_pd = is_pd; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ + desc.identity = NULL; + + if (desc.usb_pd) + desc.identity = &st.partner_identity; + + tps->partner = typec_register_partner(tps->port, &desc); + if (IS_ERR(tps->partner)) + dev_warn(tps->dev, "%s: failed to register partnet\n", __func__); + + if (desc.identity) { + typec_partner_set_identity(tps->partner); + cd321x->cur_partner_identity = st.partner_identity; + } + } + + /* Update the TypeC MUX/PHY state */ + cd321x_typec_update_mode(tps, &st); + + /* Launch the USB role switch */ + usb_role_switch_set_role(tps->role_sw, new_role); + + power_supply_changed(tps->psy); +} + +static void cd321x_queue_status(struct cd321x *cd321x) +{ + cd321x->update_status.status_changed |= cd321x->update_status.status ^ cd321x->tps.status; + + cd321x->update_status.status = cd321x->tps.status; + cd321x->update_status.pwr_status = cd321x->tps.pwr_status; + cd321x->update_status.data_status = cd321x->tps.data_status; + + cd321x->update_status.partner_identity = cd321x->tps.partner_identity; + cd321x->update_status.dp_sid_status = cd321x->dp_sid_status; + cd321x->update_status.intel_vid_status = cd321x->intel_vid_status; + cd321x->update_status.usb4_status = cd321x->usb4_status; +} + +static int cd321x_connect(struct tps6598x *tps, u32 status) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + + tps->status = status; + cd321x_queue_status(cd321x); + + /* + * Cancel pending work if not already running, then requeue after CD321X_DEBOUNCE_DELAY_MS + * regardless since the work function will check for any plug or altmodes changes since + * its last run anyway. + */ + cancel_delayed_work(&cd321x->update_work); + schedule_delayed_work(&cd321x->update_work, msecs_to_jiffies(CD321X_DEBOUNCE_DELAY_MS)); + + return 0; +} + static irqreturn_t cd321x_interrupt(int irq, void *data) { struct tps6598x *tps = data; @@ -545,23 +883,28 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) if (!event) goto err_unlock; + tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event); + if (!tps6598x_read_status(tps, &status)) - goto err_clear_ints; + goto err_unlock; - if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) + if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) { if (!tps6598x_read_power_status(tps)) - goto err_clear_ints; + goto err_unlock; + if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) { + if (tps6598x_read_partner_identity(tps)) { + dev_err(tps->dev, "failed to read partner identity\n"); + tps->partner_identity = (struct usb_pd_identity) {0}; + } + } + } if (event & APPLE_CD_REG_INT_DATA_STATUS_UPDATE) - if (!tps6598x_read_data_status(tps)) - goto err_clear_ints; + if (!tps->data->read_data_status(tps)) + goto err_unlock; - /* Handle plug insert or removal */ - if (event & APPLE_CD_REG_INT_PLUG_EVENT) - tps6598x_handle_plug_event(tps, status); - -err_clear_ints: - tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event); + /* Can be called uncondtionally since it will check for any changes itself */ + cd321x_connect(tps, status); err_unlock: mutex_unlock(&tps->lock); @@ -605,7 +948,7 @@ static irqreturn_t tps25750_interrupt(int irq, void *data) goto err_clear_ints; if (event[0] & TPS_REG_INT_DATA_STATUS_UPDATE) - if (!tps6598x_read_data_status(tps)) + if (!tps->data->read_data_status(tps)) goto err_clear_ints; /* @@ -668,25 +1011,24 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) if (!(event1[0] | event1[1] | event2[0] | event2[1])) goto err_unlock; + tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event1, intev_len); + tps6598x_block_write(tps, TPS_REG_INT_CLEAR2, event2, intev_len); + if (!tps6598x_read_status(tps, &status)) - goto err_clear_ints; + goto err_unlock; if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE) if (!tps6598x_read_power_status(tps)) - goto err_clear_ints; + goto err_unlock; if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE) - if (!tps6598x_read_data_status(tps)) - goto err_clear_ints; + if (!tps->data->read_data_status(tps)) + goto err_unlock; /* Handle plug insert or removal */ if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) tps6598x_handle_plug_event(tps, status); -err_clear_ints: - tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event1, intev_len); - tps6598x_block_write(tps, TPS_REG_INT_CLEAR2, event2, intev_len); - err_unlock: mutex_unlock(&tps->lock); @@ -887,6 +1229,94 @@ tps6598x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) return 0; } +static int cd321x_register_port_altmodes(struct cd321x *cd321x) +{ + struct typec_altmode_desc desc; + struct typec_altmode *amode; + + memset(&desc, 0, sizeof(desc)); + desc.svid = USB_TYPEC_DP_SID; + desc.mode = USB_TYPEC_DP_MODE; + desc.vdo = DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D)); + desc.vdo |= DP_CAP_DFP_D; + amode = typec_port_register_altmode(cd321x->tps.port, &desc); + if (IS_ERR(amode)) + return PTR_ERR(amode); + cd321x->port_altmode_dp = amode; + + memset(&desc, 0, sizeof(desc)); + desc.svid = USB_TYPEC_TBT_SID; + desc.mode = TYPEC_ANY_MODE; + amode = typec_port_register_altmode(cd321x->tps.port, &desc); + if (IS_ERR(amode)) { + typec_unregister_altmode(cd321x->port_altmode_dp); + cd321x->port_altmode_dp = NULL; + return PTR_ERR(amode); + } + cd321x->port_altmode_tbt = amode; + + return 0; +} + +static int +cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + int ret; + + INIT_DELAYED_WORK(&cd321x->update_work, cd321x_update_work); + + ret = tps6598x_register_port(tps, fwnode); + if (ret) + return ret; + + ret = cd321x_register_port_altmodes(cd321x); + if (ret) + goto err_unregister_port; + + cd321x->mux = fwnode_typec_mux_get(fwnode); + if (IS_ERR(cd321x->mux)) { + ret = PTR_ERR(cd321x->mux); + goto err_unregister_altmodes; + } + + cd321x->state.alt = NULL; + cd321x->state.mode = TYPEC_STATE_SAFE; + cd321x->state.data = NULL; + typec_set_mode(tps->port, TYPEC_STATE_SAFE); + + return 0; + +err_unregister_altmodes: + typec_unregister_altmode(cd321x->port_altmode_dp); + typec_unregister_altmode(cd321x->port_altmode_tbt); + cd321x->port_altmode_dp = NULL; + cd321x->port_altmode_tbt = NULL; +err_unregister_port: + typec_unregister_port(tps->port); + return ret; +} + +static void +tps6598x_unregister_port(struct tps6598x *tps) +{ + typec_unregister_port(tps->port); +} + +static void +cd321x_unregister_port(struct tps6598x *tps) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + + typec_mux_put(cd321x->mux); + cd321x->mux = NULL; + typec_unregister_altmode(cd321x->port_altmode_dp); + cd321x->port_altmode_dp = NULL; + typec_unregister_altmode(cd321x->port_altmode_tbt); + cd321x->port_altmode_tbt = NULL; + typec_unregister_port(tps->port); +} + static int tps_request_firmware(struct tps6598x *tps, const struct firmware **fw, const char **firmware_name) { @@ -1292,22 +1722,33 @@ tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) return 0; } +static void cd321x_remove(struct tps6598x *tps) +{ + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + + cancel_delayed_work_sync(&cd321x->update_work); +} + static int tps6598x_probe(struct i2c_client *client) { - struct device_node *np = client->dev.of_node; + const struct tipd_data *data; struct tps6598x *tps; struct fwnode_handle *fwnode; u32 status; u32 vid; int ret; - u64 mask1; - tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); + data = i2c_get_match_data(client); + if (!data) + return -EINVAL; + + tps = devm_kzalloc(&client->dev, data->tps_struct_size, GFP_KERNEL); if (!tps) return -ENOMEM; mutex_init(&tps->lock); tps->dev = &client->dev; + tps->data = data; tps->reset = devm_gpiod_get_optional(tps->dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(tps->reset)) @@ -1333,28 +1774,12 @@ static int tps6598x_probe(struct i2c_client *client) if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) tps->i2c_protocol = true; - if (np && of_device_is_compatible(np, "apple,cd321x")) { - /* Switch CD321X chips to the correct system power state */ - ret = cd321x_switch_power_state(tps, TPS_SYSTEM_POWER_STATE_S0); + if (tps->data->switch_power_state) { + ret = tps->data->switch_power_state(tps, TPS_SYSTEM_POWER_STATE_S0); if (ret) return ret; - - /* CD321X chips have all interrupts masked initially */ - mask1 = APPLE_CD_REG_INT_POWER_STATUS_UPDATE | - APPLE_CD_REG_INT_DATA_STATUS_UPDATE | - APPLE_CD_REG_INT_PLUG_EVENT; - - } else { - /* Enable power status, data status and plug event interrupts */ - mask1 = TPS_REG_INT_POWER_STATUS_UPDATE | - TPS_REG_INT_DATA_STATUS_UPDATE | - TPS_REG_INT_PLUG_EVENT; } - tps->data = i2c_get_match_data(client); - if (!tps->data) - return -EINVAL; - /* Make sure the controller has application firmware running */ ret = tps6598x_check_mode(tps); if (ret < 0) @@ -1366,7 +1791,7 @@ static int tps6598x_probe(struct i2c_client *client) return ret; } - ret = tps6598x_write64(tps, TPS_REG_INT_MASK1, mask1); + ret = tps6598x_write64(tps, TPS_REG_INT_MASK1, tps->data->irq_mask1); if (ret) goto err_reset_controller; @@ -1401,12 +1826,11 @@ static int tps6598x_probe(struct i2c_client *client) goto err_role_put; if (status & TPS_STATUS_PLUG_PRESENT) { - ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &tps->pwr_status); - if (ret < 0) { - dev_err(tps->dev, "failed to read power status: %d\n", ret); + if (!tps6598x_read_power_status(tps)) goto err_unregister_port; - } - ret = tps6598x_connect(tps, status); + if (!tps->data->read_data_status(tps)) + goto err_unregister_port; + ret = tps->data->connect(tps, status); if (ret) dev_err(&client->dev, "failed to register partner\n"); } @@ -1440,7 +1864,7 @@ static int tps6598x_probe(struct i2c_client *client) err_disconnect: tps6598x_disconnect(tps, 0); err_unregister_port: - typec_unregister_port(tps->port); + tps->data->unregister_port(tps); err_role_put: usb_role_switch_put(tps->role_sw); err_fwnode_put: @@ -1463,8 +1887,11 @@ static void tps6598x_remove(struct i2c_client *client) else devm_free_irq(tps->dev, client->irq, tps); + if (tps->data->remove) + tps->data->remove(tps); + tps6598x_disconnect(tps, 0); - typec_unregister_port(tps->port); + tps->data->unregister_port(tps); usb_role_switch_put(tps->role_sw); /* Reset PD controller to remove any applied patch */ @@ -1529,31 +1956,57 @@ static const struct dev_pm_ops tps6598x_pm_ops = { static const struct tipd_data cd321x_data = { .irq_handler = cd321x_interrupt, - .register_port = tps6598x_register_port, + .irq_mask1 = APPLE_CD_REG_INT_POWER_STATUS_UPDATE | + APPLE_CD_REG_INT_DATA_STATUS_UPDATE | + APPLE_CD_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct cd321x), + .remove = cd321x_remove, + .register_port = cd321x_register_port, + .unregister_port = cd321x_unregister_port, + .trace_data_status = trace_cd321x_data_status, .trace_power_status = trace_tps6598x_power_status, .trace_status = trace_tps6598x_status, .init = cd321x_init, + .read_data_status = cd321x_read_data_status, .reset = cd321x_reset, + .switch_power_state = cd321x_switch_power_state, + .connect = cd321x_connect, }; static const struct tipd_data tps6598x_data = { .irq_handler = tps6598x_interrupt, + .irq_mask1 = TPS_REG_INT_POWER_STATUS_UPDATE | + TPS_REG_INT_DATA_STATUS_UPDATE | + TPS_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct tps6598x), .register_port = tps6598x_register_port, + .unregister_port = tps6598x_unregister_port, + .trace_data_status = trace_tps6598x_data_status, .trace_power_status = trace_tps6598x_power_status, .trace_status = trace_tps6598x_status, .apply_patch = tps6598x_apply_patch, .init = tps6598x_init, + .read_data_status = tps6598x_read_data_status, .reset = tps6598x_reset, + .connect = tps6598x_connect, }; static const struct tipd_data tps25750_data = { .irq_handler = tps25750_interrupt, + .irq_mask1 = TPS_REG_INT_POWER_STATUS_UPDATE | + TPS_REG_INT_DATA_STATUS_UPDATE | + TPS_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct tps6598x), .register_port = tps25750_register_port, + .unregister_port = tps6598x_unregister_port, + .trace_data_status = trace_tps6598x_data_status, .trace_power_status = trace_tps25750_power_status, .trace_status = trace_tps25750_status, .apply_patch = tps25750_apply_patch, .init = tps25750_init, + .read_data_status = tps6598x_read_data_status, .reset = tps25750_reset, + .connect = tps6598x_connect, }; static const struct of_device_id tps6598x_of_match[] = { diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index cecb8d11d239..03edbb77bbd6 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -197,6 +197,11 @@ #define TPS_DATA_STATUS_FORCE_LSX BIT(23) #define TPS_DATA_STATUS_POWER_MISMATCH BIT(24) +/* modified TPS_REG_DATA_STATUS bits for CD321x (and likely also TPS65987DDK) */ +#define CD321X_DATA_STATUS_HPD_IRQ BIT(14) +#define CD321X_DATA_STATUS_HPD_LEVEL BIT(15) +#define CD321X_DATA_STATUS_USB4_CONNECTION BIT(23) + #define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK GENMASK(11, 10) #define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT(x) \ TPS_FIELD_GET(TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK, (x)) diff --git a/drivers/usb/typec/tipd/trace.h b/drivers/usb/typec/tipd/trace.h index bea383f2db9d..e9e40425138a 100644 --- a/drivers/usb/typec/tipd/trace.h +++ b/drivers/usb/typec/tipd/trace.h @@ -217,6 +217,26 @@ { TPS_DATA_STATUS_FORCE_LSX, "FORCE_LSX" }, \ { TPS_DATA_STATUS_POWER_MISMATCH, "POWER_MISMATCH" }) +#define show_cd321x_data_status_flags(data_status) \ + __print_flags(data_status & TPS_DATA_STATUS_FLAGS_MASK, "|", \ + { TPS_DATA_STATUS_DATA_CONNECTION, "DATA_CONNECTION" }, \ + { TPS_DATA_STATUS_UPSIDE_DOWN, "DATA_UPSIDE_DOWN" }, \ + { TPS_DATA_STATUS_ACTIVE_CABLE, "ACTIVE_CABLE" }, \ + { TPS_DATA_STATUS_USB2_CONNECTION, "USB2_CONNECTION" }, \ + { TPS_DATA_STATUS_USB3_CONNECTION, "USB3_CONNECTION" }, \ + { TPS_DATA_STATUS_USB3_GEN2, "USB3_GEN2" }, \ + { TPS_DATA_STATUS_USB_DATA_ROLE, "USB_DATA_ROLE" }, \ + { TPS_DATA_STATUS_DP_CONNECTION, "DP_CONNECTION" }, \ + { TPS_DATA_STATUS_DP_SINK, "DP_SINK" }, \ + { CD321X_DATA_STATUS_HPD_IRQ, "HPD_IRQ" }, \ + { CD321X_DATA_STATUS_HPD_LEVEL, "HPD_LEVEL" }, \ + { TPS_DATA_STATUS_TBT_CONNECTION, "TBT_CONNECTION" }, \ + { TPS_DATA_STATUS_TBT_TYPE, "TBT_TYPE" }, \ + { TPS_DATA_STATUS_OPTICAL_CABLE, "OPTICAL_CABLE" }, \ + { TPS_DATA_STATUS_ACTIVE_LINK_TRAIN, "ACTIVE_LINK_TRAIN" }, \ + { CD321X_DATA_STATUS_USB4_CONNECTION, "USB4" }, \ + { TPS_DATA_STATUS_POWER_MISMATCH, "POWER_MISMATCH" }) + #define show_data_status_dp_pin_assignment(data_status) \ __print_symbolic(TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT(data_status), \ { TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_E, "E" }, \ @@ -388,6 +408,25 @@ TRACE_EVENT(tps6598x_data_status, ) ); +TRACE_EVENT(cd321x_data_status, + TP_PROTO(u32 data_status), + TP_ARGS(data_status), + + TP_STRUCT__entry( + __field(u32, data_status) + ), + + TP_fast_assign( + __entry->data_status = data_status; + ), + + TP_printk("%s%s%s", + show_cd321x_data_status_flags(__entry->data_status), + __entry->data_status & TPS_DATA_STATUS_DP_CONNECTION ? ", DP pinout " : "", + maybe_show_data_status_dp_pin_assignment(__entry->data_status) + ) +); + #endif /* _TPS6598X_TRACE_H_ */ /* This part must be outside protection */ diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c index 92ebf1a2defd..f73f2b54554e 100644 --- a/drivers/usb/typec/ucsi/debugfs.c +++ b/drivers/usb/typec/ucsi/debugfs.c @@ -35,6 +35,7 @@ static int ucsi_cmd(void *data, u64 val) case UCSI_SET_SINK_PATH: case UCSI_SET_NEW_CAM: case UCSI_SET_USB: + case UCSI_READ_POWER_LEVEL: ret = ucsi_send_command(ucsi, val, NULL, 0); break; case UCSI_GET_CAPABILITY: @@ -80,6 +81,33 @@ static int ucsi_resp_show(struct seq_file *s, void *not_used) } DEFINE_SHOW_ATTRIBUTE(ucsi_resp); +static int ucsi_peak_curr_show(struct seq_file *m, void *v) +{ + struct ucsi *ucsi = m->private; + + seq_printf(m, "%u mA\n", ucsi->connector->peak_current); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ucsi_peak_curr); + +static int ucsi_avg_curr_show(struct seq_file *m, void *v) +{ + struct ucsi *ucsi = m->private; + + seq_printf(m, "%u mA\n", ucsi->connector->avg_current); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ucsi_avg_curr); + +static int ucsi_vbus_volt_show(struct seq_file *m, void *v) +{ + struct ucsi *ucsi = m->private; + + seq_printf(m, "%u mV\n", ucsi->connector->vbus_voltage); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ucsi_vbus_volt); + void ucsi_debugfs_register(struct ucsi *ucsi) { ucsi->debugfs = kzalloc(sizeof(*ucsi->debugfs), GFP_KERNEL); @@ -89,6 +117,9 @@ void ucsi_debugfs_register(struct ucsi *ucsi) ucsi->debugfs->dentry = debugfs_create_dir(dev_name(ucsi->dev), ucsi_debugfs_root); debugfs_create_file("command", 0200, ucsi->debugfs->dentry, ucsi, &ucsi_cmd_fops); debugfs_create_file("response", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_resp_fops); + debugfs_create_file("peak_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_peak_curr_fops); + debugfs_create_file("avg_current", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_avg_curr_fops); + debugfs_create_file("vbus_voltage", 0400, ucsi->debugfs->dentry, ucsi, &ucsi_vbus_volt_fops); } void ucsi_debugfs_unregister(struct ucsi *ucsi) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 5739ea2abdd1..3f568f790f39 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1217,9 +1217,11 @@ static void ucsi_handle_connector_change(struct work_struct *work) struct ucsi_connector *con = container_of(work, struct ucsi_connector, work); struct ucsi *ucsi = con->ucsi; + u8 curr_scale, volt_scale; enum typec_role role; u16 change; int ret; + u32 val; mutex_lock(&con->lock); @@ -1291,6 +1293,21 @@ static void ucsi_handle_connector_change(struct work_struct *work) if (change & UCSI_CONSTAT_BC_CHANGE) ucsi_port_psy_changed(con); + if (con->ucsi->version >= UCSI_VERSION_2_1 && + UCSI_CONSTAT(con, PWR_READING_READY_V2_1)) { + curr_scale = UCSI_CONSTAT(con, CURRENT_SCALE_V2_1); + volt_scale = UCSI_CONSTAT(con, VOLTAGE_SCALE_V2_1); + + val = UCSI_CONSTAT(con, PEAK_CURRENT_V2_1); + con->peak_current = UCSI_CONSTAT_CURR_SCALE_MULT * curr_scale * val; + + val = UCSI_CONSTAT(con, AVG_CURRENT_V2_1); + con->avg_current = UCSI_CONSTAT_CURR_SCALE_MULT * curr_scale * val; + + val = UCSI_CONSTAT(con, VBUS_VOLTAGE_V2_1); + con->vbus_voltage = UCSI_CONSTAT_VOLT_SCALE_MULT * volt_scale * val; + } + out_unlock: mutex_unlock(&con->lock); } diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index ebd7c27c2cc7..e301d9012936 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -131,6 +131,7 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num); #define UCSI_GET_PD_MESSAGE 0x15 #define UCSI_GET_CAM_CS 0x18 #define UCSI_SET_SINK_PATH 0x1c +#define UCSI_READ_POWER_LEVEL 0x1e #define UCSI_SET_USB 0x21 #define UCSI_GET_LPM_PPM_INFO 0x22 @@ -359,6 +360,14 @@ struct ucsi_cable_property { #define UCSI_CONSTAT_BC_SLOW_CHARGING 2 #define UCSI_CONSTAT_BC_TRICKLE_CHARGING 3 #define UCSI_CONSTAT_PD_VERSION_V1_2 UCSI_DECLARE_BITFIELD_V1_2(70, 16) +#define UCSI_CONSTAT_PWR_READING_READY_V2_1 UCSI_DECLARE_BITFIELD_V2_1(89, 1) +#define UCSI_CONSTAT_CURRENT_SCALE_V2_1 UCSI_DECLARE_BITFIELD_V2_1(90, 3) +#define UCSI_CONSTAT_PEAK_CURRENT_V2_1 UCSI_DECLARE_BITFIELD_V2_1(93, 16) +#define UCSI_CONSTAT_AVG_CURRENT_V2_1 UCSI_DECLARE_BITFIELD_V2_1(109, 16) +#define UCSI_CONSTAT_VOLTAGE_SCALE_V2_1 UCSI_DECLARE_BITFIELD_V2_1(125, 4) +#define UCSI_CONSTAT_VBUS_VOLTAGE_V2_1 UCSI_DECLARE_BITFIELD_V2_1(129, 16) +#define UCSI_CONSTAT_CURR_SCALE_MULT 5 +#define UCSI_CONSTAT_VOLT_SCALE_MULT 5 /* Connector Status Change Bits. */ #define UCSI_CONSTAT_EXT_SUPPLY_CHANGE BIT(1) @@ -519,6 +528,10 @@ struct ucsi_connector { u32 src_pdos[PDO_MAX_OBJECTS]; int num_pdos; + u32 peak_current; + u32 avg_current; + u32 vbus_voltage; + /* USB PD objects */ struct usb_power_delivery *pd; struct usb_power_delivery_capabilities *port_source_caps; diff --git a/drivers/usb/typec/ucsi/ucsi_stm32g0.c b/drivers/usb/typec/ucsi/ucsi_stm32g0.c index 57ef7d83a412..838ac0185082 100644 --- a/drivers/usb/typec/ucsi/ucsi_stm32g0.c +++ b/drivers/usb/typec/ucsi/ucsi_stm32g0.c @@ -10,6 +10,7 @@ #include <linux/firmware.h> #include <linux/i2c.h> #include <linux/interrupt.h> +#include <linux/minmax.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/unaligned.h> @@ -523,11 +524,7 @@ static void ucsi_stm32g0_fw_cb(const struct firmware *fw, void *context) data = fw->data; end = fw->data + fw->size; while (data < end) { - if ((end - data) < STM32G0_I2C_BL_SZ) - size = end - data; - else - size = STM32G0_I2C_BL_SZ; - + size = min(end - data, STM32G0_I2C_BL_SZ); ret = ucsi_stm32g0_bl_write(g0->ucsi, addr, data, size); if (ret) { dev_err(g0->dev, "Write failed %d\n", ret); diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index e70fba9f55d6..0d6c10a8490c 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -765,6 +765,17 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag ctrlreq->wValue, vdev->rhport); vdev->udev = usb_get_dev(urb->dev); + /* + * NOTE: A similar operation has been done via + * USB_REQ_GET_DESCRIPTOR handler below, which is + * supposed to always precede USB_REQ_SET_ADDRESS. + * + * It's not entirely clear if operating on a different + * usb_device instance here is a real possibility, + * otherwise this call and vdev->udev assignment above + * should be dropped. + */ + dev_pm_syscore_device(&vdev->udev->dev, true); usb_put_dev(old); spin_lock(&vdev->ud.lock); @@ -785,6 +796,17 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag "Not yet?:Get_Descriptor to device 0 (get max pipe size)\n"); vdev->udev = usb_get_dev(urb->dev); + /* + * Set syscore PM flag for the virtually attached + * devices to ensure they will not enter suspend on + * the client side. + * + * Note this doesn't have any impact on the physical + * devices attached to the host system on the server + * side, hence there is no need to undo the operation + * on disconnect. + */ + dev_pm_syscore_device(&vdev->udev->dev, true); usb_put_dev(old); goto out; diff --git a/drivers/w1/masters/matrox_w1.c b/drivers/w1/masters/matrox_w1.c index 2852cd2dc67c..146fa7c6e74e 100644 --- a/drivers/w1/masters/matrox_w1.c +++ b/drivers/w1/masters/matrox_w1.c @@ -47,7 +47,6 @@ struct matrox_device { unsigned long phys_addr; void __iomem *virt_addr; - unsigned long found; struct w1_bus_master *bus_master; }; @@ -158,8 +157,6 @@ static int matrox_w1_probe(struct pci_dev *pdev, const struct pci_device_id *ent pci_set_drvdata(pdev, dev); - dev->found = 1; - dev_info(&pdev->dev, "Matrox G400 GPIO transport layer for 1-wire.\n"); return 0; @@ -176,10 +173,9 @@ static void matrox_w1_remove(struct pci_dev *pdev) { struct matrox_device *dev = pci_get_drvdata(pdev); - if (dev->found) { - w1_remove_master_device(dev->bus_master); - iounmap(dev->virt_addr); - } + w1_remove_master_device(dev->bus_master); + iounmap(dev->virt_addr); + kfree(dev); } |